Complete overhaul of MTProto message system, parallelized getPwrChat
This commit is contained in:
parent
a376f0bf82
commit
eca8542be4
@ -21,11 +21,13 @@ namespace danog\MadelineProto;
|
|||||||
|
|
||||||
use Amp\ByteStream\ClosedException;
|
use Amp\ByteStream\ClosedException;
|
||||||
use Amp\Deferred;
|
use Amp\Deferred;
|
||||||
|
use Amp\Failure;
|
||||||
use danog\MadelineProto\Loop\Connection\CheckLoop;
|
use danog\MadelineProto\Loop\Connection\CheckLoop;
|
||||||
use danog\MadelineProto\Loop\Connection\HttpWaitLoop;
|
use danog\MadelineProto\Loop\Connection\HttpWaitLoop;
|
||||||
use danog\MadelineProto\Loop\Connection\PingLoop;
|
use danog\MadelineProto\Loop\Connection\PingLoop;
|
||||||
use danog\MadelineProto\Loop\Connection\ReadLoop;
|
use danog\MadelineProto\Loop\Connection\ReadLoop;
|
||||||
use danog\MadelineProto\Loop\Connection\WriteLoop;
|
use danog\MadelineProto\Loop\Connection\WriteLoop;
|
||||||
|
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||||
use danog\MadelineProto\MTProtoSession\Session;
|
use danog\MadelineProto\MTProtoSession\Session;
|
||||||
use danog\MadelineProto\Stream\ConnectionContext;
|
use danog\MadelineProto\Stream\ConnectionContext;
|
||||||
use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream;
|
use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream;
|
||||||
@ -39,6 +41,8 @@ use danog\MadelineProto\Stream\Transport\WsStream;
|
|||||||
*
|
*
|
||||||
* Manages connection to Telegram datacenters
|
* Manages connection to Telegram datacenters
|
||||||
*
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
* @author Daniil Gentili <daniil@daniil.it>
|
* @author Daniil Gentili <daniil@daniil.it>
|
||||||
*/
|
*/
|
||||||
class Connection
|
class Connection
|
||||||
@ -336,11 +340,12 @@ class Connection
|
|||||||
if (!isset($this->pinger) && ($this->ctx->hasStreamName(WssStream::class) || $this->ctx->hasStreamName(WsStream::class))) {
|
if (!isset($this->pinger) && ($this->ctx->hasStreamName(WssStream::class) || $this->ctx->hasStreamName(WsStream::class))) {
|
||||||
$this->pinger = new PingLoop($this);
|
$this->pinger = new PingLoop($this);
|
||||||
}
|
}
|
||||||
foreach ($this->new_outgoing as $message_id) {
|
foreach ($this->new_outgoing as $message_id => $message) {
|
||||||
if ($this->outgoing_messages[$message_id]['unencrypted']) {
|
if ($message->isUnencrypted()) {
|
||||||
$promise = $this->outgoing_messages[$message_id]['promise'];
|
\Amp\Loop::defer(function () use ($message) {
|
||||||
\Amp\Loop::defer(function () use ($promise) {
|
if (!($message->getState() & OutgoingMessage::STATE_REPLIED)) {
|
||||||
$promise->fail(new Exception('Restart because we were reconnected'));
|
$message->reply(new Failure(new Exception('Restart because we were reconnected')));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
unset($this->new_outgoing[$message_id], $this->outgoing_messages[$message_id]);
|
unset($this->new_outgoing[$message_id], $this->outgoing_messages[$message_id]);
|
||||||
}
|
}
|
||||||
@ -384,40 +389,37 @@ class Connection
|
|||||||
* 'tries' => number
|
* 'tries' => number
|
||||||
* ]
|
* ]
|
||||||
*
|
*
|
||||||
* @param array $message The message to send
|
* @param OutgoingMessage $message The message to send
|
||||||
* @param boolean $flush Whether to flush the message right away
|
* @param boolean $flush Whether to flush the message right away
|
||||||
*
|
*
|
||||||
* @return \Generator
|
* @return \Generator
|
||||||
*/
|
*/
|
||||||
public function sendMessage(array $message, bool $flush = true): \Generator
|
public function sendMessage(OutgoingMessage $message, bool $flush = true): \Generator
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$message->trySend();
|
||||||
if (!isset($message['serialized_body'])) {
|
$promise = $message->getSendPromise();
|
||||||
$body = $message['body'] instanceof \Generator
|
if (!$message->hasSerializedBody() || $message->shouldRefreshReferences()) {
|
||||||
? yield from $message['body']
|
$body = yield from $message->getBody();
|
||||||
: $message['body'];
|
if ($message->shouldRefreshReferences()) {
|
||||||
$refreshNext = $message['refreshReferences'] ?? false;
|
|
||||||
if ($refreshNext) {
|
|
||||||
$this->API->referenceDatabase->refreshNext(true);
|
$this->API->referenceDatabase->refreshNext(true);
|
||||||
}
|
}
|
||||||
if ($message['method']) {
|
if ($message->isMethod()) {
|
||||||
$body = (yield from $this->API->getTL()->serializeMethod($message['_'], $body));
|
$body = yield from $this->API->getTL()->serializeMethod($message->getConstructor(), $body);
|
||||||
} else {
|
} else {
|
||||||
$body['_'] = $message['_'];
|
$body['_'] = $message->getConstructor();
|
||||||
$body = (yield from $this->API->getTL()->serializeObject(['type' => ''], $body, $message['_']));
|
$body = yield from $this->API->getTL()->serializeObject(['type' => ''], $body, $message->getConstructor());
|
||||||
}
|
}
|
||||||
if ($refreshNext) {
|
if ($message->shouldRefreshReferences()) {
|
||||||
$this->API->referenceDatabase->refreshNext(false);
|
$this->API->referenceDatabase->refreshNext(false);
|
||||||
}
|
}
|
||||||
$message['serialized_body'] = $body;
|
$message->setSerializedBody($body);
|
||||||
unset($body);
|
unset($body);
|
||||||
}
|
}
|
||||||
$message['send_promise'] = $deferred;
|
$this->pendingOutgoing[$this->pendingOutgoingKey++] = $message;
|
||||||
$this->pending_outgoing[$this->pending_outgoing_key++] = $message;
|
|
||||||
if ($flush && isset($this->writer)) {
|
if ($flush && isset($this->writer)) {
|
||||||
$this->writer->resume();
|
$this->writer->resume();
|
||||||
}
|
}
|
||||||
return yield $deferred->promise();
|
return yield $promise;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Flush pending packets.
|
* Flush pending packets.
|
||||||
|
@ -401,7 +401,7 @@ class DataCenter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$combos = array_merge($proxyCombos, $combos);
|
$combos = \array_merge($proxyCombos, $combos);
|
||||||
if ($dc_number) {
|
if ($dc_number) {
|
||||||
$combos[] = [[DefaultStream::class, []], [BufferedRawStream::class, []], [HttpsStream::class, []]];
|
$combos[] = [[DefaultStream::class, []], [BufferedRawStream::class, []], [HttpsStream::class, []]];
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ use Amp\Promise;
|
|||||||
use Amp\Success;
|
use Amp\Success;
|
||||||
use danog\MadelineProto\Loop\Generic\PeriodicLoopInternal;
|
use danog\MadelineProto\Loop\Generic\PeriodicLoopInternal;
|
||||||
use danog\MadelineProto\MTProto\AuthKey;
|
use danog\MadelineProto\MTProto\AuthKey;
|
||||||
|
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||||
use danog\MadelineProto\MTProto\PermAuthKey;
|
use danog\MadelineProto\MTProto\PermAuthKey;
|
||||||
use danog\MadelineProto\MTProto\TempAuthKey;
|
use danog\MadelineProto\MTProto\TempAuthKey;
|
||||||
use danog\MadelineProto\Settings\Connection as ConnectionSettings;
|
use danog\MadelineProto\Settings\Connection as ConnectionSettings;
|
||||||
@ -327,7 +328,7 @@ class DataCenterConnection implements JsonSerializable
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function flush()
|
public function flush(): void
|
||||||
{
|
{
|
||||||
foreach ($this->connections as $socket) {
|
foreach ($this->connections as $socket) {
|
||||||
$socket->flush();
|
$socket->flush();
|
||||||
@ -424,11 +425,11 @@ class DataCenterConnection implements JsonSerializable
|
|||||||
$backup = $this->connections[$id]->backupSession();
|
$backup = $this->connections[$id]->backupSession();
|
||||||
$list = '';
|
$list = '';
|
||||||
foreach ($backup as $k => $message) {
|
foreach ($backup as $k => $message) {
|
||||||
if (($message['_'] ?? '') === 'msgs_state_req') {
|
if ($message->getConstructor() === 'msgs_state_req') {
|
||||||
unset($backup[$k]);
|
unset($backup[$k]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$list .= $message['_'] ?? '-';
|
$list .= $message->getConstructor();
|
||||||
$list .= ', ';
|
$list .= ', ';
|
||||||
}
|
}
|
||||||
$this->API->logger->logger("Backed up {$list} from DC {$this->datacenter}.{$id}");
|
$this->API->logger->logger("Backed up {$list} from DC {$this->datacenter}.{$id}");
|
||||||
@ -480,14 +481,15 @@ class DataCenterConnection implements JsonSerializable
|
|||||||
$this->backup = [];
|
$this->backup = [];
|
||||||
$count = \count($backup);
|
$count = \count($backup);
|
||||||
$this->API->logger->logger("Restoring {$count} messages to DC {$this->datacenter}");
|
$this->API->logger->logger("Restoring {$count} messages to DC {$this->datacenter}");
|
||||||
|
/** @var OutgoingMessage */
|
||||||
foreach ($backup as $message) {
|
foreach ($backup as $message) {
|
||||||
if (isset($message['seqno'])) {
|
if ($message->hasSeqno()) {
|
||||||
unset($message['seqno']);
|
$message->setSeqno(null);
|
||||||
}
|
}
|
||||||
if (isset($message['msg_id'])) {
|
if ($message->hasMsgId()) {
|
||||||
unset($message['msg_id']);
|
$message->setMsgId(null);
|
||||||
}
|
}
|
||||||
if (isset($message['body'])) {
|
if (!($message->getState() & OutgoingMessage::STATE_REPLIED)) {
|
||||||
Tools::callFork($this->getConnection()->sendMessage($message, false));
|
Tools::callFork($this->getConnection()->sendMessage($message, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,10 +208,8 @@ class RedisArray extends SqlArray
|
|||||||
return call(function () {
|
return call(function () {
|
||||||
$iterator = $this->getIterator();
|
$iterator = $this->getIterator();
|
||||||
$result = [];
|
$result = [];
|
||||||
$len = \strlen($this->rKey(''));
|
|
||||||
while (yield $iterator->advance()) {
|
while (yield $iterator->advance()) {
|
||||||
[$key, $value] = $iterator->getCurrent();
|
[$key, $value] = $iterator->getCurrent();
|
||||||
$key = \substr($key, $len);
|
|
||||||
$result[$key] = $value;
|
$result[$key] = $value;
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
@ -223,8 +221,10 @@ class RedisArray extends SqlArray
|
|||||||
return new Producer(function (callable $emit) {
|
return new Producer(function (callable $emit) {
|
||||||
$request = $this->db->scan($this->itKey());
|
$request = $this->db->scan($this->itKey());
|
||||||
|
|
||||||
|
$len = \strlen($this->rKey(''));
|
||||||
while (yield $request->advance()) {
|
while (yield $request->advance()) {
|
||||||
yield $emit([$key = $request->getCurrent(), \unserialize(yield $this->db->get($key))]);
|
$key = $request->getCurrent();
|
||||||
|
yield $emit([\substr($key, $len), \unserialize(yield $this->db->get($key))]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -79,40 +79,41 @@ class CheckLoop extends ResumableSignalLoop
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!isset($connection->new_outgoing[$message_id])) {
|
if (!isset($connection->new_outgoing[$message_id])) {
|
||||||
$API->logger->logger('Already got response for '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id);
|
$API->logger->logger('Already got response for '.$connection->outgoing_messages[$message_id]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
$message = $connection->new_outgoing[$message_id];
|
||||||
$chr = \ord($chr);
|
$chr = \ord($chr);
|
||||||
switch ($chr & 7) {
|
switch ($chr & 7) {
|
||||||
case 0:
|
case 0:
|
||||||
$API->logger->logger('Wrong message status 0 for '.$connection->outgoing_messages[$message_id]['_'], \danog\MadelineProto\Logger::FATAL_ERROR);
|
$API->logger->logger("Wrong message status 0 for $message", \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
case 3:
|
case 3:
|
||||||
if ($connection->outgoing_messages[$message_id]['_'] === 'msgs_state_req') {
|
if ($message->getConstructor() === 'msgs_state_req') {
|
||||||
$connection->gotResponseForOutgoingMessageId($message_id);
|
$connection->gotResponseForOutgoingMessage($message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' not received by server, resending...', \danog\MadelineProto\Logger::ERROR);
|
$API->logger->logger("Message $message not received by server, resending...", \danog\MadelineProto\Logger::ERROR);
|
||||||
$connection->methodRecall('watcherId', ['message_id' => $message_id, 'postpone' => true]);
|
$connection->methodRecall('watcherId', ['message_id' => $message_id, 'postpone' => true]);
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
if ($chr & 32) {
|
if ($chr & 32) {
|
||||||
if ($connection->outgoing_messages[$message_id]['sent'] + $timeoutResend < \time()) {
|
if ($message->getSent() + $timeoutResend < \time()) {
|
||||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' received by server and is being processed for way too long, resending request...', \danog\MadelineProto\Logger::ERROR);
|
$API->logger->logger("Message $message received by server and is being processed for way too long, resending request...", \danog\MadelineProto\Logger::ERROR);
|
||||||
$connection->methodRecall('', ['message_id' => $message_id, 'postpone' => true]);
|
$connection->methodRecall('', ['message_id' => $message_id, 'postpone' => true]);
|
||||||
} else {
|
} else {
|
||||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' received by server and is being processed, waiting...', \danog\MadelineProto\Logger::ERROR);
|
$API->logger->logger("Message $message received by server and is being processed, waiting...", \danog\MadelineProto\Logger::ERROR);
|
||||||
}
|
}
|
||||||
} elseif ($chr & 64) {
|
} elseif ($chr & 64) {
|
||||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' received by server and was already processed, requesting reply...', \danog\MadelineProto\Logger::ERROR);
|
$API->logger->logger("Message $message received by server and was already processed, requesting reply...", \danog\MadelineProto\Logger::ERROR);
|
||||||
$reply[] = $message_id;
|
$reply[] = $message_id;
|
||||||
} elseif ($chr & 128) {
|
} elseif ($chr & 128) {
|
||||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' received by server and was already sent, requesting reply...', \danog\MadelineProto\Logger::ERROR);
|
$API->logger->logger("Message $message received by server and was already sent, requesting reply...", \danog\MadelineProto\Logger::ERROR);
|
||||||
$reply[] = $message_id;
|
$reply[] = $message_id;
|
||||||
} else {
|
} else {
|
||||||
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.$message_id.' received by server, waiting...', \danog\MadelineProto\Logger::ERROR);
|
$API->logger->logger("Message $message received by server, waiting...", \danog\MadelineProto\Logger::ERROR);
|
||||||
$reply[] = $message_id;
|
$reply[] = $message_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,16 +129,19 @@ class CheckLoop extends ResumableSignalLoop
|
|||||||
$list = '';
|
$list = '';
|
||||||
// Don't edit this here pls
|
// Don't edit this here pls
|
||||||
foreach ($message_ids as $message_id) {
|
foreach ($message_ids as $message_id) {
|
||||||
$list .= $connection->outgoing_messages[$message_id]['_'].', ';
|
$list .= $connection->outgoing_messages[$message_id]->getConstructor().', ';
|
||||||
}
|
}
|
||||||
$API->logger->logger("Still missing {$list} on DC {$datacenter}, sending state request", \danog\MadelineProto\Logger::ERROR);
|
$API->logger->logger("Still missing {$list} on DC {$datacenter}, sending state request", \danog\MadelineProto\Logger::ERROR);
|
||||||
yield from $connection->objectCall('msgs_state_req', ['msg_ids' => $message_ids], ['promise' => $deferred]);
|
yield from $connection->objectCall('msgs_state_req', ['msg_ids' => $message_ids], ['promise' => $deferred]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($connection->new_outgoing as $message_id) {
|
foreach ($connection->new_outgoing as $message_id => $message) {
|
||||||
if (isset($connection->outgoing_messages[$message_id]['sent']) && $connection->outgoing_messages[$message_id]['sent'] + $timeout < \time() && $connection->outgoing_messages[$message_id]['unencrypted']) {
|
if ($message->wasSent()
|
||||||
$API->logger->logger('Still missing '.$connection->outgoing_messages[$message_id]['_'].' with message id '.$message_id." on DC {$datacenter}, resending", \danog\MadelineProto\Logger::ERROR);
|
&& $message->getSent() + $timeout < \time()
|
||||||
$connection->methodRecall('', ['message_id' => $message_id, 'postpone' => true]);
|
&& $message->isUnencrypted()
|
||||||
|
) {
|
||||||
|
$API->logger->logger("Still missing $message on DC $datacenter, resending", \danog\MadelineProto\Logger::ERROR);
|
||||||
|
$connection->methodRecall('', ['message_id' => $message->getMsgId(), 'postpone' => true]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$connection->flush();
|
$connection->flush();
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
namespace danog\MadelineProto\Loop\Connection;
|
namespace danog\MadelineProto\Loop\Connection;
|
||||||
|
|
||||||
use danog\Loop\ResumableSignalLoop;
|
use danog\Loop\ResumableSignalLoop;
|
||||||
|
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HttpWait loop.
|
* HttpWait loop.
|
||||||
@ -56,8 +57,16 @@ class HttpWaitLoop extends ResumableSignalLoop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$API->logger->logger("DC {$datacenter}: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
|
$API->logger->logger("DC {$datacenter}: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
|
||||||
if ($connection->countHttpSent() === $connection->countHttpReceived() && (!empty($connection->pending_outgoing) || !empty($connection->new_outgoing) && !$connection->hasPendingCalls())) {
|
if ($connection->countHttpSent() === $connection->countHttpReceived() && (!empty($connection->pendingOutgoing) || !empty($connection->new_outgoing) && !$connection->hasPendingCalls())) {
|
||||||
yield from $connection->sendMessage(['_' => 'http_wait', 'body' => ['max_wait' => 30000, 'wait_after' => 0, 'max_delay' => 0], 'contentRelated' => true, 'unencrypted' => false, 'method' => false]);
|
yield from $connection->sendMessage(
|
||||||
|
new OutgoingMessage(
|
||||||
|
['max_wait' => 30000, 'wait_after' => 0, 'max_delay' => 0],
|
||||||
|
'http_wait',
|
||||||
|
'',
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$API->logger->logger("DC {$datacenter}: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
|
$API->logger->logger("DC {$datacenter}: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ use Amp\Loop;
|
|||||||
use Amp\Websocket\ClosedException;
|
use Amp\Websocket\ClosedException;
|
||||||
use danog\Loop\SignalLoop;
|
use danog\Loop\SignalLoop;
|
||||||
use danog\MadelineProto\Logger;
|
use danog\MadelineProto\Logger;
|
||||||
|
use danog\MadelineProto\MTProto\IncomingMessage;
|
||||||
use danog\MadelineProto\MTProtoTools\Crypt;
|
use danog\MadelineProto\MTProtoTools\Crypt;
|
||||||
use danog\MadelineProto\NothingInTheSocketException;
|
use danog\MadelineProto\NothingInTheSocketException;
|
||||||
use danog\MadelineProto\Tools;
|
use danog\MadelineProto\Tools;
|
||||||
@ -70,8 +71,8 @@ class ReadLoop extends SignalLoop
|
|||||||
$API->logger->logger("WARNING: Resetting auth key in DC {$datacenter}...", Logger::WARNING);
|
$API->logger->logger("WARNING: Resetting auth key in DC {$datacenter}...", Logger::WARNING);
|
||||||
$shared->setTempAuthKey(null);
|
$shared->setTempAuthKey(null);
|
||||||
$shared->resetSession();
|
$shared->resetSession();
|
||||||
foreach ($connection->new_outgoing as $message_id) {
|
foreach ($connection->new_outgoing as $message) {
|
||||||
$connection->outgoing_messages[$message_id]['sent'] = 0;
|
$message->resetSent();
|
||||||
}
|
}
|
||||||
yield from $shared->reconnect();
|
yield from $shared->reconnect();
|
||||||
yield from $API->initAuthorization();
|
yield from $API->initAuthorization();
|
||||||
@ -146,7 +147,6 @@ class ReadLoop extends SignalLoop
|
|||||||
}
|
}
|
||||||
yield $buffer->bufferRead($left);
|
yield $buffer->bufferRead($left);
|
||||||
}
|
}
|
||||||
$connection->incoming_messages[$message_id] = [];
|
|
||||||
} elseif ($auth_key_id === $shared->getTempAuthKey()->getID()) {
|
} elseif ($auth_key_id === $shared->getTempAuthKey()->getID()) {
|
||||||
$message_key = yield $buffer->bufferRead(16);
|
$message_key = yield $buffer->bufferRead(16);
|
||||||
list($aes_key, $aes_iv) = Crypt::aesCalculate($message_key, $shared->getTempAuthKey()->getAuthKey(), false);
|
list($aes_key, $aes_iv) = Crypt::aesCalculate($message_key, $shared->getTempAuthKey()->getAuthKey(), false);
|
||||||
@ -191,18 +191,22 @@ class ReadLoop extends SignalLoop
|
|||||||
if ($message_key != \substr(\hash('sha256', \substr($shared->getTempAuthKey()->getAuthKey(), 96, 32).$decrypted_data, true), 8, 16)) {
|
if ($message_key != \substr(\hash('sha256', \substr($shared->getTempAuthKey()->getAuthKey(), 96, 32).$decrypted_data, true), 8, 16)) {
|
||||||
throw new \danog\MadelineProto\SecurityException('msg_key mismatch');
|
throw new \danog\MadelineProto\SecurityException('msg_key mismatch');
|
||||||
}
|
}
|
||||||
$connection->incoming_messages[$message_id] = ['seq_no' => $seq_no];
|
|
||||||
} else {
|
} else {
|
||||||
$API->logger->logger('Got unknown auth_key id', Logger::ERROR);
|
$API->logger->logger('Got unknown auth_key id', Logger::ERROR);
|
||||||
return -404;
|
return -404;
|
||||||
}
|
}
|
||||||
$deserialized = yield from $API->getTL()->deserialize($message_data, ['type' => '', 'connection' => $connection]);
|
[$deserialized, $sideEffects] = $API->getTL()->deserialize($message_data, ['type' => '', 'connection' => $connection]);
|
||||||
if (isset($API->referenceDatabase)) {
|
if (isset($API->referenceDatabase)) {
|
||||||
$API->referenceDatabase->reset();
|
$API->referenceDatabase->reset();
|
||||||
}
|
}
|
||||||
$connection->incoming_messages[$message_id]['content'] = $deserialized;
|
$message = new IncomingMessage($deserialized, $message_id);
|
||||||
$connection->incoming_messages[$message_id]['response'] = -1;
|
if (isset($seq_no)) {
|
||||||
$connection->new_incoming[$message_id] = $message_id;
|
$message->setSeqNo($seq_no);
|
||||||
|
}
|
||||||
|
if ($sideEffects) {
|
||||||
|
$message->setSideEffects($sideEffects);
|
||||||
|
}
|
||||||
|
$connection->new_incoming[$message_id] = $connection->incoming_messages[$message_id] = $message;
|
||||||
$API->logger->logger('Received payload from DC '.$datacenter, Logger::ULTRA_VERBOSE);
|
$API->logger->logger('Received payload from DC '.$datacenter, Logger::ULTRA_VERBOSE);
|
||||||
} finally {
|
} finally {
|
||||||
$connection->reading(false);
|
$connection->reading(false);
|
||||||
|
@ -23,6 +23,7 @@ use Amp\ByteStream\StreamException;
|
|||||||
use Amp\Loop;
|
use Amp\Loop;
|
||||||
use danog\Loop\ResumableSignalLoop;
|
use danog\Loop\ResumableSignalLoop;
|
||||||
use danog\MadelineProto\Logger;
|
use danog\MadelineProto\Logger;
|
||||||
|
use danog\MadelineProto\MTProto\Container;
|
||||||
use danog\MadelineProto\MTProtoTools\Crypt;
|
use danog\MadelineProto\MTProtoTools\Crypt;
|
||||||
use danog\MadelineProto\Tools;
|
use danog\MadelineProto\Tools;
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ class WriteLoop extends ResumableSignalLoop
|
|||||||
$datacenter = $this->datacenter;
|
$datacenter = $this->datacenter;
|
||||||
$please_wait = false;
|
$please_wait = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
while (empty($connection->pending_outgoing) || $please_wait) {
|
while (empty($connection->pendingOutgoing) || $please_wait) {
|
||||||
if ($connection->shouldReconnect()) {
|
if ($connection->shouldReconnect()) {
|
||||||
$API->logger->logger('Not writing because connection is old');
|
$API->logger->logger('Not writing because connection is old');
|
||||||
return;
|
return;
|
||||||
@ -93,35 +94,34 @@ class WriteLoop extends ResumableSignalLoop
|
|||||||
$datacenter = $this->datacenter;
|
$datacenter = $this->datacenter;
|
||||||
$connection = $this->connection;
|
$connection = $this->connection;
|
||||||
$shared = $this->datacenterConnection;
|
$shared = $this->datacenterConnection;
|
||||||
while ($connection->pending_outgoing) {
|
while ($connection->pendingOutgoing) {
|
||||||
$skipped_all = true;
|
$skipped_all = true;
|
||||||
foreach ($connection->pending_outgoing as $k => $message) {
|
foreach ($connection->pendingOutgoing as $k => $message) {
|
||||||
if ($shared->hasTempAuthKey()) {
|
if ($shared->hasTempAuthKey()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!$message['unencrypted']) {
|
if ($message->isEncrypted()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$skipped_all = false;
|
$skipped_all = false;
|
||||||
$API->logger->logger("Sending {$message['_']} as unencrypted message to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
$API->logger->logger("Sending $message as unencrypted message to DC $datacenter", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||||
$message_id = isset($message['msg_id']) ? $message['msg_id'] : $connection->msgIdHandler->generateMessageId();
|
$message_id = $message->getMsgId() ?? $connection->msgIdHandler->generateMessageId();
|
||||||
$length = \strlen($message['serialized_body']);
|
$length = \strlen($message->getSerializedBody());
|
||||||
$pad_length = -$length & 15;
|
$pad_length = -$length & 15;
|
||||||
$pad_length += 16 * \danog\MadelineProto\Tools::randomInt($modulus = 16);
|
$pad_length += 16 * \danog\MadelineProto\Tools::randomInt($modulus = 16);
|
||||||
$pad = \danog\MadelineProto\Tools::random($pad_length);
|
$pad = \danog\MadelineProto\Tools::random($pad_length);
|
||||||
$buffer = yield $connection->stream->getWriteBuffer(8 + 8 + 4 + $pad_length + $length);
|
$buffer = yield $connection->stream->getWriteBuffer(8 + 8 + 4 + $pad_length + $length);
|
||||||
yield $buffer->bufferWrite("\0\0\0\0\0\0\0\0".$message_id.\danog\MadelineProto\Tools::packUnsignedInt($length).$message['serialized_body'].$pad);
|
yield $buffer->bufferWrite("\0\0\0\0\0\0\0\0".$message_id.\danog\MadelineProto\Tools::packUnsignedInt($length).$message->getSerializedBody().$pad);
|
||||||
//var_dump("plain ".bin2hex($message_id));
|
//var_dump("plain ".bin2hex($message_id));
|
||||||
$connection->httpSent();
|
$connection->httpSent();
|
||||||
|
|
||||||
|
$API->logger->logger("Sent $message as unencrypted message to DC $datacenter!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||||
|
|
||||||
|
unset($connection->pendingOutgoing[$k]);
|
||||||
$connection->outgoing_messages[$message_id] = $message;
|
$connection->outgoing_messages[$message_id] = $message;
|
||||||
$connection->outgoing_messages[$message_id]['sent'] = \time();
|
$connection->new_outgoing[$message_id] = $message;
|
||||||
$connection->outgoing_messages[$message_id]['tries'] = 0;
|
|
||||||
$connection->outgoing_messages[$message_id]['unencrypted'] = true;
|
$message->sent();
|
||||||
$connection->new_outgoing[$message_id] = $message_id;
|
|
||||||
unset($connection->pending_outgoing[$k]);
|
|
||||||
$API->logger->logger("Sent {$message['_']} as unencrypted message to DC {$datacenter}!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
|
||||||
$message['send_promise']->resolve(isset($message['promise']) ? $message['promise'] : true);
|
|
||||||
unset($message['send_promise']);
|
|
||||||
}
|
}
|
||||||
if ($skipped_all) {
|
if ($skipped_all) {
|
||||||
return true;
|
return true;
|
||||||
@ -138,11 +138,11 @@ class WriteLoop extends ResumableSignalLoop
|
|||||||
if (!$shared->hasTempAuthKey()) {
|
if (!$shared->hasTempAuthKey()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($shared->isHttp() && empty($connection->pending_outgoing)) {
|
if ($shared->isHttp() && empty($connection->pendingOutgoing)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
\ksort($connection->pending_outgoing);
|
\ksort($connection->pendingOutgoing);
|
||||||
|
|
||||||
$messages = [];
|
$messages = [];
|
||||||
$keys = [];
|
$keys = [];
|
||||||
@ -157,71 +157,72 @@ class WriteLoop extends ResumableSignalLoop
|
|||||||
$has_state = false;
|
$has_state = false;
|
||||||
$has_resend = false;
|
$has_resend = false;
|
||||||
$has_http_wait = false;
|
$has_http_wait = false;
|
||||||
foreach ($connection->pending_outgoing as $k => $message) {
|
foreach ($connection->pendingOutgoing as $k => $message) {
|
||||||
if ($message['unencrypted']) {
|
if ($message->isUnencrypted()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isset($message['container'])) {
|
if ($message instanceof Container) {
|
||||||
unset($connection->pending_outgoing[$k]);
|
unset($connection->pendingOutgoing[$k]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($shared->getGenericSettings()->getAuth()->getPfs() && !$shared->isBound() && !$connection->isCDN() && !\in_array($message['_'], ['http_wait', 'auth.bindTempAuthKey']) && $message['method']) {
|
$constructor = $message->getConstructor();
|
||||||
$API->logger->logger("Skipping {$message['_']} due to unbound keys in DC {$datacenter}");
|
if ($shared->getGenericSettings()->getAuth()->getPfs() && !$shared->isBound() && !$connection->isCDN() && $message->isMethod() && !\in_array($constructor, ['http_wait', 'auth.bindTempAuthKey'])) {
|
||||||
|
$API->logger->logger("Skipping $message due to unbound keys in DC $datacenter");
|
||||||
$skipped = true;
|
$skipped = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($message['_'] === 'http_wait') {
|
if ($constructor === 'http_wait') {
|
||||||
$has_http_wait = true;
|
$has_http_wait = true;
|
||||||
}
|
}
|
||||||
if ($message['_'] === 'msgs_state_req') {
|
if ($constructor === 'msgs_state_req') {
|
||||||
if ($has_state) {
|
if ($has_state) {
|
||||||
$API->logger->logger("Already have a state request queued for the current container in DC {$datacenter}");
|
$API->logger->logger("Already have a state request queued for the current container in DC {$datacenter}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$has_state = true;
|
$has_state = true;
|
||||||
}
|
}
|
||||||
if ($message['_'] === 'msg_resend_req') {
|
if ($constructor === 'msg_resend_req') {
|
||||||
if ($has_resend) {
|
if ($has_resend) {
|
||||||
$API->logger->logger("Already have a resend request queued for the current container in DC {$datacenter}");
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$has_resend = true;
|
$has_resend = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$body_length = \strlen($message['serialized_body']);
|
$body_length = \strlen($message->getSerializedBody());
|
||||||
$actual_length = $body_length + 32;
|
$actual_length = $body_length + 32;
|
||||||
if ($total_length && $total_length + $actual_length > 32760 || $count >= 1020) {
|
if ($total_length && $total_length + $actual_length > 32760 || $count >= 1020) {
|
||||||
$API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
$API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (isset($message['seqno'])) {
|
if ($message->hasSeqNo()) {
|
||||||
$has_seq = true;
|
$has_seq = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$message_id = $message['msg_id'] ?? $connection->msgIdHandler->generateMessageId();
|
$message_id = $message->getMsgId() ?? $connection->msgIdHandler->generateMessageId();
|
||||||
$API->logger->logger("Sending {$message['_']} as encrypted message to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
$API->logger->logger("Sending $message as encrypted message to DC $datacenter", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||||
$MTmessage = [
|
$MTmessage = [
|
||||||
'_' => 'MTmessage',
|
'_' => 'MTmessage',
|
||||||
'msg_id' => $message_id,
|
'msg_id' => $message_id,
|
||||||
'body' => $message['serialized_body'],
|
'body' => $message->getSerializedBody(),
|
||||||
'seqno' => $message['seqno'] ?? $connection->generateOutSeqNo($message['contentRelated'])
|
'seqno' => $message->getSeqNo() ?? $connection->generateOutSeqNo($message->isContentRelated())
|
||||||
];
|
];
|
||||||
if (isset($message['method']) && $message['method'] && $message['_'] !== 'http_wait') {
|
if ($message->isMethod() && $constructor !== 'http_wait') {
|
||||||
if (!$shared->getTempAuthKey()->isInited() && $message['_'] !== 'auth.bindTempAuthKey' && !$inited) {
|
if (!$shared->getTempAuthKey()->isInited() && $constructor !== 'auth.bindTempAuthKey' && !$inited) {
|
||||||
$inited = true;
|
$inited = true;
|
||||||
$API->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['write_client_info'], $message['_']), \danog\MadelineProto\Logger::NOTICE);
|
$API->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['write_client_info'], $constructor), \danog\MadelineProto\Logger::NOTICE);
|
||||||
$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']])]));
|
$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 {
|
} else {
|
||||||
if (isset($message['queue'])) {
|
if ($message->hasQueue()) {
|
||||||
if (!isset($connection->call_queue[$message['queue']])) {
|
$queueId = $message->getQueueId();
|
||||||
$connection->call_queue[$message['queue']] = [];
|
if (!isset($connection->call_queue[$queueId])) {
|
||||||
|
$connection->call_queue[$queueId] = [];
|
||||||
}
|
}
|
||||||
$MTmessage['body'] = (yield from $API->getTL()->serializeMethod('invokeAfterMsgs', ['msg_ids' => $connection->call_queue[$message['queue']], 'query' => $MTmessage['body']]));
|
$MTmessage['body'] = (yield from $API->getTL()->serializeMethod('invokeAfterMsgs', ['msg_ids' => $connection->call_queue[$queueId], 'query' => $MTmessage['body']]));
|
||||||
$connection->call_queue[$message['queue']][$message_id] = $message_id;
|
$connection->call_queue[$queueId][$message_id] = $message_id;
|
||||||
if (\count($connection->call_queue[$message['queue']]) > $API->settings->getRpc()->getLimitCallQueue()) {
|
if (\count($connection->call_queue[$queueId]) > $API->settings->getRpc()->getLimitCallQueue()) {
|
||||||
\reset($connection->call_queue[$message['queue']]);
|
\reset($connection->call_queue[$queueId]);
|
||||||
$key = \key($connection->call_queue[$message['queue']]);
|
$key = \key($connection->call_queue[$queueId]);
|
||||||
unset($connection->call_queue[$message['queue']][$key]);
|
unset($connection->call_queue[$queueId][$key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO
|
// TODO
|
||||||
@ -229,7 +230,7 @@ class WriteLoop extends ResumableSignalLoop
|
|||||||
if ($API->settings['requests']['gzip_encode_if_gt'] !== -1 && ($l = strlen($MTmessage['body'])) > $API->settings['requests']['gzip_encode_if_gt']) {
|
if ($API->settings['requests']['gzip_encode_if_gt'] !== -1 && ($l = strlen($MTmessage['body'])) > $API->settings['requests']['gzip_encode_if_gt']) {
|
||||||
if (($g = strlen($gzipped = gzencode($MTmessage['body']))) < $l) {
|
if (($g = strlen($gzipped = gzencode($MTmessage['body']))) < $l) {
|
||||||
$MTmessage['body'] = yield $API->getTL()->serializeObject(['type' => ''], ['_' => 'gzip_packed', 'packed_data' => $gzipped], 'gzipped data');
|
$MTmessage['body'] = yield $API->getTL()->serializeObject(['type' => ''], ['_' => 'gzip_packed', 'packed_data' => $gzipped], 'gzipped data');
|
||||||
$API->logger->logger('Using GZIP compression for ' . $message['_'] . ', saved ' . ($l - $g) . ' bytes of data, reduced call size by ' . $g * 100 / $l . '%', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
$API->logger->logger('Using GZIP compression for ' . $constructor . ', saved ' . ($l - $g) . ' bytes of data, reduced call size by ' . $g * 100 / $l . '%', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||||
}
|
}
|
||||||
unset($gzipped);
|
unset($gzipped);
|
||||||
}*/
|
}*/
|
||||||
@ -247,8 +248,8 @@ class WriteLoop extends ResumableSignalLoop
|
|||||||
$messages[] = $MTmessage;
|
$messages[] = $MTmessage;
|
||||||
$keys[$k] = $message_id;
|
$keys[$k] = $message_id;
|
||||||
|
|
||||||
$connection->pending_outgoing[$k]['seqno'] = $MTmessage['seqno'];
|
$message->setSeqNo($MTmessage['seqno'])
|
||||||
$connection->pending_outgoing[$k]['msg_id'] = $MTmessage['msg_id'];
|
->setMsgId($MTmessage['msg_id']);
|
||||||
}
|
}
|
||||||
$MTmessage = null;
|
$MTmessage = null;
|
||||||
|
|
||||||
@ -284,9 +285,9 @@ class WriteLoop extends ResumableSignalLoop
|
|||||||
if ($count > 1 || $has_seq) {
|
if ($count > 1 || $has_seq) {
|
||||||
$API->logger->logger("Wrapping in msg_container ({$count} messages of total size {$total_length}) as encrypted message for DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
$API->logger->logger("Wrapping in msg_container ({$count} messages of total size {$total_length}) as encrypted message for DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||||
$message_id = $connection->msgIdHandler->generateMessageId();
|
$message_id = $connection->msgIdHandler->generateMessageId();
|
||||||
$connection->pending_outgoing[$connection->pending_outgoing_key] = ['_' => 'msg_container', 'container' => \array_values($keys), 'contentRelated' => false, 'method' => false, 'unencrypted' => false];
|
$connection->pendingOutgoing[$connection->pendingOutgoingKey] = new Container(\array_values($keys));
|
||||||
$keys[$connection->pending_outgoing_key++] = $message_id;
|
$keys[$connection->pendingOutgoingKey++] = $message_id;
|
||||||
$message_data = (yield from $API->getTL()->serializeObject(['type' => ''], ['_' => 'msg_container', 'messages' => $messages], 'container'));
|
$message_data = yield from $API->getTL()->serializeObject(['type' => ''], ['_' => 'msg_container', 'messages' => $messages], 'container');
|
||||||
$message_data_length = \strlen($message_data);
|
$message_data_length = \strlen($message_data);
|
||||||
$seq_no = $connection->generateOutSeqNo(false);
|
$seq_no = $connection->generateOutSeqNo(false);
|
||||||
} elseif ($count) {
|
} elseif ($count) {
|
||||||
@ -320,20 +321,17 @@ class WriteLoop extends ResumableSignalLoop
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($keys as $key => $message_id) {
|
foreach ($keys as $key => $message_id) {
|
||||||
$connection->outgoing_messages[$message_id] =& $connection->pending_outgoing[$key];
|
$message = $connection->pendingOutgoing[$key];
|
||||||
if (isset($connection->outgoing_messages[$message_id]['promise'])) {
|
unset($connection->pendingOutgoing[$key]);
|
||||||
$connection->new_outgoing[$message_id] = $message_id;
|
$connection->outgoing_messages[$message_id] = $message;
|
||||||
$connection->outgoing_messages[$message_id]['sent'] = $sent;
|
if ($message->hasPromise()) {
|
||||||
|
$connection->new_outgoing[$message_id] = $message;
|
||||||
}
|
}
|
||||||
if (isset($connection->outgoing_messages[$message_id]['send_promise'])) {
|
$message->sent();
|
||||||
$connection->outgoing_messages[$message_id]['send_promise']->resolve(isset($connection->outgoing_messages[$message_id]['promise']) ? $connection->outgoing_messages[$message_id]['promise'] : true);
|
|
||||||
unset($connection->outgoing_messages[$message_id]['send_promise']);
|
|
||||||
}
|
|
||||||
unset($connection->pending_outgoing[$key]);
|
|
||||||
}
|
}
|
||||||
} while ($connection->pending_outgoing && !$skipped);
|
} while ($connection->pendingOutgoing && !$skipped);
|
||||||
if (empty($connection->pending_outgoing)) {
|
if (empty($connection->pendingOutgoing)) {
|
||||||
$connection->pending_outgoing_key = 'a';
|
$connection->pendingOutgoingKey = 'a';
|
||||||
}
|
}
|
||||||
return $skipped;
|
return $skipped;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
|||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
const V = 147;
|
const V = 148;
|
||||||
/**
|
/**
|
||||||
* Release version.
|
* Release version.
|
||||||
*
|
*
|
||||||
@ -301,7 +301,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
|||||||
*
|
*
|
||||||
* @var DbArray|Promise[]
|
* @var DbArray|Promise[]
|
||||||
*/
|
*/
|
||||||
public $channel_participants;
|
public $channelParticipants;
|
||||||
/**
|
/**
|
||||||
* When we last stored data in remote peer database (now doesn't exist anymore).
|
* When we last stored data in remote peer database (now doesn't exist anymore).
|
||||||
*
|
*
|
||||||
@ -491,7 +491,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
|||||||
protected static array $dbProperties = [
|
protected static array $dbProperties = [
|
||||||
'chats' => 'array',
|
'chats' => 'array',
|
||||||
'full_chats' => 'array',
|
'full_chats' => 'array',
|
||||||
'channel_participants' => 'array',
|
'channelParticipants' => 'array',
|
||||||
'usernames' => 'array',
|
'usernames' => 'array',
|
||||||
'session' => [
|
'session' => [
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
@ -574,7 +574,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
|||||||
$this->datacenter->curdc = 2;
|
$this->datacenter->curdc = 2;
|
||||||
if ((!isset($this->authorization['user']['bot']) || !$this->authorization['user']['bot']) && $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->hasTempAuthKey()) {
|
if ((!isset($this->authorization['user']['bot']) || !$this->authorization['user']['bot']) && $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->hasTempAuthKey()) {
|
||||||
try {
|
try {
|
||||||
$nearest_dc = yield from $this->methodCallAsyncRead('help.getNearestDc', [], ['datacenter' => $this->datacenter->curdc]);
|
$nearest_dc = yield from $this->methodCallAsyncRead('help.getNearestDc', []);
|
||||||
$this->logger->logger(\sprintf(Lang::$current_lang['nearest_dc'], $nearest_dc['country'], $nearest_dc['nearest_dc']), Logger::NOTICE);
|
$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']) {
|
if ($nearest_dc['nearest_dc'] != $nearest_dc['this_dc']) {
|
||||||
$this->settings->setDefaultDc($this->datacenter->curdc = (int) $nearest_dc['nearest_dc']);
|
$this->settings->setDefaultDc($this->datacenter->curdc = (int) $nearest_dc['nearest_dc']);
|
||||||
@ -585,7 +585,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield from $this->getConfig([], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->getConfig([]);
|
||||||
$this->startUpdateSystem(true);
|
$this->startUpdateSystem(true);
|
||||||
$this->v = self::V;
|
$this->v = self::V;
|
||||||
|
|
||||||
@ -627,7 +627,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
|||||||
'full_chats',
|
'full_chats',
|
||||||
'referenceDatabase',
|
'referenceDatabase',
|
||||||
'minDatabase',
|
'minDatabase',
|
||||||
'channel_participants',
|
'channelParticipants',
|
||||||
'usernames',
|
'usernames',
|
||||||
|
|
||||||
// Misc caching
|
// Misc caching
|
||||||
@ -1029,6 +1029,39 @@ class MTProto extends AsyncConstruct implements TLCallback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($this->channel_participants)) {
|
||||||
|
if (\is_array($this->channel_participants)) {
|
||||||
|
foreach ($this->channel_participants as $channelId => $filters) {
|
||||||
|
foreach ($filters as $filter => $qs) {
|
||||||
|
foreach ($qs as $q => $offsets) {
|
||||||
|
foreach ($offsets as $offset => $limits) {
|
||||||
|
foreach ($limits as $limit => $data) {
|
||||||
|
$this->channelParticipants[$this->participantsKey($channelId, $filter, $q, $offset, $limit)] = $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($this->channel_participants[$channelId]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self::$dbProperties['channel_participants'] = 'array';
|
||||||
|
yield from $this->initDb($this);
|
||||||
|
$iterator = $this->channel_participants->getIterator();
|
||||||
|
while (yield $iterator->advance()) {
|
||||||
|
[$channelId, $filters] = $iterator->getCurrent();
|
||||||
|
foreach ($filters as $filter => $qs) {
|
||||||
|
foreach ($qs as $q => $offsets) {
|
||||||
|
foreach ($offsets as $offset => $limits) {
|
||||||
|
foreach ($limits as $limit => $data) {
|
||||||
|
$this->channelParticipants[$this->participantsKey($channelId, $filter, $q, $offset, $limit)] = $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($this->channel_participants[$channelId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
foreach ($this->secret_chats as $key => &$chat) {
|
foreach ($this->secret_chats as $key => &$chat) {
|
||||||
if (!\is_array($chat)) {
|
if (!\is_array($chat)) {
|
||||||
unset($this->secret_chats[$key]);
|
unset($this->secret_chats[$key]);
|
||||||
@ -1157,6 +1190,9 @@ class MTProto extends AsyncConstruct implements TLCallback
|
|||||||
// onStart event handler
|
// onStart event handler
|
||||||
if ($this->event_handler && \class_exists($this->event_handler) && \is_subclass_of($this->event_handler, EventHandler::class)) {
|
if ($this->event_handler && \class_exists($this->event_handler) && \is_subclass_of($this->event_handler, EventHandler::class)) {
|
||||||
yield from $this->setEventHandler($this->event_handler);
|
yield from $this->setEventHandler($this->event_handler);
|
||||||
|
} else {
|
||||||
|
$this->event_handler = null;
|
||||||
|
$this->event_handler_instance = null;
|
||||||
}
|
}
|
||||||
$this->startUpdateSystem(true);
|
$this->startUpdateSystem(true);
|
||||||
if ($this->authorized === self::LOGGED_IN && !$this->authorization['user']['bot'] && $this->settings->getPeer()->getCacheAllPeersOnStartup()) {
|
if ($this->authorized === self::LOGGED_IN && !$this->authorization['user']['bot'] && $this->settings->getPeer()->getCacheAllPeersOnStartup()) {
|
||||||
@ -1550,13 +1586,22 @@ class MTProto extends AsyncConstruct implements TLCallback
|
|||||||
$this->updaters[$channelId]->resume();
|
$this->updaters[$channelId]->resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach ($this->datacenter->getDataCenterConnections() as $datacenter) {
|
$this->flushAll();
|
||||||
$datacenter->flush();
|
|
||||||
}
|
|
||||||
if ($this->seqUpdater->start()) {
|
if ($this->seqUpdater->start()) {
|
||||||
$this->seqUpdater->resume();
|
$this->seqUpdater->resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Flush all datacenter connections.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function flushAll(): void
|
||||||
|
{
|
||||||
|
foreach ($this->datacenter->getDataCenterConnections() as $datacenter) {
|
||||||
|
$datacenter->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Store shared phone config.
|
* Store shared phone config.
|
||||||
*
|
*
|
||||||
@ -1688,7 +1733,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
|||||||
public function fullGetSelf(): \Generator
|
public function fullGetSelf(): \Generator
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->authorization = ['user' => (yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [['_' => 'inputUserSelf']]], ['datacenter' => $this->datacenter->curdc]))[0]];
|
$this->authorization = ['user' => (yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [['_' => 'inputUserSelf']]]))[0]];
|
||||||
} catch (RPCErrorException $e) {
|
} catch (RPCErrorException $e) {
|
||||||
$this->logger->logger($e->getMessage());
|
$this->logger->logger($e->getMessage());
|
||||||
return false;
|
return false;
|
||||||
|
54
src/danog/MadelineProto/MTProto/Container.php
Normal file
54
src/danog/MadelineProto/MTProto/Container.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container message.
|
||||||
|
*
|
||||||
|
* 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\MTProto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outgoing container message.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class Container extends OutgoingMessage
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Message IDs.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private array $ids = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param array $ids
|
||||||
|
*/
|
||||||
|
public function __construct(array $ids)
|
||||||
|
{
|
||||||
|
$this->ids = $ids;
|
||||||
|
parent::__construct([], 'msg_container', '', false, false);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get message IDs.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getIds(): array
|
||||||
|
{
|
||||||
|
return $this->ids;
|
||||||
|
}
|
||||||
|
}
|
237
src/danog/MadelineProto/MTProto/IncomingMessage.php
Normal file
237
src/danog/MadelineProto/MTProto/IncomingMessage.php
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outgoing message.
|
||||||
|
*
|
||||||
|
* 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\MTProto;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Incoming message.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class IncomingMessage extends Message
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* We have received this message.
|
||||||
|
*/
|
||||||
|
const STATE_RECEIVED = 4;
|
||||||
|
/**
|
||||||
|
* We have acknowledged this message.
|
||||||
|
*/
|
||||||
|
const STATE_ACKED = 8;
|
||||||
|
/**
|
||||||
|
* We have read the contents of this message.
|
||||||
|
*/
|
||||||
|
const STATE_READ = 128;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response field map.
|
||||||
|
*/
|
||||||
|
private const RESPONSE_ID_MAP = [
|
||||||
|
'rpc_result' => 'req_msg_id',
|
||||||
|
'future_salts' => 'req_msg_id',
|
||||||
|
'msgs_state_info' => 'req_msg_id',
|
||||||
|
'bad_server_salt' => 'bad_msg_id',
|
||||||
|
'bad_message_notification' => 'bad_msg_id',
|
||||||
|
'pong' => 'msg_id',
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* State.
|
||||||
|
*/
|
||||||
|
private int $state = self::STATE_RECEIVED;
|
||||||
|
/**
|
||||||
|
* Receive date.
|
||||||
|
*/
|
||||||
|
private int $received;
|
||||||
|
/**
|
||||||
|
* Deserialized response content.
|
||||||
|
*/
|
||||||
|
private array $content;
|
||||||
|
/**
|
||||||
|
* Was present in container.
|
||||||
|
*/
|
||||||
|
private bool $fromContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DB side effects to be resolved before using the content.
|
||||||
|
*/
|
||||||
|
private ?Promise $sideEffects = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param array $content Content
|
||||||
|
* @param boolean $fromContainer Whether this message was in a container
|
||||||
|
*/
|
||||||
|
public function __construct(array $content, string $msgId, bool $fromContainer = false)
|
||||||
|
{
|
||||||
|
$this->content = $content;
|
||||||
|
$this->fromContainer = $fromContainer;
|
||||||
|
$this->msgId = $msgId;
|
||||||
|
|
||||||
|
$this->received = \time();
|
||||||
|
|
||||||
|
$this->contentRelated = !isset(Message::NOT_CONTENT_RELATED[$content['_']]);
|
||||||
|
if (!$this->contentRelated) {
|
||||||
|
$this->state |= 16; // message not requiring acknowledgment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get deserialized response content.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getContent(): array
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get was present in container.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFromContainer(): bool
|
||||||
|
{
|
||||||
|
return $this->fromContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get log line.
|
||||||
|
*
|
||||||
|
* @param int|string $dc DC ID
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function log($dc): string
|
||||||
|
{
|
||||||
|
if ($this->fromContainer) {
|
||||||
|
return "Inside of container, received {$this->content['_']} from DC $dc";
|
||||||
|
}
|
||||||
|
return "Received {$this->content['_']} from DC $dc";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get message type.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return $this->content['_'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get message type.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->content['_'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We have acked this message.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function ack(): void
|
||||||
|
{
|
||||||
|
$this->state |= self::STATE_ACKED;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Read this message, clearing its contents.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function read(): array
|
||||||
|
{
|
||||||
|
$this->state |= self::STATE_READ;
|
||||||
|
$content = $this->content;
|
||||||
|
$this->content = ['_' => $content['_']];
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this message can be garbage collected.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function canGarbageCollect(): bool
|
||||||
|
{
|
||||||
|
return (bool) ($this->state & self::STATE_READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ID of message to which this message replies.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getRequestId(): string
|
||||||
|
{
|
||||||
|
return $this->content[self::RESPONSE_ID_MAP[$this->content['_']]];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get state.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getState(): int
|
||||||
|
{
|
||||||
|
return $this->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set DB side effects to be resolved before using the content.
|
||||||
|
*
|
||||||
|
* @param ?Promise $sideEffects DB side effects to be resolved before using the content
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setSideEffects(?Promise $sideEffects): self
|
||||||
|
{
|
||||||
|
$this->sideEffects = $sideEffects;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* yield DB side effects to be resolved before using the content.
|
||||||
|
*
|
||||||
|
* @return \Generator
|
||||||
|
*/
|
||||||
|
public function yieldSideEffects(): \Generator
|
||||||
|
{
|
||||||
|
if ($this->sideEffects) {
|
||||||
|
yield $this->sideEffects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get receive date.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getReceived(): int
|
||||||
|
{
|
||||||
|
return $this->received;
|
||||||
|
}
|
||||||
|
}
|
160
src/danog/MadelineProto/MTProto/Message.php
Normal file
160
src/danog/MadelineProto/MTProto/Message.php
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message.
|
||||||
|
*
|
||||||
|
* 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\MTProto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MTProto message.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
abstract class Message
|
||||||
|
{
|
||||||
|
protected const NOT_CONTENT_RELATED = [
|
||||||
|
//'rpc_result' => true,
|
||||||
|
//'rpc_error' => true,
|
||||||
|
'rpc_drop_answer' => true,
|
||||||
|
'rpc_answer_unknown' => true,
|
||||||
|
'rpc_answer_dropped_running' => true,
|
||||||
|
'rpc_answer_dropped' => true,
|
||||||
|
'get_future_salts' => true,
|
||||||
|
'future_salt' => true,
|
||||||
|
'future_salts' => true,
|
||||||
|
'ping' => true,
|
||||||
|
'pong' => true,
|
||||||
|
'ping_delay_disconnect' => true,
|
||||||
|
'destroy_session' => true,
|
||||||
|
'destroy_session_ok' => true,
|
||||||
|
'destroy_session_none' => true,
|
||||||
|
//'new_session_created' => true,
|
||||||
|
'msg_container' => true,
|
||||||
|
'msg_copy' => true,
|
||||||
|
'gzip_packed' => true,
|
||||||
|
'http_wait' => true,
|
||||||
|
'msgs_ack' => true,
|
||||||
|
'bad_msg_notification' => true,
|
||||||
|
'bad_server_salt' => true,
|
||||||
|
'msgs_state_req' => true,
|
||||||
|
'msgs_state_info' => true,
|
||||||
|
'msgs_all_info' => true,
|
||||||
|
'msg_detailed_info' => true,
|
||||||
|
'msg_new_detailed_info' => true,
|
||||||
|
'msg_resend_req' => true,
|
||||||
|
'msg_resend_ans_req' => true,
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* My message ID.
|
||||||
|
*/
|
||||||
|
protected $msgId = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sequence number.
|
||||||
|
*/
|
||||||
|
protected ?int $seqNo = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether constructor is content related.
|
||||||
|
*/
|
||||||
|
protected bool $contentRelated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether constructor is content related.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isContentRelated(): bool
|
||||||
|
{
|
||||||
|
return $this->contentRelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get my message ID.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getMsgId()
|
||||||
|
{
|
||||||
|
return $this->msgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set my message ID.
|
||||||
|
*
|
||||||
|
* @param mixed $msgId My message ID
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setMsgId($msgId): self
|
||||||
|
{
|
||||||
|
$this->msgId = $msgId;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we have a message ID.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function hasMsgId(): bool
|
||||||
|
{
|
||||||
|
return $this->msgId !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sequence number.
|
||||||
|
*
|
||||||
|
* @return ?int
|
||||||
|
*/
|
||||||
|
public function getSeqNo(): ?int
|
||||||
|
{
|
||||||
|
return $this->seqNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has sequence number.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasSeqNo(): bool
|
||||||
|
{
|
||||||
|
return isset($this->seqNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set sequence number.
|
||||||
|
*
|
||||||
|
* @param ?int $seqNo Sequence number
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setSeqNo(?int $seqNo): self
|
||||||
|
{
|
||||||
|
$this->seqNo = $seqNo;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this message can be garbage collected.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
abstract public function canGarbageCollect(): bool;
|
||||||
|
}
|
617
src/danog/MadelineProto/MTProto/OutgoingMessage.php
Normal file
617
src/danog/MadelineProto/MTProto/OutgoingMessage.php
Normal file
@ -0,0 +1,617 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outgoing message.
|
||||||
|
*
|
||||||
|
* 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\MTProto;
|
||||||
|
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Promise;
|
||||||
|
use danog\MadelineProto\Exception;
|
||||||
|
use danog\MadelineProto\MTProtoSession\MsgIdHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outgoing message.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class OutgoingMessage extends Message
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The message was created.
|
||||||
|
*/
|
||||||
|
const STATE_PENDING = 0;
|
||||||
|
/**
|
||||||
|
* The message was sent.
|
||||||
|
*/
|
||||||
|
const STATE_SENT = 1;
|
||||||
|
/**
|
||||||
|
* The message was acked.
|
||||||
|
*/
|
||||||
|
const STATE_ACKED = 2;
|
||||||
|
/**
|
||||||
|
* We got a reply to the message.
|
||||||
|
*/
|
||||||
|
const STATE_REPLIED = self::STATE_ACKED | 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State of message.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @psalm-var self::STATE_*
|
||||||
|
*/
|
||||||
|
private int $state = self::STATE_PENDING;
|
||||||
|
/**
|
||||||
|
* Constructor name.
|
||||||
|
*/
|
||||||
|
private string $constructor;
|
||||||
|
/**
|
||||||
|
* Constructor type.
|
||||||
|
*/
|
||||||
|
private string $type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this is a method.
|
||||||
|
*/
|
||||||
|
private bool $method;
|
||||||
|
/**
|
||||||
|
* Resolution deferred.
|
||||||
|
*/
|
||||||
|
private ?Deferred $promise = null;
|
||||||
|
/**
|
||||||
|
* Send deferred.
|
||||||
|
*/
|
||||||
|
private ?Deferred $sendPromise = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this is an unencrypted message.
|
||||||
|
*/
|
||||||
|
private bool $unencrypted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message body.
|
||||||
|
*
|
||||||
|
* @var \Generator|array|null
|
||||||
|
*/
|
||||||
|
private $body;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialized body.
|
||||||
|
*/
|
||||||
|
private ?string $serializedBody = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this message is related to a user, as in getting a successful reply means we have auth.
|
||||||
|
*/
|
||||||
|
private bool $userRelated = false;
|
||||||
|
/**
|
||||||
|
* Whether this message is related to a file upload, as in getting a redirect should redirect to a media server.
|
||||||
|
*/
|
||||||
|
private bool $fileRelated = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom flood wait limit for this bot
|
||||||
|
*/
|
||||||
|
private ?int $floodWaitLimit = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether we should try converting the result to a bot API object.
|
||||||
|
*/
|
||||||
|
private bool $botAPI = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether we should refresh references upon serialization of this message.
|
||||||
|
*/
|
||||||
|
private bool $refreshReferences = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue ID.
|
||||||
|
*/
|
||||||
|
private ?string $queueId = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When was this message sent.
|
||||||
|
*/
|
||||||
|
private int $sent = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of times this message was sent.
|
||||||
|
*/
|
||||||
|
private int $tries = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create outgoing message.
|
||||||
|
*
|
||||||
|
* @param \Generator|array $body Body
|
||||||
|
* @param string $constructor Constructor name
|
||||||
|
* @param string $type Constructor type
|
||||||
|
* @param boolean $method Is this a method?
|
||||||
|
* @param boolean $unencrypted Is this an unencrypted message?
|
||||||
|
*/
|
||||||
|
public function __construct($body, string $constructor, string $type, bool $method, bool $unencrypted)
|
||||||
|
{
|
||||||
|
$this->body = $body;
|
||||||
|
$this->constructor = $constructor;
|
||||||
|
$this->type = $type;
|
||||||
|
$this->method = $method;
|
||||||
|
$this->unencrypted = $unencrypted;
|
||||||
|
if ($method) {
|
||||||
|
$this->promise = new Deferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->contentRelated = !isset(Message::NOT_CONTENT_RELATED[$constructor]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that we're trying to send the message.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function trySend(): void
|
||||||
|
{
|
||||||
|
if (!isset($this->sendPromise)) {
|
||||||
|
$this->sendPromise = new Deferred;
|
||||||
|
}
|
||||||
|
$this->tries++;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Signal that the message was sent.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function sent(): void
|
||||||
|
{
|
||||||
|
if ($this->state & self::STATE_REPLIED) {
|
||||||
|
throw new Exception("Trying to resend already replied message $this!");
|
||||||
|
}
|
||||||
|
$this->state |= self::STATE_SENT;
|
||||||
|
$this->sent = \time();
|
||||||
|
if (isset($this->sendPromise)) {
|
||||||
|
$sendPromise = $this->sendPromise;
|
||||||
|
$this->sendPromise = null;
|
||||||
|
$sendPromise->resolve($this->promise ?? true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set reply to message.
|
||||||
|
*
|
||||||
|
* @param mixed $result
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function reply($result): void
|
||||||
|
{
|
||||||
|
if ($this->state & self::STATE_REPLIED) {
|
||||||
|
throw new Exception("Trying to double reply to message $this!");
|
||||||
|
}
|
||||||
|
$this->serializedBody = null;
|
||||||
|
$this->body = null;
|
||||||
|
|
||||||
|
$this->state |= self::STATE_REPLIED;
|
||||||
|
if ($this->promise) { // Sometimes can get an RPC error for constructors
|
||||||
|
$promise = $this->promise;
|
||||||
|
$this->promise = null;
|
||||||
|
$promise->resolve($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ACK message.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function ack(): void
|
||||||
|
{
|
||||||
|
$this->state |= self::STATE_ACKED;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get state of message.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
* @psalm-return self::STATE_*
|
||||||
|
*/
|
||||||
|
public function getState(): int
|
||||||
|
{
|
||||||
|
return $this->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get message body.
|
||||||
|
*
|
||||||
|
* @return \Generator
|
||||||
|
*/
|
||||||
|
public function getBody(): \Generator
|
||||||
|
{
|
||||||
|
return $this->body instanceof \Generator ? yield from $this->body : $this->body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get message body or empty array.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getBodyOrEmpty(): array
|
||||||
|
{
|
||||||
|
return \is_array($this->body) ? $this->body : [];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check if we have a body.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function hasBody(): bool
|
||||||
|
{
|
||||||
|
return $this->body !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get serialized body.
|
||||||
|
*
|
||||||
|
* @return ?string
|
||||||
|
*/
|
||||||
|
public function getSerializedBody(): ?string
|
||||||
|
{
|
||||||
|
return $this->serializedBody;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check if we have a serialized body.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function hasSerializedBody(): bool
|
||||||
|
{
|
||||||
|
return $this->serializedBody !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get number of times this message was sent.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getTries(): int
|
||||||
|
{
|
||||||
|
return $this->tries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get constructor name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getConstructor(): string
|
||||||
|
{
|
||||||
|
return $this->constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get constructor type.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether this is a method.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isMethod(): bool
|
||||||
|
{
|
||||||
|
return $this->method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether this is an unencrypted message.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isUnencrypted(): bool
|
||||||
|
{
|
||||||
|
return $this->unencrypted;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get whether this is an encrypted message.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isEncrypted(): bool
|
||||||
|
{
|
||||||
|
return !$this->unencrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether this message is related to a user, as in getting a successful reply means we have auth.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isUserRelated(): bool
|
||||||
|
{
|
||||||
|
return $this->userRelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether we should refresh references upon serialization of this message.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function shouldRefreshReferences(): bool
|
||||||
|
{
|
||||||
|
return $this->refreshReferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get queue ID.
|
||||||
|
*
|
||||||
|
* @return ?string
|
||||||
|
*/
|
||||||
|
public function getQueueId(): ?string
|
||||||
|
{
|
||||||
|
return $this->queueId;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get whether we have a queue ID.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasQueue(): bool
|
||||||
|
{
|
||||||
|
return $this->queueId !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set serialized body.
|
||||||
|
*
|
||||||
|
* @param string $serializedBody Serialized body.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setSerializedBody(string $serializedBody): self
|
||||||
|
{
|
||||||
|
$this->serializedBody = $serializedBody;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether this message is related to a user, as in getting a successful reply means we have auth.
|
||||||
|
*
|
||||||
|
* @param bool $userRelated Whether this message is related to a user, as in getting a successful reply means we have auth.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setUserRelated(bool $userRelated): self
|
||||||
|
{
|
||||||
|
$this->userRelated = $userRelated;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether we should refresh references upon serialization of this message.
|
||||||
|
*
|
||||||
|
* @param bool $refreshReferences Whether we should refresh references upon serialization of this message.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setRefreshReferences(bool $refreshReferences): self
|
||||||
|
{
|
||||||
|
$this->refreshReferences = $refreshReferences;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set queue ID.
|
||||||
|
*
|
||||||
|
* @param ?string $queueId Queue ID.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setQueueId(?string $queueId): self
|
||||||
|
{
|
||||||
|
$this->queueId = $queueId;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get when was this message sent.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getSent(): int
|
||||||
|
{
|
||||||
|
return $this->sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the message was sent.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function wasSent(): bool
|
||||||
|
{
|
||||||
|
return (bool) ($this->state & self::STATE_SENT);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check if can garbage collect this message.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function canGarbageCollect(): bool
|
||||||
|
{
|
||||||
|
if ($this->state & self::STATE_REPLIED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!$this->hasPromise()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* For logging.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
if ($this->msgId) {
|
||||||
|
$msgId = MsgIdHandler::toString($this->msgId);
|
||||||
|
return "{$this->constructor} with message ID $msgId";
|
||||||
|
}
|
||||||
|
return $this->constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set resolution deferred.
|
||||||
|
*
|
||||||
|
* @param Deferred $promise Resolution deferred.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setPromise(Deferred $promise): self
|
||||||
|
{
|
||||||
|
$this->promise = $promise;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for message to be sent.
|
||||||
|
*
|
||||||
|
* @return Promise
|
||||||
|
*/
|
||||||
|
public function getSendPromise(): Promise
|
||||||
|
{
|
||||||
|
if (!$this->sendPromise) {
|
||||||
|
throw new Exception("Message was already sent, can't get send promise!");
|
||||||
|
}
|
||||||
|
return $this->sendPromise->promise();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we have a promise.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasPromise(): bool
|
||||||
|
{
|
||||||
|
return $this->promise !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset sent time to trigger resending.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function resetSent(): self
|
||||||
|
{
|
||||||
|
$this->sent = 0;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether we should try converting the result to a bot API object.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getBotAPI(): bool
|
||||||
|
{
|
||||||
|
return $this->botAPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether we should try converting the result to a bot API object.
|
||||||
|
*
|
||||||
|
* @param bool $botAPI Whether we should try converting the result to a bot API object
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setBotAPI(bool $botAPI): self
|
||||||
|
{
|
||||||
|
$this->botAPI = $botAPI;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether this message is related to a file upload, as in getting a redirect should redirect to a media server.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFileRelated(): bool
|
||||||
|
{
|
||||||
|
return $this->fileRelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether this message is related to a file upload, as in getting a redirect should redirect to a media server.
|
||||||
|
*
|
||||||
|
* @param bool $fileRelated Whether this message is related to a file upload, as in getting a redirect should redirect to a media server.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setFileRelated(bool $fileRelated): self
|
||||||
|
{
|
||||||
|
$this->fileRelated = $fileRelated;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom flood wait limit for this bot
|
||||||
|
*
|
||||||
|
* @return ?int
|
||||||
|
*/
|
||||||
|
public function getFloodWaitLimit(): ?int
|
||||||
|
{
|
||||||
|
return $this->floodWaitLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom flood wait limit for this bot
|
||||||
|
*
|
||||||
|
* @param ?int $floodWaitLimit Custom flood wait limit for this bot
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setFloodWaitLimit(?int $floodWaitLimit): self
|
||||||
|
{
|
||||||
|
$this->floodWaitLimit = $floodWaitLimit;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set when was this message sent.
|
||||||
|
*
|
||||||
|
* @param int $sent When was this message sent.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setSent(int $sent): self
|
||||||
|
{
|
||||||
|
$this->sent = $sent;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,8 @@
|
|||||||
namespace danog\MadelineProto\MTProtoSession;
|
namespace danog\MadelineProto\MTProtoSession;
|
||||||
|
|
||||||
use danog\MadelineProto\DataCenterConnection;
|
use danog\MadelineProto\DataCenterConnection;
|
||||||
|
use danog\MadelineProto\MTProto\IncomingMessage;
|
||||||
|
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages acknowledgement of messages.
|
* Manages acknowledgement of messages.
|
||||||
@ -45,45 +47,33 @@ trait AckHandler
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* We have gotten response for outgoing message ID.
|
* We have gotten a response for an outgoing message.
|
||||||
*
|
*
|
||||||
* @param string|int $message_id Message ID
|
* @param OutgoingMessage $message Message
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function gotResponseForOutgoingMessageId($message_id): bool
|
public function gotResponseForOutgoingMessage(OutgoingMessage $outgoingMessage): void
|
||||||
{
|
{
|
||||||
// The server acknowledges that it received my message
|
// The server acknowledges that it received my message
|
||||||
if (isset($this->new_outgoing[$message_id])) {
|
if (isset($this->new_outgoing[$outgoingMessage->getMsgId()])) {
|
||||||
unset($this->new_outgoing[$message_id]);
|
unset($this->new_outgoing[$outgoingMessage->getMsgId()]);
|
||||||
}
|
}
|
||||||
if (!isset($this->outgoing_messages[$message_id])) {
|
|
||||||
$this->logger->logger("WARNING: Couldn't find message id ".$message_id.' in the array of outgoing messages. Maybe try to increase its size?', \danog\MadelineProto\Logger::WARNING);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isset($this->outgoing_messages[$message_id]['body'])) {
|
|
||||||
unset($this->outgoing_messages[$message_id]['body']);
|
|
||||||
}
|
|
||||||
if (isset($this->outgoing_messages[$message_id]['serialized_body'])) {
|
|
||||||
unset($this->outgoing_messages[$message_id]['serialized_body']);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Acknowledge incoming message ID.
|
* Acknowledge incoming message ID.
|
||||||
*
|
*
|
||||||
* @param string|int $message_id Message ID
|
* @param IncomingMessage $message Message
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function ackIncomingMessageId($message_id): bool
|
public function ackIncomingMessage(IncomingMessage $message): void
|
||||||
{
|
{
|
||||||
|
// Not exactly true, but we don't care
|
||||||
|
$message->ack();
|
||||||
|
$message_id = $message->getMsgId();
|
||||||
// I let the server know that I received its message
|
// I let the server know that I received its message
|
||||||
if (!isset($this->incoming_messages[$message_id])) {
|
|
||||||
$this->logger->logger("WARNING: Couldn't find message id ".$message_id.' in the array of incoming messages. Maybe try to increase its size?', \danog\MadelineProto\Logger::WARNING);
|
|
||||||
}
|
|
||||||
$this->ack_queue[$message_id] = $message_id;
|
$this->ack_queue[$message_id] = $message_id;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,9 +88,13 @@ trait AckHandler
|
|||||||
$unencrypted = !$this->shared->hasTempAuthKey();
|
$unencrypted = !$this->shared->hasTempAuthKey();
|
||||||
$notBound = !$this->shared->isBound();
|
$notBound = !$this->shared->isBound();
|
||||||
$pfsNotBound = $pfs && $notBound;
|
$pfsNotBound = $pfs && $notBound;
|
||||||
foreach ($this->new_outgoing as $message_id) {
|
/** @var OutgoingMessage */
|
||||||
if (isset($this->outgoing_messages[$message_id]['sent']) && $this->outgoing_messages[$message_id]['sent'] + $timeout < \time() && $unencrypted === $this->outgoing_messages[$message_id]['unencrypted'] && $this->outgoing_messages[$message_id]['_'] !== 'msgs_state_req') {
|
foreach ($this->new_outgoing as $message) {
|
||||||
if ($pfsNotBound && $this->outgoing_messages[$message_id]['_'] !== 'auth.bindTempAuthKey') {
|
if ($message->wasSent()
|
||||||
|
&& $message->getSent() + $timeout < \time()
|
||||||
|
&& $message->isUnencrypted() === $unencrypted
|
||||||
|
&& $message->getConstructor() !== 'msgs_state_req') {
|
||||||
|
if ($pfsNotBound && $message->getConstructor() !== 'auth.bindTempAuthKey') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -124,18 +118,25 @@ trait AckHandler
|
|||||||
$notBound = !$this->shared->isBound();
|
$notBound = !$this->shared->isBound();
|
||||||
$pfsNotBound = $pfs && $notBound;
|
$pfsNotBound = $pfs && $notBound;
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($this->new_outgoing as $k => $message_id) {
|
/** @var OutgoingMessage $message */
|
||||||
if (isset($this->outgoing_messages[$message_id]['sent']) && $this->outgoing_messages[$message_id]['sent'] + $timeout < \time() && $unencrypted === $this->outgoing_messages[$message_id]['unencrypted']) {
|
foreach ($this->new_outgoing as $message_id => $message) {
|
||||||
if ($pfsNotBound && $this->outgoing_messages[$message_id]['_'] !== 'auth.bindTempAuthKey') {
|
if ($message->wasSent()
|
||||||
|
&& $message->getSent() + $timeout < \time()
|
||||||
|
&& $message->isUnencrypted() === $unencrypted
|
||||||
|
) {
|
||||||
|
if ($pfsNotBound && $message->getConstructor() !== 'auth.bindTempAuthKey') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($this->outgoing_messages[$message_id]['_'] === 'msgs_state_req') {
|
if ($message->getConstructor() === 'msgs_state_req') {
|
||||||
unset($this->new_outgoing[$k], $this->outgoing_messages[$message_id]);
|
unset($this->new_outgoing[$message_id], $this->outgoing_messages[$message_id]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($this->outgoing_messages[$message_id]['sent'] + $dropTimeout < \time()) {
|
if ($message->getSent() + $dropTimeout < \time()) {
|
||||||
$this->gotResponseForOutgoingMessageId($message_id);
|
$this->handleReject($message, new \danog\MadelineProto\Exception("Request timeout"));
|
||||||
$this->handleReject($this->outgoing_messages[$message_id], new \danog\MadelineProto\Exception("Request timeout"));
|
continue;
|
||||||
|
}
|
||||||
|
if ($message->getState() & OutgoingMessage::STATE_REPLIED) {
|
||||||
|
$this->logger->logger("Already replied to message $message, but still in new_outgoing");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$result[] = $message_id;
|
$result[] = $message_id;
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
namespace danog\MadelineProto\MTProtoSession;
|
namespace danog\MadelineProto\MTProtoSession;
|
||||||
|
|
||||||
use Amp\Deferred;
|
use Amp\Deferred;
|
||||||
|
use danog\MadelineProto\MTProto\Container;
|
||||||
|
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||||
use danog\MadelineProto\TL\Exception;
|
use danog\MadelineProto\TL\Exception;
|
||||||
use danog\MadelineProto\Tools;
|
use danog\MadelineProto\Tools;
|
||||||
|
|
||||||
@ -44,25 +46,31 @@ trait CallHandler
|
|||||||
if ($datacenter === $this->datacenter) {
|
if ($datacenter === $this->datacenter) {
|
||||||
$datacenter = false;
|
$datacenter = false;
|
||||||
}
|
}
|
||||||
$message_ids = $this->outgoing_messages[$message_id]['container'] ?? [$message_id];
|
$message_ids = $this->outgoing_messages[$message_id] instanceof Container
|
||||||
|
? $this->outgoing_messages[$message_id]->getIds()
|
||||||
|
: [$message_id];
|
||||||
foreach ($message_ids as $message_id) {
|
foreach ($message_ids as $message_id) {
|
||||||
if (isset($this->outgoing_messages[$message_id]['body'])) {
|
if (isset($this->outgoing_messages[$message_id])
|
||||||
|
&& $this->outgoing_messages[$message_id]->canGarbageCollect()) {
|
||||||
if ($datacenter) {
|
if ($datacenter) {
|
||||||
unset($this->outgoing_messages[$message_id]['msg_id'], $this->outgoing_messages[$message_id]['seqno']);
|
/** @var OutgoingMessage */
|
||||||
Tools::call($this->API->datacenter->waitGetConnection($datacenter))->onResolve(function ($e, $r) use ($message_id) {
|
$message = $this->outgoing_messages[$message_id];
|
||||||
Tools::callFork($r->sendMessage($this->outgoing_messages[$message_id], false));
|
$message->setMsgId(null);
|
||||||
|
$message->setSeqNo(null);
|
||||||
|
Tools::call($this->API->datacenter->waitGetConnection($datacenter))->onResolve(function ($e, $r) use ($message) {
|
||||||
|
Tools::callFork($r->sendMessage($message, false));
|
||||||
});
|
});
|
||||||
$this->ackOutgoingMessageId($message_id);
|
$this->gotResponseForOutgoingMessage($message);
|
||||||
$this->gotResponseForOutgoingMessageId($message_id);
|
|
||||||
} else {
|
} else {
|
||||||
Tools::callFork($this->sendMessage($this->outgoing_messages[$message_id], false));
|
/** @var OutgoingMessage */
|
||||||
if (!isset($this->outgoing_messages[$message_id]['seqno'])) {
|
$message = $this->outgoing_messages[$message_id];
|
||||||
$this->ackOutgoingMessageId($message_id);
|
Tools::callFork($this->sendMessage($message, false));
|
||||||
$this->gotResponseForOutgoingMessageId($message_id);
|
if (!$message->hasSeqNo()) {
|
||||||
|
$this->gotResponseForOutgoingMessage($message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->logger->logger('Could not resend '.(isset($this->outgoing_messages[$message_id]['_']) ? $this->outgoing_messages[$message_id]['_'] : $message_id));
|
$this->logger->logger('Could not resend '.($this->outgoing_messages[$message_id] ?? $message_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!$postpone) {
|
if (!$postpone) {
|
||||||
@ -154,25 +162,28 @@ trait CallHandler
|
|||||||
$args['ping_id'] = Tools::packSignedLong($args['ping_id']);
|
$args['ping_id'] = Tools::packSignedLong($args['ping_id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$deferred = new Deferred();
|
|
||||||
$methodInfo = $this->API->getTL()->getMethods()->findByMethod($method);
|
$methodInfo = $this->API->getTL()->getMethods()->findByMethod($method);
|
||||||
if (!$methodInfo) {
|
if (!$methodInfo) {
|
||||||
throw new Exception("Could not find method $method!");
|
throw new Exception("Could not find method $method!");
|
||||||
}
|
}
|
||||||
$message = \array_merge(
|
$message = new OutgoingMessage(
|
||||||
$aargs,
|
$args,
|
||||||
[
|
$method,
|
||||||
'_' => $method,
|
$methodInfo['type'],
|
||||||
'body' => $args,
|
true,
|
||||||
'type' => $methodInfo['type'],
|
!$this->shared->hasTempAuthKey() && \strpos($method, '.') === false
|
||||||
'contentRelated' => $this->contentRelated($method),
|
|
||||||
'promise' => $deferred,
|
|
||||||
'method' => true,
|
|
||||||
'unencrypted' => !$this->shared->hasTempAuthKey() && \strpos($method, '.') === false
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
if ($method === 'users.getUsers' && $args === ['id' => [['_' => 'inputUserSelf']]] || $method === 'auth.exportAuthorization' || $method === 'updates.getDifference') {
|
if ($method === 'users.getUsers' && $args === ['id' => [['_' => 'inputUserSelf']]] || $method === 'auth.exportAuthorization' || $method === 'updates.getDifference') {
|
||||||
$message['user_related'] = true;
|
$message->setUserRelated(true);
|
||||||
|
}
|
||||||
|
if (isset($aargs['msg_id'])) {
|
||||||
|
$message->setMsgId($aargs['msg_id']);
|
||||||
|
}
|
||||||
|
if ($aargs['file'] ?? false) {
|
||||||
|
$message->setFileRelated(true);
|
||||||
|
}
|
||||||
|
if (isset($aargs['FloodWaitLimit'])) {
|
||||||
|
$message->setFloodWaitLimit($aargs['FloodWaitLimit']);
|
||||||
}
|
}
|
||||||
$aargs['postpone'] = $aargs['postpone'] ?? false;
|
$aargs['postpone'] = $aargs['postpone'] ?? false;
|
||||||
$deferred = yield from $this->sendMessage($message, !$aargs['postpone']);
|
$deferred = yield from $this->sendMessage($message, !$aargs['postpone']);
|
||||||
@ -190,11 +201,17 @@ trait CallHandler
|
|||||||
*/
|
*/
|
||||||
public function objectCall(string $object, $args = [], array $aargs = ['msg_id' => null]): \Generator
|
public function objectCall(string $object, $args = [], array $aargs = ['msg_id' => null]): \Generator
|
||||||
{
|
{
|
||||||
$message = ['_' => $object, 'body' => $args, 'contentRelated' => $this->contentRelated($object), 'unencrypted' => !$this->shared->hasTempAuthKey(), 'method' => false];
|
$message = new OutgoingMessage(
|
||||||
|
$args,
|
||||||
|
$object,
|
||||||
|
'',
|
||||||
|
false,
|
||||||
|
!$this->shared->hasTempAuthKey()
|
||||||
|
);
|
||||||
if (isset($aargs['promise'])) {
|
if (isset($aargs['promise'])) {
|
||||||
$message['promise'] = $aargs['promise'];
|
$message->setPromise($aargs['promise']);
|
||||||
}
|
}
|
||||||
$aargs['postpone'] = $aargs['postpone'] ?? false;
|
$aargs['postpone'] = $aargs['postpone'] ?? false;
|
||||||
return $this->sendMessage($message, !$aargs['postpone']);
|
return yield from $this->sendMessage($message, !$aargs['postpone']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,4 +80,50 @@ abstract class MsgIdHandler
|
|||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
abstract public function getMaxId(bool $incoming);
|
abstract public function getMaxId(bool $incoming);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get readable representation of message ID.
|
||||||
|
*
|
||||||
|
* @param string $messageId
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
abstract protected static function toStringInternal(string $messageId): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup incoming or outgoing messages.
|
||||||
|
*
|
||||||
|
* @param boolean $incoming
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function cleanup(bool $incoming): void
|
||||||
|
{
|
||||||
|
if ($incoming) {
|
||||||
|
$array = &$this->session->incoming_messages;
|
||||||
|
} else {
|
||||||
|
$array = &$this->session->outgoing_messages;
|
||||||
|
}
|
||||||
|
if (\count($array) > $this->session->API->settings->getRpc()->getLimitOutgoing()) {
|
||||||
|
\reset($array);
|
||||||
|
$key = \key($array);
|
||||||
|
foreach ($array as $key => $message) {
|
||||||
|
if ($message->canGarbageCollect()) {
|
||||||
|
unset($array[$key]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$this->session->API->logger->logger("Can't garbage collect $message", \danog\MadelineProto\Logger::VERBOSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get readable representation of message ID.
|
||||||
|
*
|
||||||
|
* @param string $messageId
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function toString(string $messageId): string
|
||||||
|
{
|
||||||
|
return PHP_INT_SIZE === 8
|
||||||
|
? MsgIdHandler64::toStringInternal($messageId)
|
||||||
|
: MsgIdHandler32::toStringInternal($messageId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,15 +66,8 @@ class MsgIdHandler32 extends MsgIdHandler
|
|||||||
if ($newMessageId->compare($key = $this->getMaxId($incoming = false)) <= 0) {
|
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);
|
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->getRpc()->getLimitOutgoing()) {
|
$this->cleanup(false);
|
||||||
\reset($this->session->outgoing_messages);
|
|
||||||
$key = \key($this->session->outgoing_messages);
|
|
||||||
if (!isset($this->session->outgoing_messages[$key]['promise'])) {
|
|
||||||
unset($this->session->outgoing_messages[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->maxOutgoingId = $newMessageId;
|
$this->maxOutgoingId = $newMessageId;
|
||||||
$this->session->outgoing_messages[\strrev($newMessageId->toBytes())] = [];
|
|
||||||
} else {
|
} else {
|
||||||
if (!$newMessageId->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$one) && !$newMessageId->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$three)) {
|
if (!$newMessageId->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$one) && !$newMessageId->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$three)) {
|
||||||
throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3');
|
throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3');
|
||||||
@ -89,15 +82,8 @@ 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);
|
$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->getRpc()->getLimitIncoming()) {
|
$this->cleanup(true);
|
||||||
\reset($this->session->incoming_messages);
|
|
||||||
$key = \key($this->session->incoming_messages);
|
|
||||||
if (!isset($this->session->incoming_messages[$key]['promise'])) {
|
|
||||||
unset($this->session->incoming_messages[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->maxIncomingId = $newMessageId;
|
$this->maxIncomingId = $newMessageId;
|
||||||
$this->session->incoming_messages[\strrev($newMessageId->toBytes())] = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -140,4 +126,15 @@ class MsgIdHandler32 extends MsgIdHandler
|
|||||||
$this->maxIncomingId = null;
|
$this->maxIncomingId = null;
|
||||||
$this->maxOutgoingId = null;
|
$this->maxOutgoingId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get readable representation of message ID.
|
||||||
|
*
|
||||||
|
* @param string $messageId
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function toStringInternal(string $messageId): string
|
||||||
|
{
|
||||||
|
return new BigInteger(\strrev($messageId), 256);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,15 +65,8 @@ class MsgIdHandler64 extends MsgIdHandler
|
|||||||
if ($newMessageId <= $this->maxOutgoingId) {
|
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.');
|
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->getRpc()->getLimitOutgoing()) {
|
$this->cleanup(false);
|
||||||
\reset($this->session->outgoing_messages);
|
|
||||||
$key = \key($this->session->outgoing_messages);
|
|
||||||
if (!isset($this->session->outgoing_messages[$key]['promise'])) {
|
|
||||||
unset($this->session->outgoing_messages[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->maxOutgoingId = $newMessageId;
|
$this->maxOutgoingId = $newMessageId;
|
||||||
$this->session->outgoing_messages[Tools::packSignedLong($newMessageId)] = [];
|
|
||||||
} else {
|
} else {
|
||||||
if (!($newMessageId % 2)) {
|
if (!($newMessageId % 2)) {
|
||||||
throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3');
|
throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3');
|
||||||
@ -88,15 +81,8 @@ 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);
|
$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->getRpc()->getLimitIncoming()) {
|
$this->cleanup(true);
|
||||||
\reset($this->session->incoming_messages);
|
|
||||||
$key = \key($this->session->incoming_messages);
|
|
||||||
if (!isset($this->session->incoming_messages[$key]['promise'])) {
|
|
||||||
unset($this->session->incoming_messages[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->maxIncomingId = $newMessageId;
|
$this->maxIncomingId = $newMessageId;
|
||||||
$this->session->incoming_messages[Tools::packSignedLong($newMessageId)] = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -124,4 +110,15 @@ class MsgIdHandler64 extends MsgIdHandler
|
|||||||
{
|
{
|
||||||
return $this->{$incoming ? 'maxIncomingId' : 'maxOutgoingId'};
|
return $this->{$incoming ? 'maxIncomingId' : 'maxOutgoingId'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get readable representation of message ID.
|
||||||
|
*
|
||||||
|
* @param string $messageId
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function toStringInternal(string $messageId): string
|
||||||
|
{
|
||||||
|
return (string) Tools::unpackSignedLong($messageId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,20 +19,108 @@
|
|||||||
|
|
||||||
namespace danog\MadelineProto\MTProtoSession;
|
namespace danog\MadelineProto\MTProtoSession;
|
||||||
|
|
||||||
|
use danog\MadelineProto\MTProto;
|
||||||
|
use danog\MadelineProto\Tools;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages responses.
|
* Manages responses.
|
||||||
*/
|
*/
|
||||||
trait Reliable
|
trait Reliable
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Called when receiving a new_msg_detailed_info.
|
||||||
|
*
|
||||||
|
* @param array $content
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function onNewMsgDetailedInfo(array $content): void
|
||||||
|
{
|
||||||
|
if (isset($this->incoming_messages[$content['answer_msg_id']])) {
|
||||||
|
$this->ackIncomingMessage($this->incoming_messages[$content['answer_msg_id']]);
|
||||||
|
} else {
|
||||||
|
Tools::callFork($this->objectCall('msg_resend_req', ['msg_ids' => [$content['answer_msg_id']]], ['postpone' => true]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Called when receiving a msg_detailed_info.
|
||||||
|
*
|
||||||
|
* @param array $content
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function onMsgDetailedInfo(array $content): void
|
||||||
|
{
|
||||||
|
if (isset($this->outgoing_messages[$content['msg_id']])) {
|
||||||
|
$this->onNewMsgDetailedInfo($content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Called when receiving a msg_resend_req.
|
||||||
|
*
|
||||||
|
* @param array $content
|
||||||
|
* @param string $current_msg_id
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function onMsgResendReq(array $content, $current_msg_id): void
|
||||||
|
{
|
||||||
|
$ok = true;
|
||||||
|
foreach ($content['msg_ids'] as $msg_id) {
|
||||||
|
if (!isset($this->outgoing_messages[$msg_id]) || isset($this->incoming_messages[$msg_id])) {
|
||||||
|
$ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($ok) {
|
||||||
|
foreach ($content['msg_ids'] as $msg_id) {
|
||||||
|
$this->methodRecall('', ['message_id' => $msg_id, 'postpone' => true]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->sendMsgsStateInfo($content['msg_ids'], $current_msg_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Called when receiving a msg_resend_ans_req.
|
||||||
|
*
|
||||||
|
* @param array $content
|
||||||
|
* @param string $current_msg_id
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function onMsgResendAnsReq(array $content, $current_msg_id): void
|
||||||
|
{
|
||||||
|
$this->sendMsgsStateInfo($content['msg_ids'], $current_msg_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when receiving a msgs_all_info.
|
||||||
|
*
|
||||||
|
* @param array $content
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function onMsgsAllInfo(array $content): void
|
||||||
|
{
|
||||||
|
foreach ($content['msg_ids'] as $key => $msg_id) {
|
||||||
|
$info = \ord($content['info'][$key]);
|
||||||
|
$msg_id = MsgIdHandler::toString($msg_id);
|
||||||
|
$status = 'Status for message id '.$msg_id.': ';
|
||||||
|
/*if ($info & 4) {
|
||||||
|
*$this->gotResponseForOutgoingMessageId($msg_id);
|
||||||
|
*}
|
||||||
|
*/
|
||||||
|
foreach (MTProto::MSGS_INFO_FLAGS as $flag => $description) {
|
||||||
|
if (($info & $flag) !== 0) {
|
||||||
|
$status .= $description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->logger->logger($status, \danog\MadelineProto\Logger::NOTICE);
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Send state info for message IDs.
|
* Send state info for message IDs.
|
||||||
*
|
*
|
||||||
* @param string|int $req_msg_id Message ID of msgs_state_req that initiated this
|
|
||||||
* @param array $msg_ids Message IDs to send info about
|
* @param array $msg_ids Message IDs to send info about
|
||||||
|
* @param string|int $req_msg_id Message ID of msgs_state_req that initiated this
|
||||||
*
|
*
|
||||||
* @return \Generator
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function sendMsgsStateInfo($req_msg_id, array $msg_ids): \Generator
|
public function sendMsgsStateInfo(array $msg_ids, $req_msg_id): void
|
||||||
{
|
{
|
||||||
$this->logger->logger('Sending state info for '.\count($msg_ids).' message IDs');
|
$this->logger->logger('Sending state info for '.\count($msg_ids).' message IDs');
|
||||||
$info = '';
|
$info = '';
|
||||||
@ -52,10 +140,10 @@ trait Reliable
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->logger->logger("Know about {$msg_id}");
|
$this->logger->logger("Know about {$msg_id}");
|
||||||
$cur_info |= 4;
|
$cur_info = $this->incoming_messages[$msg_id]->getState();
|
||||||
}
|
}
|
||||||
$info .= \chr($cur_info);
|
$info .= \chr($cur_info);
|
||||||
}
|
}
|
||||||
$this->outgoing_messages[yield from $this->objectCall('msgs_state_info', ['req_msg_id' => $req_msg_id, 'info' => $info], ['postpone' => true])]['response'] = $req_msg_id;
|
Tools::callFork($this->objectCall('msgs_state_info', ['req_msg_id' => $req_msg_id, 'info' => $info], ['postpone' => true]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,14 @@
|
|||||||
|
|
||||||
namespace danog\MadelineProto\MTProtoSession;
|
namespace danog\MadelineProto\MTProtoSession;
|
||||||
|
|
||||||
|
use Amp\Failure;
|
||||||
use Amp\Loop;
|
use Amp\Loop;
|
||||||
use danog\MadelineProto\Logger;
|
use danog\MadelineProto\Logger;
|
||||||
use danog\MadelineProto\Loop\Update\UpdateLoop;
|
use danog\MadelineProto\Loop\Update\UpdateLoop;
|
||||||
use danog\MadelineProto\MTProto;
|
use danog\MadelineProto\MTProto;
|
||||||
|
use danog\MadelineProto\MTProto\IncomingMessage;
|
||||||
|
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||||
|
use danog\MadelineProto\Tools;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages responses.
|
* Manages responses.
|
||||||
@ -32,492 +36,360 @@ use danog\MadelineProto\MTProto;
|
|||||||
trait ResponseHandler
|
trait ResponseHandler
|
||||||
{
|
{
|
||||||
public $n = 0;
|
public $n = 0;
|
||||||
public function handleMessages(): bool
|
public function handleMessages(): void
|
||||||
{
|
{
|
||||||
$only_updates = true;
|
|
||||||
while ($this->new_incoming) {
|
while ($this->new_incoming) {
|
||||||
\reset($this->new_incoming);
|
\reset($this->new_incoming);
|
||||||
$current_msg_id = \key($this->new_incoming);
|
$current_msg_id = \key($this->new_incoming);
|
||||||
if (!isset($this->incoming_messages[$current_msg_id])) {
|
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
/** @var IncomingMessage */
|
||||||
continue;
|
$message = $this->new_incoming[$current_msg_id];
|
||||||
|
unset($this->new_incoming[$current_msg_id]);
|
||||||
|
|
||||||
|
|
||||||
|
$this->logger->logger($message->log($this->datacenter), Logger::ULTRA_VERBOSE);
|
||||||
|
|
||||||
|
$type = $message->getType();
|
||||||
|
if ($type !== 'msg_container') {
|
||||||
|
$this->checkInSeqNo($message);
|
||||||
}
|
}
|
||||||
$this->logger->logger((isset($this->incoming_messages[$current_msg_id]['from_container']) ? 'Inside of container, received ' : 'Received ').$this->incoming_messages[$current_msg_id]['content']['_'].' from DC '.$this->datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
switch ($type) {
|
||||||
switch ($this->incoming_messages[$current_msg_id]['content']['_']) {
|
|
||||||
case 'msgs_ack':
|
case 'msgs_ack':
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
foreach ($message->read()['msg_ids'] as $msg_id) {
|
||||||
$this->checkInSeqNo($current_msg_id);
|
|
||||||
$only_updates = false;
|
|
||||||
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
|
|
||||||
$this->ackOutgoingMessageId($msg_id);
|
|
||||||
// Acknowledge that the server received my message
|
// Acknowledge that the server received my message
|
||||||
|
$this->ackOutgoingMessageId($msg_id);
|
||||||
}
|
}
|
||||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
|
||||||
break;
|
break;
|
||||||
case 'rpc_result':
|
case 'rpc_result':
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
$this->ackIncomingMessage($message);
|
||||||
$this->ackIncomingMessageId($current_msg_id);
|
// no break
|
||||||
$only_updates = false;
|
|
||||||
// Acknowledge that the server received my request
|
|
||||||
$req_msg_id = $this->incoming_messages[$current_msg_id]['content']['req_msg_id'];
|
|
||||||
$this->incoming_messages[$current_msg_id]['content'] = $this->incoming_messages[$current_msg_id]['content']['result'];
|
|
||||||
$this->checkInSeqNo($current_msg_id);
|
|
||||||
$this->handleResponse($req_msg_id, $current_msg_id);
|
|
||||||
break;
|
|
||||||
case 'future_salts':
|
case 'future_salts':
|
||||||
case 'msgs_state_info':
|
case 'msgs_state_info':
|
||||||
$msg_id_type = 'req_msg_id';
|
|
||||||
// no break
|
|
||||||
case 'bad_server_salt':
|
case 'bad_server_salt':
|
||||||
case 'bad_msg_notification':
|
case 'bad_msg_notification':
|
||||||
$msg_id_type = isset($msg_id_type) ? $msg_id_type : 'bad_msg_id';
|
|
||||||
// no break
|
|
||||||
case 'pong':
|
case 'pong':
|
||||||
$msg_id_type = isset($msg_id_type) ? $msg_id_type : 'msg_id';
|
$this->handleResponse($message);
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
|
||||||
$this->checkInSeqNo($current_msg_id);
|
|
||||||
$only_updates = false;
|
|
||||||
$this->handleResponse($this->incoming_messages[$current_msg_id]['content'][$msg_id_type], $current_msg_id);
|
|
||||||
unset($msg_id_type);
|
|
||||||
break;
|
break;
|
||||||
case 'new_session_created':
|
case 'new_session_created':
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
$this->ackIncomingMessage($message);
|
||||||
$this->checkInSeqNo($current_msg_id);
|
$this->shared->getTempAuthKey()->setServerSalt($message->read()['server_salt']);
|
||||||
$only_updates = false;
|
|
||||||
$this->shared->getTempAuthKey()->setServerSalt($this->incoming_messages[$current_msg_id]['content']['server_salt']);
|
|
||||||
$this->ackIncomingMessageId($current_msg_id);
|
|
||||||
// Acknowledge that I received the server's response
|
|
||||||
if ($this->API->authorized === MTProto::LOGGED_IN && !$this->API->isInitingAuthorization() && $this->API->datacenter->getDataCenterConnection($this->API->datacenter->curdc)->hasTempAuthKey() && isset($this->API->updaters[UpdateLoop::GENERIC])) {
|
if ($this->API->authorized === MTProto::LOGGED_IN && !$this->API->isInitingAuthorization() && $this->API->datacenter->getDataCenterConnection($this->API->datacenter->curdc)->hasTempAuthKey() && isset($this->API->updaters[UpdateLoop::GENERIC])) {
|
||||||
$this->API->updaters[UpdateLoop::GENERIC]->resumeDefer();
|
$this->API->updaters[UpdateLoop::GENERIC]->resumeDefer();
|
||||||
}
|
}
|
||||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
|
||||||
break;
|
break;
|
||||||
case 'msg_container':
|
case 'msg_container':
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
foreach ($message->read()['messages'] as $message) {
|
||||||
$only_updates = false;
|
|
||||||
foreach ($this->incoming_messages[$current_msg_id]['content']['messages'] as $message) {
|
|
||||||
$this->msgIdHandler->checkMessageId($message['msg_id'], ['outgoing' => false, 'container' => true]);
|
$this->msgIdHandler->checkMessageId($message['msg_id'], ['outgoing' => false, 'container' => true]);
|
||||||
$this->incoming_messages[$message['msg_id']] = ['seq_no' => $message['seqno'], 'content' => $message['body'], 'from_container' => true];
|
$newMessage = new IncomingMessage($message['body'], $message['msg_id'], true);
|
||||||
$this->new_incoming[$message['msg_id']] = $message['msg_id'];
|
$newMessage->setSeqNo($message['seqno']);
|
||||||
|
$this->new_incoming[$message['msg_id']] = $this->incoming_messages[$message['msg_id']] = $newMessage;
|
||||||
}
|
}
|
||||||
|
unset($newMessage, $message);
|
||||||
\ksort($this->new_incoming);
|
\ksort($this->new_incoming);
|
||||||
//$this->handleMessages();
|
|
||||||
//$this->checkInSeqNo($current_msg_id);
|
|
||||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
|
||||||
break;
|
break;
|
||||||
case 'msg_copy':
|
case 'msg_copy':
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
$this->ackIncomingMessage($message);
|
||||||
$this->checkInSeqNo($current_msg_id);
|
$content = $message->read();
|
||||||
$only_updates = false;
|
$referencedMsgId = $content['msg_id'];
|
||||||
$this->ackIncomingMessageId($current_msg_id);
|
if (isset($this->incoming_messages[$referencedMsgId])) {
|
||||||
// Acknowledge that I received the server's response
|
$this->ackIncomingMessage($this->incoming_messages[$referencedMsgId]);
|
||||||
if (isset($this->incoming_messages[$this->incoming_messages[$current_msg_id]['content']['orig_message']['msg_id']])) {
|
|
||||||
$this->ackIncomingMessageId($this->incoming_messages[$current_msg_id]['content']['orig_message']['msg_id']);
|
|
||||||
// Acknowledge that I received the server's response
|
|
||||||
} else {
|
} else {
|
||||||
$message = $this->incoming_messages[$current_msg_id]['content'];
|
$this->msgIdHandler->checkMessageId($referencedMsgId, ['outgoing' => false, 'container' => true]);
|
||||||
$this->msgIdHandler->checkMessageId($message['orig_message']['msg_id'], ['outgoing' => false, 'container' => true]);
|
$message = new IncomingMessage($content['orig_message'], $referencedMsgId);
|
||||||
$this->incoming_messages[$message['orig_message']['msg_id']] = ['content' => $this->incoming_messages[$current_msg_id]['content']['orig_message']];
|
$this->new_incoming[$referencedMsgId] = $this->incoming_messages[$referencedMsgId] = $message;
|
||||||
$this->new_incoming[$message['orig_message']['msg_id']] = $message['orig_message']['msg_id'];
|
unset($message);
|
||||||
}
|
}
|
||||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
unset($content, $referencedMsgId);
|
||||||
break;
|
break;
|
||||||
case 'http_wait':
|
case 'http_wait':
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
$this->logger->logger($message->read(), Logger::NOTICE);
|
||||||
$this->checkInSeqNo($current_msg_id);
|
|
||||||
$only_updates = false;
|
|
||||||
$this->logger->logger($this->incoming_messages[$current_msg_id]['content'], \danog\MadelineProto\Logger::NOTICE);
|
|
||||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
|
||||||
break;
|
break;
|
||||||
case 'msgs_state_req':
|
case 'msgs_state_req':
|
||||||
$this->checkInSeqNo($current_msg_id);
|
$this->sendMsgsStateInfo($message->read()['msg_ids'], $current_msg_id);
|
||||||
$only_updates = false;
|
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
|
||||||
\danog\MadelineProto\Tools::callFork($this->sendMsgsStateInfo($current_msg_id, $this->incoming_messages[$current_msg_id]['content']['msg_ids']));
|
|
||||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
|
||||||
break;
|
break;
|
||||||
case 'msgs_all_info':
|
case 'msgs_all_info':
|
||||||
$this->checkInSeqNo($current_msg_id);
|
$this->onMsgsAllInfo($message->read());
|
||||||
$only_updates = false;
|
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
|
||||||
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $key => $msg_id) {
|
|
||||||
$info = \ord($this->incoming_messages[$current_msg_id]['content']['info'][$key]);
|
|
||||||
$msg_id = new \tgseclib\Math\BigInteger(\strrev($msg_id), 256);
|
|
||||||
$status = 'Status for message id '.$msg_id.': ';
|
|
||||||
/*if ($info & 4) {
|
|
||||||
*$this->gotResponseForOutgoingMessageId($msg_id);
|
|
||||||
*}
|
|
||||||
*/
|
|
||||||
foreach (MTProto::MSGS_INFO_FLAGS as $flag => $description) {
|
|
||||||
if (($info & $flag) !== 0) {
|
|
||||||
$status .= $description;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->logger->logger($status, \danog\MadelineProto\Logger::NOTICE);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'msg_detailed_info':
|
case 'msg_detailed_info':
|
||||||
$this->checkInSeqNo($current_msg_id);
|
$this->onMsgDetailedInfo($message->read());
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
|
||||||
$only_updates = false;
|
|
||||||
if (isset($this->outgoing_messages[$this->incoming_messages[$current_msg_id]['content']['msg_id']])) {
|
|
||||||
if (isset($this->incoming_messages[$this->incoming_messages[$current_msg_id]['content']['answer_msg_id']])) {
|
|
||||||
$this->handleResponse($this->incoming_messages[$current_msg_id]['content']['msg_id'], $this->incoming_messages[$current_msg_id]['content']['answer_msg_id']);
|
|
||||||
} else {
|
|
||||||
\danog\MadelineProto\Tools::callFork($this->objectCall('msg_resend_req', ['msg_ids' => [$this->incoming_messages[$current_msg_id]['content']['answer_msg_id']]], ['postpone' => true]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'msg_new_detailed_info':
|
case 'msg_new_detailed_info':
|
||||||
$this->checkInSeqNo($current_msg_id);
|
$this->onNewMsgDetailedInfo($message->read());
|
||||||
$only_updates = false;
|
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
|
||||||
if (isset($this->incoming_messages[$this->incoming_messages[$current_msg_id]['content']['answer_msg_id']])) {
|
|
||||||
$this->ackIncomingMessageId($this->incoming_messages[$current_msg_id]['content']['answer_msg_id']);
|
|
||||||
} else {
|
|
||||||
\danog\MadelineProto\Tools::callFork($this->objectCall('msg_resend_req', ['msg_ids' => [$this->incoming_messages[$current_msg_id]['content']['answer_msg_id']]], ['postpone' => true]));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'msg_resend_req':
|
case 'msg_resend_req':
|
||||||
$this->checkInSeqNo($current_msg_id);
|
$this->onMsgResendReq($message->read(), $current_msg_id);
|
||||||
$only_updates = false;
|
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
|
||||||
$ok = true;
|
|
||||||
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
|
|
||||||
if (!isset($this->outgoing_messages[$msg_id]) || isset($this->incoming_messages[$msg_id])) {
|
|
||||||
$ok = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($ok) {
|
|
||||||
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
|
|
||||||
$this->methodRecall('', ['message_id' => $msg_id, 'postpone' => true]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
\danog\MadelineProto\Tools::callFork($this->sendMsgsStateInfo($current_msg_id, $this->incoming_messages[$current_msg_id]['content']['msg_ids']));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'msg_resend_ans_req':
|
case 'msg_resend_ans_req':
|
||||||
$this->checkInSeqNo($current_msg_id);
|
$this->onMsgResendAnsReq($message->read(), $current_msg_id);
|
||||||
$only_updates = false;
|
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
|
||||||
\danog\MadelineProto\Tools::callFork($this->sendMsgsStateInfo($current_msg_id, $this->incoming_messages[$current_msg_id]['content']['msg_ids']));
|
|
||||||
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
|
|
||||||
if (isset($this->incoming_messages[$msg_id]['response']) && isset($this->outgoing_messages[$this->incoming_messages[$msg_id]['response']])) {
|
|
||||||
\danog\MadelineProto\Tools::callFork($this->objectCall($this->outgoing_messages[$this->incoming_messages[$msg_id]['response']]['_'], $this->outgoing_messages[$this->incoming_messages[$msg_id]['response']]['body'], ['postpone' => true]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$this->checkInSeqNo($current_msg_id);
|
$this->ackIncomingMessage($message);
|
||||||
$this->ackIncomingMessageId($current_msg_id);
|
$response_type = $this->API->getTL()->getConstructors()->findByPredicate($message->getContent()['_'])['type'];
|
||||||
// Acknowledge that I received the server's response
|
if ($response_type == 'Updates') {
|
||||||
$response_type = $this->API->getTL()->getConstructors()->findByPredicate($this->incoming_messages[$current_msg_id]['content']['_'])['type'];
|
if (!$this->isCdn()) {
|
||||||
switch ($response_type) {
|
Tools::callForkDefer($this->API->handleUpdates($message->read()));
|
||||||
case 'Updates':
|
}
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
break;
|
||||||
if (!$this->isCdn()) {
|
|
||||||
\danog\MadelineProto\Tools::callForkDefer($this->API->handleUpdates($this->incoming_messages[$current_msg_id]['content']));
|
|
||||||
}
|
|
||||||
unset($this->incoming_messages[$current_msg_id]['content']);
|
|
||||||
$only_updates = true && $only_updates;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$only_updates = false;
|
|
||||||
$this->logger->logger('Trying to assign a response of type '.$response_type.' to its request...', \danog\MadelineProto\Logger::VERBOSE);
|
|
||||||
foreach ($this->new_outgoing as $key => $expecting_msg_id) {
|
|
||||||
$expecting = $this->outgoing_messages[$expecting_msg_id];
|
|
||||||
if (!isset($expecting['type'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$this->logger->logger('Does the request of return type '.$expecting['type'].' match?', \danog\MadelineProto\Logger::VERBOSE);
|
|
||||||
if ($response_type === $expecting['type']) {
|
|
||||||
$this->logger->logger('Yes', \danog\MadelineProto\Logger::VERBOSE);
|
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
|
||||||
$this->handleResponse($expecting_msg_id, $current_msg_id);
|
|
||||||
break 2;
|
|
||||||
}
|
|
||||||
$this->logger->logger('No', \danog\MadelineProto\Logger::VERBOSE);
|
|
||||||
}
|
|
||||||
$this->logger->logger('Dunno how to handle '.PHP_EOL.\var_export($this->incoming_messages[$current_msg_id]['content'], true), \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
unset($this->new_incoming[$current_msg_id]);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->logger->logger('Trying to assign a response of type '.$response_type.' to its request...', Logger::VERBOSE);
|
||||||
|
foreach ($this->new_outgoing as $expecting_msg_id => $expecting) {
|
||||||
|
if (!$type = $expecting->getType()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->logger->logger("Does the request of return type $type match?", Logger::VERBOSE);
|
||||||
|
if ($response_type === $type) {
|
||||||
|
$this->logger->logger('Yes', Logger::VERBOSE);
|
||||||
|
$this->handleResponse($message, $expecting_msg_id);
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
$this->logger->logger('No', Logger::VERBOSE);
|
||||||
|
}
|
||||||
|
$this->logger->logger('Dunno how to handle '.PHP_EOL.\var_export($message->read(), true), Logger::FATAL_ERROR);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->pending_outgoing) {
|
if ($this->pendingOutgoing) {
|
||||||
$this->writer->resume();
|
$this->writer->resume();
|
||||||
}
|
}
|
||||||
return $only_updates;
|
|
||||||
}
|
}
|
||||||
|
public function handleReject(OutgoingMessage $message, \Throwable $data): void
|
||||||
|
{
|
||||||
|
$this->gotResponseForOutgoingMessage($message);
|
||||||
|
Loop::defer(fn () => $message->reply(new Failure($data)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reject request with exception.
|
* Handle RPC response.
|
||||||
*
|
*
|
||||||
* @param array $request Request
|
* @param IncomingMessage $message Incoming message
|
||||||
* @param \Throwable $data Exception
|
* @param string $requestId Request ID
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handleReject(&$request, \Throwable $data)
|
private function handleResponse(IncomingMessage $message, $requestId = null): void
|
||||||
{
|
{
|
||||||
if (isset($request['promise']) && \is_object($request['promise'])) {
|
$requestId ??= $message->getRequestId();
|
||||||
Loop::defer(function () use (&$request, $data) {
|
$response = $message->read();
|
||||||
if (isset($request['promise'])) {
|
if (!isset($this->outgoing_messages[$requestId])) {
|
||||||
$this->logger->logger('Rejecting: '.(isset($request['_']) ? $request['_'] : '-'));
|
$requestId = MsgIdHandler::toString($requestId);
|
||||||
$this->logger->logger("Rejecting: {$data}");
|
$this->logger->logger("Got a reponse $message with message ID $requestId, but there is no request!", Logger::FATAL_ERROR);
|
||||||
$promise = $request['promise'];
|
|
||||||
unset($request['promise']);
|
|
||||||
try {
|
|
||||||
$promise->fail($data);
|
|
||||||
} catch (\Error $e) {
|
|
||||||
if (\strpos($e->getMessage(), "Promise has already been resolved") !== 0) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
$this->logger->logger("Got promise already resolved error", \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->logger->logger('Rejecting: already got response for '.(isset($request['_']) ? $request['_'] : '-'));
|
|
||||||
$this->logger->logger("Rejecting: {$data}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} elseif (isset($request['container'])) {
|
|
||||||
foreach ($request['container'] as $message_id) {
|
|
||||||
$this->handleReject($this->outgoing_messages[$message_id], $data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->logger->logger('Rejecting: already got response for '.(isset($request['_']) ? $request['_'] : '-'));
|
|
||||||
$this->logger->logger("Rejecting: {$data}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function handleResponse($request_id, $response_id)
|
|
||||||
{
|
|
||||||
$response =& $this->incoming_messages[$response_id]['content'];
|
|
||||||
unset($this->incoming_messages[$response_id]['content']);
|
|
||||||
$request =& $this->outgoing_messages[$request_id];
|
|
||||||
if (isset($response['_'])) {
|
|
||||||
switch ($response['_']) {
|
|
||||||
case 'rpc_error':
|
|
||||||
if (($request['method'] ?? false) && $request['_'] !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
|
|
||||||
$this->shared->getTempAuthKey()->init(true);
|
|
||||||
}
|
|
||||||
if (\in_array($response['error_message'], ['PERSISTENT_TIMESTAMP_EMPTY', 'PERSISTENT_TIMESTAMP_INVALID'])) {
|
|
||||||
$this->gotResponseForOutgoingMessageId($request_id);
|
|
||||||
$this->handleReject($request, new \danog\MadelineProto\PTSException($response['error_message']));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($response['error_message'] === 'PERSISTENT_TIMESTAMP_OUTDATED') {
|
|
||||||
$response['error_code'] = 500;
|
|
||||||
}
|
|
||||||
if (\strpos($response['error_message'], 'FILE_REFERENCE_') === 0) {
|
|
||||||
$this->logger->logger("Got {$response['error_message']}, refreshing file reference and repeating method call...");
|
|
||||||
$request['refreshReferences'] = true;
|
|
||||||
if (isset($request['serialized_body'])) {
|
|
||||||
unset($request['serialized_body']);
|
|
||||||
}
|
|
||||||
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch ($response['error_code']) {
|
|
||||||
case 500:
|
|
||||||
case -500:
|
|
||||||
if ($response['error_message'] === 'MSG_WAIT_FAILED') {
|
|
||||||
$this->call_queue[$request['queue']] = [];
|
|
||||||
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (\in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'HISTORY_GET_FAILED', 'RPC_CONNECT_FAILED', 'RPC_CALL_FAIL', 'PERSISTENT_TIMESTAMP_OUTDATED', 'RPC_MCGET_FAIL', 'no workers running', 'No workers running'])) {
|
|
||||||
Loop::delay(1 * 1000, [$this, 'methodRecall'], ['message_id' => $request_id]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->gotResponseForOutgoingMessageId($request_id);
|
|
||||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
|
||||||
return;
|
|
||||||
case 303:
|
|
||||||
$this->API->datacenter->curdc = $datacenter = (int) \preg_replace('/[^0-9]+/', '', $response['error_message']);
|
|
||||||
if (isset($request['file']) && $request['file'] && $this->API->datacenter->has($datacenter.'_media')) {
|
|
||||||
$datacenter .= '_media';
|
|
||||||
}
|
|
||||||
if (isset($request['user_related']) && $request['user_related']) {
|
|
||||||
$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]);
|
|
||||||
return;
|
|
||||||
case 401:
|
|
||||||
switch ($response['error_message']) {
|
|
||||||
case 'USER_DEACTIVATED':
|
|
||||||
case 'USER_DEACTIVATED_BAN':
|
|
||||||
case 'SESSION_REVOKED':
|
|
||||||
case 'SESSION_EXPIRED':
|
|
||||||
$this->gotResponseForOutgoingMessageId($request_id);
|
|
||||||
$this->logger->logger($response['error_message'], \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
foreach ($this->API->datacenter->getDataCenterConnections() as $socket) {
|
|
||||||
$socket->setTempAuthKey(null);
|
|
||||||
$socket->setPermAuthKey(null);
|
|
||||||
$socket->resetSession();
|
|
||||||
}
|
|
||||||
if (\in_array($response['error_message'], ['USER_DEACTIVATED', 'USER_DEACTIVATED_BAN'], true)) {
|
|
||||||
$this->logger->logger('!!!!!!! WARNING !!!!!!!', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
$this->logger->logger("Telegram's flood prevention system suspended this account.", \danog\MadelineProto\Logger::ERROR);
|
|
||||||
$this->logger->logger('To continue, manual verification is required.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
$phone = isset($this->API->authorization['user']['phone']) ? '+'.$this->API->authorization['user']['phone'] : 'you are currently using';
|
|
||||||
$this->logger->logger('Send an email to recover@telegram.org, asking to unban the phone number '.$phone.', and shortly describe what will you do with this phone number.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
$this->logger->logger('Then login again.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
$this->logger->logger('If you intentionally deleted this account, ignore this message.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
}
|
|
||||||
$this->API->resetSession();
|
|
||||||
\danog\MadelineProto\Tools::callFork((function () use ($request, &$response): \Generator {
|
|
||||||
yield from $this->API->initAuthorization();
|
|
||||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
|
||||||
})());
|
|
||||||
return;
|
|
||||||
case 'AUTH_KEY_UNREGISTERED':
|
|
||||||
case 'AUTH_KEY_INVALID':
|
|
||||||
if ($this->API->authorized !== MTProto::LOGGED_IN) {
|
|
||||||
$this->gotResponseForOutgoingMessageId($request_id);
|
|
||||||
\danog\MadelineProto\Tools::callFork((function () use ($request, &$response): \Generator {
|
|
||||||
yield from $this->API->initAuthorization();
|
|
||||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
|
||||||
})());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->session_id = null;
|
|
||||||
$this->shared->setTempAuthKey(null);
|
|
||||||
$this->shared->setPermAuthKey(null);
|
|
||||||
$this->logger->logger('Auth key not registered, resetting temporary and permanent auth keys...', \danog\MadelineProto\Logger::ERROR);
|
|
||||||
if ($this->API->authorized_dc === $this->datacenter && $this->API->authorized === MTProto::LOGGED_IN) {
|
|
||||||
$this->gotResponseForOutgoingMessageId($request_id);
|
|
||||||
$this->logger->logger('Permanent auth key was main authorized key, logging out...', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
foreach ($this->API->datacenter->getDataCenterConnections() as $socket) {
|
|
||||||
$socket->setTempAuthKey(null);
|
|
||||||
$socket->setPermAuthKey(null);
|
|
||||||
}
|
|
||||||
$this->logger->logger('!!!!!!! WARNING !!!!!!!', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
$this->logger->logger("Telegram's flood prevention system suspended this account.", \danog\MadelineProto\Logger::ERROR);
|
|
||||||
$this->logger->logger('To continue, manual verification is required.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
$phone = isset($this->API->authorization['user']['phone']) ? '+'.$this->API->authorization['user']['phone'] : 'you are currently using';
|
|
||||||
$this->logger->logger('Send an email to recover@telegram.org, asking to unban the phone number '.$phone.', and quickly describe what will you do with this phone number.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
$this->logger->logger('Then login again.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
$this->logger->logger('If you intentionally deleted this account, ignore this message.', \danog\MadelineProto\Logger::FATAL_ERROR);
|
|
||||||
$this->API->resetSession();
|
|
||||||
\danog\MadelineProto\Tools::callFork((function () use ($request, &$response): \Generator {
|
|
||||||
yield from $this->API->initAuthorization();
|
|
||||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
|
||||||
})());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
\danog\MadelineProto\Tools::callFork((function () use ($request_id): \Generator {
|
|
||||||
yield from $this->API->initAuthorization();
|
|
||||||
$this->methodRecall('', ['message_id' => $request_id]);
|
|
||||||
})());
|
|
||||||
return;
|
|
||||||
case 'AUTH_KEY_PERM_EMPTY':
|
|
||||||
$this->logger->logger('Temporary auth key not bound, resetting temporary auth key...', \danog\MadelineProto\Logger::ERROR);
|
|
||||||
$this->shared->setTempAuthKey(null);
|
|
||||||
\danog\MadelineProto\Tools::callFork((function () use ($request_id): \Generator {
|
|
||||||
yield from $this->API->initAuthorization();
|
|
||||||
$this->methodRecall('', ['message_id' => $request_id]);
|
|
||||||
})());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->gotResponseForOutgoingMessageId($request_id);
|
|
||||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
|
||||||
return;
|
|
||||||
case 420:
|
|
||||||
$seconds = \preg_replace('/[^0-9]+/', '', $response['error_message']);
|
|
||||||
$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);
|
|
||||||
$request['sent'] = ($request['sent'] ?? \time()) + $seconds;
|
|
||||||
Loop::delay($seconds * 1000, [$this, 'methodRecall'], ['message_id' => $request_id]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// no break
|
|
||||||
default:
|
|
||||||
$this->gotResponseForOutgoingMessageId($request_id);
|
|
||||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case 'boolTrue':
|
|
||||||
case 'boolFalse':
|
|
||||||
$response = $response['_'] === 'boolTrue';
|
|
||||||
break;
|
|
||||||
case 'bad_server_salt':
|
|
||||||
case 'bad_msg_notification':
|
|
||||||
$this->logger->logger('Received bad_msg_notification: '.MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], \danog\MadelineProto\Logger::WARNING);
|
|
||||||
switch ($response['error_code']) {
|
|
||||||
case 48:
|
|
||||||
$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();
|
|
||||||
$this->logger->logger('Set time delta to '.$this->time_delta, \danog\MadelineProto\Logger::WARNING);
|
|
||||||
$this->API->resetMTProtoSession();
|
|
||||||
$this->shared->setTempAuthKey(null);
|
|
||||||
\danog\MadelineProto\Tools::callFork((function () use ($request_id): \Generator {
|
|
||||||
yield from $this->API->initAuthorization();
|
|
||||||
$this->methodRecall('', ['message_id' => $request_id]);
|
|
||||||
})());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->gotResponseForOutgoingMessageId($request_id);
|
|
||||||
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification: '.MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], $response['error_code'], $request['_'] ?? ''));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (($request['method'] ?? false) && $request['_'] !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
|
|
||||||
$this->shared->getTempAuthKey()->init(true);
|
|
||||||
}
|
|
||||||
if (!isset($request['promise'])) {
|
|
||||||
$this->gotResponseForOutgoingMessageId($request_id);
|
|
||||||
$this->logger->logger('Response: already got response for '.(isset($request['_']) ? $request['_'] : '-').' with message ID '.$request_id);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$botAPI = isset($request['botAPI']) && $request['botAPI'];
|
/** @var OutgoingMessage */
|
||||||
if (isset($response['_']) && !$this->isCdn() && $this->API->getTL()->getConstructors()->findByPredicate($response['_'])['type'] === 'Updates') {
|
$request = $this->outgoing_messages[$requestId];
|
||||||
$body = [];
|
if ($request->getState() & OutgoingMessage::STATE_REPLIED) {
|
||||||
if (isset($request['body']['peer'])) {
|
$this->logger->logger("Already got a reponse to $request, but there is another reply $message with message ID $requestId!", Logger::FATAL_ERROR);
|
||||||
$body['peer'] = \is_string($request['body']['peer']) ? $request['body']['peer'] : $this->API->getId($request['body']['peer']);
|
return;
|
||||||
}
|
|
||||||
if (isset($request['body']['message'])) {
|
|
||||||
$body['message'] = (string) $request['body']['message'];
|
|
||||||
}
|
|
||||||
$response['request'] = ['_' => $request['_'], 'body' => $body];
|
|
||||||
\danog\MadelineProto\Tools::callForkDefer($this->API->handleUpdates($response));
|
|
||||||
}
|
}
|
||||||
unset($request);
|
if ($response['_'] === 'rpc_result') {
|
||||||
$this->gotResponseForOutgoingMessageId($request_id);
|
$response = $response['result'];
|
||||||
$r = isset($response['_']) ? $response['_'] : \json_encode($response);
|
}
|
||||||
|
$constructor = $response['_'] ?? '';
|
||||||
|
if ($constructor === 'rpc_error') {
|
||||||
|
try {
|
||||||
|
$exception = $this->handleRpcError($request, $response);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$exception = $e;
|
||||||
|
}
|
||||||
|
if ($exception) {
|
||||||
|
$this->handleReject($request, $exception);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($constructor === 'bad_server_salt' || $constructor === 'bad_msg_notification') {
|
||||||
|
$this->logger->logger('Received bad_msg_notification: '.MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], Logger::WARNING);
|
||||||
|
switch ($response['error_code']) {
|
||||||
|
case 48:
|
||||||
|
$this->shared->getTempAuthKey()->setServerSalt($response['new_server_salt']);
|
||||||
|
$this->methodRecall('', ['message_id' => $requestId, 'postpone' => true]);
|
||||||
|
return;
|
||||||
|
case 20:
|
||||||
|
$this->methodRecall('', ['message_id' => $requestId, 'postpone' => true]);
|
||||||
|
return;
|
||||||
|
case 16:
|
||||||
|
case 17:
|
||||||
|
$this->time_delta = (int) (new \tgseclib\Math\BigInteger(\strrev($message->getMsgId()), 256))->bitwise_rightShift(32)->subtract(new \tgseclib\Math\BigInteger(\time()))->toString();
|
||||||
|
$this->logger->logger('Set time delta to '.$this->time_delta, Logger::WARNING);
|
||||||
|
$this->API->resetMTProtoSession();
|
||||||
|
$this->shared->setTempAuthKey(null);
|
||||||
|
Tools::callFork((function () use ($requestId): \Generator {
|
||||||
|
yield from $this->API->initAuthorization();
|
||||||
|
$this->methodRecall('', ['message_id' => $requestId]);
|
||||||
|
})());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification: '.MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], $response['error_code'], $request->getConstructor()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isMethod() && $request->getConstructor() !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
|
||||||
|
$this->shared->getTempAuthKey()->init(true);
|
||||||
|
}
|
||||||
|
$botAPI = $request->getBotAPI();
|
||||||
|
if (isset($response['_']) && !$this->isCdn() && $this->API->getTL()->getConstructors()->findByPredicate($response['_'])['type'] === 'Updates') {
|
||||||
|
$body = $request->getBodyOrEmpty();
|
||||||
|
$trimmed = [];
|
||||||
|
if (isset($body['peer'])) {
|
||||||
|
$trimmed['peer'] = \is_string($body['peer']) ? $body['peer'] : $this->API->getId($body['peer']);
|
||||||
|
}
|
||||||
|
if (isset($body['message'])) {
|
||||||
|
$trimmed['message'] = (string) $body['message'];
|
||||||
|
}
|
||||||
|
$response['request'] = ['_' => $request->getConstructor(), 'body' => $trimmed];
|
||||||
|
unset($body);
|
||||||
|
Tools::callForkDefer($this->API->handleUpdates($response));
|
||||||
|
}
|
||||||
|
$this->gotResponseForOutgoingMessage($request);
|
||||||
|
|
||||||
|
$r = $response['_'] ?? \json_encode($response);
|
||||||
$this->logger->logger("Defer sending {$r} to deferred", Logger::ULTRA_VERBOSE);
|
$this->logger->logger("Defer sending {$r} to deferred", Logger::ULTRA_VERBOSE);
|
||||||
\danog\MadelineProto\Tools::callFork((function () use ($request_id, $response, $botAPI): \Generator {
|
|
||||||
$r = isset($response['_']) ? $response['_'] : \json_encode($response);
|
if (!$botAPI) {
|
||||||
$this->logger->logger("Deferred: sent {$r} to deferred", Logger::ULTRA_VERBOSE);
|
Tools::callForkDefer((function () use ($request, $message, $response): \Generator {
|
||||||
if ($botAPI) {
|
yield from $message->yieldSideEffects();
|
||||||
$response = (yield from $this->API->MTProtoToBotAPI($response));
|
$request->reply($response);
|
||||||
}
|
})());
|
||||||
if (isset($this->outgoing_messages[$request_id]['promise'])) {
|
} else {
|
||||||
// This should not happen but happens, should debug
|
Tools::callForkDefer((function () use ($request, $message, $response): \Generator {
|
||||||
$promise = $this->outgoing_messages[$request_id]['promise'];
|
yield from $message->yieldSideEffects();
|
||||||
unset($this->outgoing_messages[$request_id]['promise']);
|
$response = yield from $this->API->MTProtoToBotAPI($response);
|
||||||
try {
|
$request->reply($response);
|
||||||
$promise->resolve($response);
|
})());
|
||||||
} catch (\Error $e) {
|
}
|
||||||
if (\strpos($e->getMessage(), "Promise has already been resolved") !== 0) {
|
}
|
||||||
throw $e;
|
public function handleRpcError(OutgoingMessage $request, array $response): ?\Throwable
|
||||||
}
|
{
|
||||||
$this->logger->logger("Got promise already resolved error", \danog\MadelineProto\Logger::FATAL_ERROR);
|
if ($request->isMethod() && $request->getConstructor() !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
|
||||||
|
$this->shared->getTempAuthKey()->init(true);
|
||||||
|
}
|
||||||
|
if (\in_array($response['error_message'], ['PERSISTENT_TIMESTAMP_EMPTY', 'PERSISTENT_TIMESTAMP_INVALID'])) {
|
||||||
|
return new \danog\MadelineProto\PTSException($response['error_message']);
|
||||||
|
}
|
||||||
|
if ($response['error_message'] === 'PERSISTENT_TIMESTAMP_OUTDATED') {
|
||||||
|
$response['error_code'] = 500;
|
||||||
|
}
|
||||||
|
if (\strpos($response['error_message'], 'FILE_REFERENCE_') === 0) {
|
||||||
|
$this->logger->logger("Got {$response['error_message']}, refreshing file reference and repeating method call...");
|
||||||
|
$request->setRefreshReferences(true);
|
||||||
|
$this->methodRecall('', ['message_id' => $request->getMsgId(), 'postpone' => true]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($response['error_code']) {
|
||||||
|
case 500:
|
||||||
|
case -500:
|
||||||
|
if ($response['error_message'] === 'MSG_WAIT_FAILED') {
|
||||||
|
$this->call_queue[$request->getQueueId()] = [];
|
||||||
|
$this->methodRecall('', ['message_id' => $request->getMsgId(), 'postpone' => true]);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
if (\in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'HISTORY_GET_FAILED', 'RPC_CONNECT_FAILED', 'RPC_CALL_FAIL', 'PERSISTENT_TIMESTAMP_OUTDATED', 'RPC_MCGET_FAIL', 'no workers running', 'No workers running'])) {
|
||||||
})());
|
Loop::delay(1 * 1000, [$this, 'methodRecall'], ['message_id' => $request->getMsgId()]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor());
|
||||||
|
case 303:
|
||||||
|
$this->API->datacenter->curdc = $datacenter = (int) \preg_replace('/[^0-9]+/', '', $response['error_message']);
|
||||||
|
if ($request->isFileRelated() && $this->API->datacenter->has($datacenter.'_media')) {
|
||||||
|
$datacenter .= '_media';
|
||||||
|
}
|
||||||
|
if ($request->isUserRelated()) {
|
||||||
|
$this->API->settings->setDefaultDc($this->API->authorized_dc = $this->API->datacenter->curdc);
|
||||||
|
}
|
||||||
|
Loop::defer([$this, 'methodRecall'], ['message_id' => $request->getMsgId(), 'datacenter' => $datacenter]);
|
||||||
|
//$this->API->methodRecall('', ['message_id' => $requestId, 'datacenter' => $datacenter, 'postpone' => true]);
|
||||||
|
return null;
|
||||||
|
case 401:
|
||||||
|
switch ($response['error_message']) {
|
||||||
|
case 'USER_DEACTIVATED':
|
||||||
|
case 'USER_DEACTIVATED_BAN':
|
||||||
|
case 'SESSION_REVOKED':
|
||||||
|
case 'SESSION_EXPIRED':
|
||||||
|
$this->logger->logger($response['error_message'], Logger::FATAL_ERROR);
|
||||||
|
foreach ($this->API->datacenter->getDataCenterConnections() as $socket) {
|
||||||
|
$socket->setTempAuthKey(null);
|
||||||
|
$socket->setPermAuthKey(null);
|
||||||
|
$socket->resetSession();
|
||||||
|
}
|
||||||
|
if (\in_array($response['error_message'], ['USER_DEACTIVATED', 'USER_DEACTIVATED_BAN'], true)) {
|
||||||
|
$this->logger->logger('!!!!!!! WARNING !!!!!!!', Logger::FATAL_ERROR);
|
||||||
|
$this->logger->logger("Telegram's flood prevention system suspended this account.", Logger::ERROR);
|
||||||
|
$this->logger->logger('To continue, manual verification is required.', Logger::FATAL_ERROR);
|
||||||
|
$phone = isset($this->API->authorization['user']['phone']) ? '+'.$this->API->authorization['user']['phone'] : 'you are currently using';
|
||||||
|
$this->logger->logger('Send an email to recover@telegram.org, asking to unban the phone number '.$phone.', and shortly describe what will you do with this phone number.', Logger::FATAL_ERROR);
|
||||||
|
$this->logger->logger('Then login again.', Logger::FATAL_ERROR);
|
||||||
|
$this->logger->logger('If you intentionally deleted this account, ignore this message.', Logger::FATAL_ERROR);
|
||||||
|
}
|
||||||
|
$this->API->resetSession();
|
||||||
|
$this->gotResponseForOutgoingMessage($request);
|
||||||
|
Tools::callFork((function () use ($request, $response): \Generator {
|
||||||
|
yield from $this->API->initAuthorization();
|
||||||
|
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor()));
|
||||||
|
})());
|
||||||
|
return null;
|
||||||
|
case 'AUTH_KEY_UNREGISTERED':
|
||||||
|
case 'AUTH_KEY_INVALID':
|
||||||
|
if ($this->API->authorized !== MTProto::LOGGED_IN) {
|
||||||
|
$this->gotResponseForOutgoingMessage($request);
|
||||||
|
Tools::callFork((function () use ($request, $response): \Generator {
|
||||||
|
yield from $this->API->initAuthorization();
|
||||||
|
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor()));
|
||||||
|
})());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$this->session_id = null;
|
||||||
|
$this->shared->setTempAuthKey(null);
|
||||||
|
$this->shared->setPermAuthKey(null);
|
||||||
|
$this->logger->logger('Auth key not registered, resetting temporary and permanent auth keys...', Logger::ERROR);
|
||||||
|
if ($this->API->authorized_dc === $this->datacenter && $this->API->authorized === MTProto::LOGGED_IN) {
|
||||||
|
$this->logger->logger('Permanent auth key was main authorized key, logging out...', Logger::FATAL_ERROR);
|
||||||
|
foreach ($this->API->datacenter->getDataCenterConnections() as $socket) {
|
||||||
|
$socket->setTempAuthKey(null);
|
||||||
|
$socket->setPermAuthKey(null);
|
||||||
|
}
|
||||||
|
$this->logger->logger('!!!!!!! WARNING !!!!!!!', Logger::FATAL_ERROR);
|
||||||
|
$this->logger->logger("Telegram's flood prevention system suspended this account.", Logger::ERROR);
|
||||||
|
$this->logger->logger('To continue, manual verification is required.', Logger::FATAL_ERROR);
|
||||||
|
$phone = isset($this->API->authorization['user']['phone']) ? '+'.$this->API->authorization['user']['phone'] : 'you are currently using';
|
||||||
|
$this->logger->logger('Send an email to recover@telegram.org, asking to unban the phone number '.$phone.', and quickly describe what will you do with this phone number.', Logger::FATAL_ERROR);
|
||||||
|
$this->logger->logger('Then login again.', Logger::FATAL_ERROR);
|
||||||
|
$this->logger->logger('If you intentionally deleted this account, ignore this message.', Logger::FATAL_ERROR);
|
||||||
|
$this->API->resetSession();
|
||||||
|
$this->gotResponseForOutgoingMessage($request);
|
||||||
|
Tools::callFork((function () use ($request, &$response): \Generator {
|
||||||
|
yield from $this->API->initAuthorization();
|
||||||
|
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor()));
|
||||||
|
})());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Tools::callFork((function () use ($request): \Generator {
|
||||||
|
yield from $this->API->initAuthorization();
|
||||||
|
$this->methodRecall('', ['message_id' => $request->getMsgId()]);
|
||||||
|
})());
|
||||||
|
return null;
|
||||||
|
case 'AUTH_KEY_PERM_EMPTY':
|
||||||
|
$this->logger->logger('Temporary auth key not bound, resetting temporary auth key...', Logger::ERROR);
|
||||||
|
$this->shared->setTempAuthKey(null);
|
||||||
|
Tools::callFork((function () use ($request): \Generator {
|
||||||
|
yield from $this->API->initAuthorization();
|
||||||
|
$this->methodRecall('', ['message_id' => $request->getMsgId()]);
|
||||||
|
})());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor());
|
||||||
|
case 420:
|
||||||
|
$seconds = \preg_replace('/[^0-9]+/', '', $response['error_message']);
|
||||||
|
$limit = $request->getFloodWaitLimit() ?? $this->API->settings->getRPC()->getFloodTimeout();
|
||||||
|
if (\is_numeric($seconds) && $seconds < $limit) {
|
||||||
|
$this->logger->logger("Flood, waiting '.$seconds.' seconds before repeating async call of $request...", Logger::NOTICE);
|
||||||
|
$request->setSent(($request->getSent() ?? \time()) + $seconds);
|
||||||
|
Loop::delay($seconds * 1000, [$this, 'methodRecall'], ['message_id' => $request->getMsgId()]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// no break
|
||||||
|
default:
|
||||||
|
return new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
namespace danog\MadelineProto\MTProtoSession;
|
namespace danog\MadelineProto\MTProtoSession;
|
||||||
|
|
||||||
|
use danog\MadelineProto\MTProto\IncomingMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages sequence number.
|
* Manages sequence number.
|
||||||
*/
|
*/
|
||||||
@ -35,11 +37,16 @@ trait SeqNoHandler
|
|||||||
//$this->API->logger->logger("OUT: $value + $in = ".$this->session_out_seq_no);
|
//$this->API->logger->logger("OUT: $value + $in = ".$this->session_out_seq_no);
|
||||||
return $value * 2 + $in;
|
return $value * 2 + $in;
|
||||||
}
|
}
|
||||||
public function checkInSeqNo($current_msg_id): void
|
public function checkInSeqNo(IncomingMessage $message): void
|
||||||
{
|
{
|
||||||
$type = isset($this->incoming_messages[$current_msg_id]['content']['_']) ? $this->incoming_messages[$current_msg_id]['content']['_'] : '-';
|
if ($message->hasSeqNo()) {
|
||||||
if (isset($this->incoming_messages[$current_msg_id]['seq_no']) && ($seq_no = $this->generateInSeqNo($this->contentRelated($this->incoming_messages[$current_msg_id]['content']))) !== $this->incoming_messages[$current_msg_id]['seq_no']) {
|
$seq_no = $this->generateInSeqNo($message->isContentRelated());
|
||||||
$this->API->logger->logger('SECURITY WARNING: Seqno mismatch (should be '.$seq_no.', is '.$this->incoming_messages[$current_msg_id]['seq_no'].', '.$type.')', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
if ($seq_no !== $message->getSeqNo()) {
|
||||||
|
if ($message->isContentRelated()) {
|
||||||
|
$this->session_in_seq_no -= 1;
|
||||||
|
}
|
||||||
|
$this->API->logger->logger('SECURITY WARNING: Seqno mismatch (should be '.$seq_no.', is '.$message->getSeqNo().", $message)", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function generateInSeqNo($contentRelated)
|
public function generateInSeqNo($contentRelated)
|
||||||
|
@ -21,6 +21,8 @@ namespace danog\MadelineProto\MTProtoSession;
|
|||||||
|
|
||||||
use danog\MadelineProto\Connection;
|
use danog\MadelineProto\Connection;
|
||||||
use danog\MadelineProto\MTProto;
|
use danog\MadelineProto\MTProto;
|
||||||
|
use danog\MadelineProto\MTProto\IncomingMessage;
|
||||||
|
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages MTProto session-specific data.
|
* Manages MTProto session-specific data.
|
||||||
@ -37,39 +39,39 @@ trait Session
|
|||||||
/**
|
/**
|
||||||
* Incoming message array.
|
* Incoming message array.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var IncomingMessage[]
|
||||||
*/
|
*/
|
||||||
public $incoming_messages = [];
|
public $incoming_messages = [];
|
||||||
/**
|
/**
|
||||||
* Outgoing message array.
|
* Outgoing message array.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var OutgoingMessage[]
|
||||||
*/
|
*/
|
||||||
public $outgoing_messages = [];
|
public $outgoing_messages = [];
|
||||||
/**
|
/**
|
||||||
* New incoming message ID array.
|
* New incoming message ID array.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var IncomingMessage[]
|
||||||
*/
|
*/
|
||||||
public $new_incoming = [];
|
public $new_incoming = [];
|
||||||
/**
|
/**
|
||||||
* New outgoing message ID array.
|
* New outgoing message array.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var OutgoingMessage[]
|
||||||
*/
|
*/
|
||||||
public $new_outgoing = [];
|
public $new_outgoing = [];
|
||||||
/**
|
/**
|
||||||
* Pending outgoing messages.
|
* Pending outgoing messages.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var OutgoingMessage[]
|
||||||
*/
|
*/
|
||||||
public $pending_outgoing = [];
|
public $pendingOutgoing = [];
|
||||||
/**
|
/**
|
||||||
* Pending outgoing key.
|
* Pending outgoing key.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $pending_outgoing_key = 'a';
|
public $pendingOutgoingKey = 'a';
|
||||||
/**
|
/**
|
||||||
* Time delta with server.
|
* Time delta with server.
|
||||||
*
|
*
|
||||||
@ -113,16 +115,19 @@ trait Session
|
|||||||
*/
|
*/
|
||||||
public function resetSession(): void
|
public function resetSession(): void
|
||||||
{
|
{
|
||||||
|
$this->API->logger->logger("Resetting session in DC {$this->datacenterId}...", \danog\MadelineProto\Logger::WARNING);
|
||||||
$this->session_id = \danog\MadelineProto\Tools::random(8);
|
$this->session_id = \danog\MadelineProto\Tools::random(8);
|
||||||
$this->session_in_seq_no = 0;
|
$this->session_in_seq_no = 0;
|
||||||
$this->session_out_seq_no = 0;
|
$this->session_out_seq_no = 0;
|
||||||
$this->msgIdHandler = MsgIdHandler::createInstance($this);
|
if (!isset($this->msgIdHandler)) {
|
||||||
|
$this->msgIdHandler = MsgIdHandler::createInstance($this);
|
||||||
|
}
|
||||||
foreach ($this->outgoing_messages as &$msg) {
|
foreach ($this->outgoing_messages as &$msg) {
|
||||||
if (isset($msg['msg_id'])) {
|
if ($msg->hasMsgId()) {
|
||||||
unset($msg['msg_id']);
|
$msg->setMsgId(null);
|
||||||
}
|
}
|
||||||
if (isset($msg['seqno'])) {
|
if ($msg->hasSeqNo()) {
|
||||||
unset($msg['seqno']);
|
$msg->setSeqNo(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,10 +149,7 @@ trait Session
|
|||||||
*/
|
*/
|
||||||
public function backupSession(): array
|
public function backupSession(): array
|
||||||
{
|
{
|
||||||
$pending = \array_values($this->pending_outgoing);
|
$pending = \array_values($this->pendingOutgoing);
|
||||||
foreach ($this->new_outgoing as $id) {
|
return \array_merge($pending, $this->new_outgoing);
|
||||||
$pending[] = $this->outgoing_messages[$id];
|
|
||||||
}
|
|
||||||
return $pending;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ trait AuthKeyHandler
|
|||||||
* int $server_time
|
* int $server_time
|
||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
$server_DH_inner_data = yield from $this->TL->deserialize($answer, ['type' => '']);
|
[$server_DH_inner_data] = $this->TL->deserialize($answer, ['type' => '']);
|
||||||
/*
|
/*
|
||||||
* ***********************************************************************
|
* ***********************************************************************
|
||||||
* Do some checks
|
* Do some checks
|
||||||
@ -411,7 +411,7 @@ trait AuthKeyHandler
|
|||||||
*/
|
*/
|
||||||
public function getDhConfig(): \Generator
|
public function getDhConfig(): \Generator
|
||||||
{
|
{
|
||||||
$dh_config = yield from $this->methodCallAsyncRead('messages.getDhConfig', ['version' => $this->dh_config['version'], 'random_length' => 0], ['datacenter' => $this->datacenter->curdc]);
|
$dh_config = yield from $this->methodCallAsyncRead('messages.getDhConfig', ['version' => $this->dh_config['version'], 'random_length' => 0]);
|
||||||
if ($dh_config['_'] === 'messages.dhConfigNotModified') {
|
if ($dh_config['_'] === 'messages.dhConfigNotModified') {
|
||||||
$this->logger->logger('DH configuration not modified', \danog\MadelineProto\Logger::VERBOSE);
|
$this->logger->logger('DH configuration not modified', \danog\MadelineProto\Logger::VERBOSE);
|
||||||
return $this->dh_config;
|
return $this->dh_config;
|
||||||
|
@ -1015,12 +1015,12 @@ trait Files
|
|||||||
$datacenter = $res['dc_id'].'_cdn';
|
$datacenter = $res['dc_id'].'_cdn';
|
||||||
if (!$this->datacenter->has($datacenter)) {
|
if (!$this->datacenter->has($datacenter)) {
|
||||||
$this->config['expires'] = -1;
|
$this->config['expires'] = -1;
|
||||||
yield from $this->getConfig([], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->getConfig([]);
|
||||||
}
|
}
|
||||||
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['stored_on_cdn'], \danog\MadelineProto\Logger::NOTICE);
|
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['stored_on_cdn'], \danog\MadelineProto\Logger::NOTICE);
|
||||||
} elseif ($res['_'] === 'upload.cdnFileReuploadNeeded') {
|
} elseif ($res['_'] === 'upload.cdnFileReuploadNeeded') {
|
||||||
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['cdn_reupload'], \danog\MadelineProto\Logger::NOTICE);
|
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['cdn_reupload'], \danog\MadelineProto\Logger::NOTICE);
|
||||||
yield from $this->getConfig([], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->getConfig([]);
|
||||||
try {
|
try {
|
||||||
$this->addCdnHashes($messageMedia['file_token'], yield from $this->methodCallAsyncRead('upload.reuploadCdnFile', ['file_token' => $messageMedia['file_token'], 'request_token' => $res['request_token']], ['heavy' => true, 'datacenter' => $old_dc]));
|
$this->addCdnHashes($messageMedia['file_token'], yield from $this->methodCallAsyncRead('upload.reuploadCdnFile', ['file_token' => $messageMedia['file_token'], 'request_token' => $res['request_token']], ['heavy' => true, 'datacenter' => $old_dc]));
|
||||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
namespace danog\MadelineProto\MTProtoTools;
|
namespace danog\MadelineProto\MTProtoTools;
|
||||||
|
|
||||||
use Amp\Http\Client\Request;
|
use Amp\Http\Client\Request;
|
||||||
|
use Amp\Promise;
|
||||||
use danog\Decoder\FileId;
|
use danog\Decoder\FileId;
|
||||||
use danog\Decoder\PhotoSizeSource\PhotoSizeSourceDialogPhoto;
|
use danog\Decoder\PhotoSizeSource\PhotoSizeSourceDialogPhoto;
|
||||||
use danog\MadelineProto\Db\DbArray;
|
use danog\MadelineProto\Db\DbArray;
|
||||||
@ -478,7 +479,13 @@ trait PeerHandler
|
|||||||
*
|
*
|
||||||
* @return \Generator Info object
|
* @return \Generator Info object
|
||||||
*
|
*
|
||||||
|
* @template TConstructor
|
||||||
|
* @psalm-param $id array{_: TConstructor}|mixed
|
||||||
|
*
|
||||||
|
* @return (((mixed|string)[]|mixed|string)[]|int|mixed|string)[]
|
||||||
|
*
|
||||||
* @psalm-return \Generator<int|mixed, \Amp\Promise|\Amp\Promise<string>|array, mixed, array{
|
* @psalm-return \Generator<int|mixed, \Amp\Promise|\Amp\Promise<string>|array, mixed, array{
|
||||||
|
* TConstructor: array
|
||||||
* InputPeer: array{_: string, user_id?: mixed, access_hash?: mixed, min?: mixed, chat_id?: mixed, channel_id?: mixed},
|
* InputPeer: array{_: string, user_id?: mixed, access_hash?: mixed, min?: mixed, chat_id?: mixed, channel_id?: mixed},
|
||||||
* Peer: array{_: string, user_id?: mixed, chat_id?: mixed, channel_id?: mixed},
|
* Peer: array{_: string, user_id?: mixed, chat_id?: mixed, channel_id?: mixed},
|
||||||
* DialogPeer: array{_: string, peer: array{_: string, user_id?: mixed, chat_id?: mixed, channel_id?: mixed}},
|
* DialogPeer: array{_: string, peer: array{_: string, user_id?: mixed, chat_id?: mixed, channel_id?: mixed}},
|
||||||
@ -529,12 +536,12 @@ trait PeerHandler
|
|||||||
$this->caching_simple[$id] = true;
|
$this->caching_simple[$id] = true;
|
||||||
if ($id < 0) {
|
if ($id < 0) {
|
||||||
if ($this->isSupergroup($id)) {
|
if ($this->isSupergroup($id)) {
|
||||||
yield from $this->methodCallAsyncRead('channels.getChannels', ['id' => [['access_hash' => 0, 'channel_id' => $this->fromSupergroup($id), '_' => 'inputChannel']]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('channels.getChannels', ['id' => [['access_hash' => 0, 'channel_id' => $this->fromSupergroup($id), '_' => 'inputChannel']]]);
|
||||||
} else {
|
} else {
|
||||||
yield from $this->methodCallAsyncRead('messages.getFullChat', ['chat_id' => -$id], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.getFullChat', ['chat_id' => -$id]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [['access_hash' => 0, 'user_id' => $id, '_' => 'inputUser']]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [['access_hash' => 0, 'user_id' => $id, '_' => 'inputUser']]]);
|
||||||
}
|
}
|
||||||
} catch (\danog\MadelineProto\Exception $e) {
|
} catch (\danog\MadelineProto\Exception $e) {
|
||||||
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
|
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
|
||||||
@ -553,9 +560,9 @@ trait PeerHandler
|
|||||||
$this->logger->logger("Only have min peer for {$id} in database, trying to fetch full info");
|
$this->logger->logger("Only have min peer for {$id} in database, trying to fetch full info");
|
||||||
try {
|
try {
|
||||||
if ($id < 0) {
|
if ($id < 0) {
|
||||||
yield from $this->methodCallAsyncRead('channels.getChannels', ['id' => [$this->genAll(yield $this->chats[$id], $folder_id)['InputChannel']]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('channels.getChannels', ['id' => [$this->genAll(yield $this->chats[$id], $folder_id)['InputChannel']]]);
|
||||||
} else {
|
} else {
|
||||||
yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [$this->genAll(yield $this->chats[$id], $folder_id)['InputUser']]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [$this->genAll(yield $this->chats[$id], $folder_id)['InputUser']]]);
|
||||||
}
|
}
|
||||||
} catch (\danog\MadelineProto\Exception $e) {
|
} catch (\danog\MadelineProto\Exception $e) {
|
||||||
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
|
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
|
||||||
@ -599,7 +606,7 @@ trait PeerHandler
|
|||||||
if ($matches[1] === '') {
|
if ($matches[1] === '') {
|
||||||
$id = $matches[2];
|
$id = $matches[2];
|
||||||
} else {
|
} else {
|
||||||
$invite = yield from $this->methodCallAsyncRead('messages.checkChatInvite', ['hash' => $matches[2]], ['datacenter' => $this->datacenter->curdc]);
|
$invite = yield from $this->methodCallAsyncRead('messages.checkChatInvite', ['hash' => $matches[2]]);
|
||||||
if (isset($invite['chat'])) {
|
if (isset($invite['chat'])) {
|
||||||
return yield from $this->getInfo($invite['chat']);
|
return yield from $this->getInfo($invite['chat']);
|
||||||
}
|
}
|
||||||
@ -628,9 +635,9 @@ trait PeerHandler
|
|||||||
$this->logger->logger("Only have min peer for {$bot_api_id} in database, trying to fetch full info");
|
$this->logger->logger("Only have min peer for {$bot_api_id} in database, trying to fetch full info");
|
||||||
try {
|
try {
|
||||||
if ($bot_api_id < 0) {
|
if ($bot_api_id < 0) {
|
||||||
yield from $this->methodCallAsyncRead('channels.getChannels', ['id' => [$this->genAll(yield $this->chats[$bot_api_id], $folder_id)['InputChannel']]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('channels.getChannels', ['id' => [$this->genAll(yield $this->chats[$bot_api_id], $folder_id)['InputChannel']]]);
|
||||||
} else {
|
} else {
|
||||||
yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [$this->genAll(yield $this->chats[$bot_api_id], $folder_id)['InputUser']]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [$this->genAll(yield $this->chats[$bot_api_id], $folder_id)['InputUser']]]);
|
||||||
}
|
}
|
||||||
} catch (\danog\MadelineProto\Exception $e) {
|
} catch (\danog\MadelineProto\Exception $e) {
|
||||||
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
|
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
|
||||||
@ -651,9 +658,13 @@ trait PeerHandler
|
|||||||
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
|
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
* @template TConstructor
|
||||||
|
* @psalm-param $constructor array{_: TConstructor}
|
||||||
|
*
|
||||||
* @return (((mixed|string)[]|mixed|string)[]|int|mixed|string)[]
|
* @return (((mixed|string)[]|mixed|string)[]|int|mixed|string)[]
|
||||||
*
|
*
|
||||||
* @psalm-return array{
|
* @psalm-return array{
|
||||||
|
* TConstructor: array
|
||||||
* InputPeer: array{_: string, user_id?: mixed, access_hash?: mixed, min?: mixed, chat_id?: mixed, channel_id?: mixed},
|
* InputPeer: array{_: string, user_id?: mixed, access_hash?: mixed, min?: mixed, chat_id?: mixed, channel_id?: mixed},
|
||||||
* Peer: array{_: string, user_id?: mixed, chat_id?: mixed, channel_id?: mixed},
|
* Peer: array{_: string, user_id?: mixed, chat_id?: mixed, channel_id?: mixed},
|
||||||
* DialogPeer: array{_: string, peer: array{_: string, user_id?: mixed, chat_id?: mixed, channel_id?: mixed}},
|
* DialogPeer: array{_: string, peer: array{_: string, user_id?: mixed, chat_id?: mixed, channel_id?: mixed}},
|
||||||
@ -762,14 +773,14 @@ trait PeerHandler
|
|||||||
switch ($partial['type']) {
|
switch ($partial['type']) {
|
||||||
case 'user':
|
case 'user':
|
||||||
case 'bot':
|
case 'bot':
|
||||||
$full = yield from $this->methodCallAsyncRead('users.getFullUser', ['id' => $partial['InputUser']], ['datacenter' => $this->datacenter->curdc]);
|
$full = yield from $this->methodCallAsyncRead('users.getFullUser', ['id' => $partial['InputUser']]);
|
||||||
break;
|
break;
|
||||||
case 'chat':
|
case 'chat':
|
||||||
$full = (yield from $this->methodCallAsyncRead('messages.getFullChat', $partial, ['datacenter' => $this->datacenter->curdc]))['full_chat'];
|
$full = (yield from $this->methodCallAsyncRead('messages.getFullChat', $partial))['full_chat'];
|
||||||
break;
|
break;
|
||||||
case 'channel':
|
case 'channel':
|
||||||
case 'supergroup':
|
case 'supergroup':
|
||||||
$full = (yield from $this->methodCallAsyncRead('channels.getFullChannel', ['channel' => $partial['InputChannel']], ['datacenter' => $this->datacenter->curdc]))['full_chat'];
|
$full = (yield from $this->methodCallAsyncRead('channels.getFullChannel', ['channel' => $partial['InputChannel']]))['full_chat'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$res = [];
|
$res = [];
|
||||||
@ -786,7 +797,7 @@ trait PeerHandler
|
|||||||
*
|
*
|
||||||
* @see https://docs.madelineproto.xyz/Chat.html
|
* @see https://docs.madelineproto.xyz/Chat.html
|
||||||
*
|
*
|
||||||
* @return \Generator<array> Chat object
|
* @return \Generator Chat object
|
||||||
*/
|
*/
|
||||||
public function getPwrChat($id, bool $fullfetch = true, bool $send = true): \Generator
|
public function getPwrChat($id, bool $fullfetch = true, bool $send = true): \Generator
|
||||||
{
|
{
|
||||||
@ -901,16 +912,21 @@ trait PeerHandler
|
|||||||
if (!isset($res['participants']) && $fullfetch && \in_array($res['type'], ['supergroup', 'channel'])) {
|
if (!isset($res['participants']) && $fullfetch && \in_array($res['type'], ['supergroup', 'channel'])) {
|
||||||
$total_count = (isset($res['participants_count']) ? $res['participants_count'] : 0) + (isset($res['admins_count']) ? $res['admins_count'] : 0) + (isset($res['kicked_count']) ? $res['kicked_count'] : 0) + (isset($res['banned_count']) ? $res['banned_count'] : 0);
|
$total_count = (isset($res['participants_count']) ? $res['participants_count'] : 0) + (isset($res['admins_count']) ? $res['admins_count'] : 0) + (isset($res['kicked_count']) ? $res['kicked_count'] : 0) + (isset($res['banned_count']) ? $res['banned_count'] : 0);
|
||||||
$res['participants'] = [];
|
$res['participants'] = [];
|
||||||
$limit = 200;
|
|
||||||
$filters = ['channelParticipantsAdmins', 'channelParticipantsBots'];
|
$filters = ['channelParticipantsAdmins', 'channelParticipantsBots'];
|
||||||
|
$promises = [];
|
||||||
foreach ($filters as $filter) {
|
foreach ($filters as $filter) {
|
||||||
yield from $this->fetchParticipants($full['InputChannel'], $filter, '', $total_count, $res);
|
$promises []= $this->fetchParticipants($full['InputChannel'], $filter, '', $total_count, $res);
|
||||||
}
|
}
|
||||||
|
yield Tools::all($promises);
|
||||||
|
|
||||||
$q = '';
|
$q = '';
|
||||||
$filters = ['channelParticipantsSearch', 'channelParticipantsKicked', 'channelParticipantsBanned'];
|
$filters = ['channelParticipantsSearch', 'channelParticipantsKicked', 'channelParticipantsBanned'];
|
||||||
|
$promises = [];
|
||||||
foreach ($filters as $filter) {
|
foreach ($filters as $filter) {
|
||||||
yield from $this->recurseAlphabetSearchParticipants($full['InputChannel'], $filter, $q, $total_count, $res);
|
$promises []= $this->recurseAlphabetSearchParticipants($full['InputChannel'], $filter, $q, $total_count, $res, 0);
|
||||||
}
|
}
|
||||||
|
yield Tools::all($promises);
|
||||||
|
|
||||||
$this->logger->logger('Fetched '.\count($res['participants'])." out of {$total_count}");
|
$this->logger->logger('Fetched '.\count($res['participants'])." out of {$total_count}");
|
||||||
$res['participants'] = \array_values($res['participants']);
|
$res['participants'] = \array_values($res['participants']);
|
||||||
}
|
}
|
||||||
@ -950,14 +966,32 @@ trait PeerHandler
|
|||||||
}
|
}
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
private function recurseAlphabetSearchParticipants($channel, $filter, $q, $total_count, &$res): \Generator
|
private function recurseAlphabetSearchParticipants($channel, $filter, $q, $total_count, &$res, int $depth): \Generator
|
||||||
{
|
{
|
||||||
if (!(yield from $this->fetchParticipants($channel, $filter, $q, $total_count, $res))) {
|
if (!(yield from $this->fetchParticipants($channel, $filter, $q, $total_count, $res))) {
|
||||||
return false;
|
return [];
|
||||||
}
|
}
|
||||||
|
$promises = [];
|
||||||
for ($x = 'a'; $x !== 'aa' && $total_count > \count($res['participants']); $x++) {
|
for ($x = 'a'; $x !== 'aa' && $total_count > \count($res['participants']); $x++) {
|
||||||
yield from $this->recurseAlphabetSearchParticipants($channel, $filter, $q.$x, $total_count, $res);
|
$promises []= $this->recurseAlphabetSearchParticipants($channel, $filter, $q.$x, $total_count, $res, $depth + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($depth > 2) {
|
||||||
|
return $promises;
|
||||||
|
}
|
||||||
|
|
||||||
|
$yielded = yield Tools::all($promises);
|
||||||
|
while ($yielded) {
|
||||||
|
$newYielded = [];
|
||||||
|
|
||||||
|
foreach (\array_chunk([...$yielded], 10) as $promises) {
|
||||||
|
$newYielded = \array_merge(yield Tools::all($promises), $newYielded);
|
||||||
|
}
|
||||||
|
|
||||||
|
$yielded = $newYielded;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
private function fetchParticipants($channel, $filter, $q, $total_count, &$res): \Generator
|
private function fetchParticipants($channel, $filter, $q, $total_count, &$res): \Generator
|
||||||
{
|
{
|
||||||
@ -977,9 +1011,9 @@ trait PeerHandler
|
|||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
if ($cached = ($gres['_'] === 'channels.channelParticipantsNotModified')) {
|
if ($cached = ($gres['_'] === 'channels.channelParticipantsNotModified')) {
|
||||||
$gres = yield from $this->fetchParticipantsCache($channel, $filter, $q, $offset, $limit);
|
$gres = yield $this->fetchParticipantsCache($channel, $filter, $q, $offset, $limit);
|
||||||
} else {
|
} else {
|
||||||
yield from $this->storeParticipantsCache($gres, $channel, $filter, $q, $offset, $limit);
|
$this->storeParticipantsCache($gres, $channel, $filter, $q, $offset, $limit);
|
||||||
}
|
}
|
||||||
if ($last_count !== -1 && $last_count !== $gres['count']) {
|
if ($last_count !== -1 && $last_count !== $gres['count']) {
|
||||||
$has_more = true;
|
$has_more = true;
|
||||||
@ -1037,11 +1071,25 @@ trait PeerHandler
|
|||||||
}
|
}
|
||||||
return $has_more;
|
return $has_more;
|
||||||
}
|
}
|
||||||
private function fetchParticipantsCache($channel, $filter, $q, $offset, $limit): \Generator
|
/**
|
||||||
|
* Key for participatns cache.
|
||||||
|
*
|
||||||
|
* @param integer $channelId
|
||||||
|
* @param string $filter
|
||||||
|
* @param string $q
|
||||||
|
* @param integer $offset
|
||||||
|
* @param integer $limit
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function participantsKey(int $channelId, string $filter, string $q, int $offset, int $limit): string
|
||||||
{
|
{
|
||||||
return (yield $this->channel_participants[$channel['channel_id']])[$filter][$q][$offset][$limit];
|
return "$channelId'$filter'$q'$offset'$limit";
|
||||||
}
|
}
|
||||||
private function storeParticipantsCache($gres, $channel, $filter, $q, $offset, $limit): \Generator
|
private function fetchParticipantsCache($channel, $filter, $q, $offset, $limit): Promise
|
||||||
|
{
|
||||||
|
return $this->channelParticipants[$this->participantsKey($channel['channel_id'], $filter, $q, $offset, $limit)];
|
||||||
|
}
|
||||||
|
private function storeParticipantsCache($gres, $channel, $filter, $q, $offset, $limit)
|
||||||
{
|
{
|
||||||
unset($gres['users']);
|
unset($gres['users']);
|
||||||
$ids = [];
|
$ids = [];
|
||||||
@ -1050,13 +1098,11 @@ trait PeerHandler
|
|||||||
}
|
}
|
||||||
\sort($ids, SORT_NUMERIC);
|
\sort($ids, SORT_NUMERIC);
|
||||||
$gres['hash'] = \danog\MadelineProto\Tools::genVectorHash($ids);
|
$gres['hash'] = \danog\MadelineProto\Tools::genVectorHash($ids);
|
||||||
$participant = yield $this->channel_participants[$channel['channel_id']];
|
$this->channelParticipants[$this->participantsKey($channel['channel_id'], $filter, $q, $offset, $limit)] = $gres;
|
||||||
$participant[$filter][$q][$offset][$limit] = $gres;
|
|
||||||
$this->channel_participants[$channel['channel_id']] = $participant;
|
|
||||||
}
|
}
|
||||||
private function getParticipantsHash($channel, $filter, $q, $offset, $limit): \Generator
|
private function getParticipantsHash($channel, $filter, $q, $offset, $limit): \Generator
|
||||||
{
|
{
|
||||||
return (yield $this->channel_participants[$channel['channel_id']])[$filter][$q][$offset][$limit]['hash'] ?? 0;
|
return (yield $this->channelParticipants[$this->participantsKey($channel['channel_id'], $filter, $q, $offset, $limit)])['hash'] ?? 0;
|
||||||
}
|
}
|
||||||
private function storeDb($res, $force = false): \Generator
|
private function storeDb($res, $force = false): \Generator
|
||||||
{
|
{
|
||||||
@ -1107,7 +1153,7 @@ trait PeerHandler
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$this->caching_simple_username[$username] = true;
|
$this->caching_simple_username[$username] = true;
|
||||||
$res = yield from $this->methodCallAsyncRead('contacts.resolveUsername', ['username' => $username], ['datacenter' => $this->datacenter->curdc]);
|
$res = yield from $this->methodCallAsyncRead('contacts.resolveUsername', ['username' => $username]);
|
||||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||||
$this->logger->logger('Username resolution failed with error '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
|
$this->logger->logger('Username resolution failed with error '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
|
||||||
if (\strpos($e->rpc, 'FLOOD_WAIT_') === 0 || $e->rpc === 'AUTH_KEY_UNREGISTERED' || $e->rpc === 'USERNAME_INVALID') {
|
if (\strpos($e->rpc, 'FLOOD_WAIT_') === 0 || $e->rpc === 'AUTH_KEY_UNREGISTERED' || $e->rpc === 'USERNAME_INVALID') {
|
||||||
|
@ -200,7 +200,7 @@ class ReferenceDatabase implements TLCallback
|
|||||||
throw new \danog\MadelineProto\Exception("Unknown origin type provided: {$type}");
|
throw new \danog\MadelineProto\Exception("Unknown origin type provided: {$type}");
|
||||||
}
|
}
|
||||||
$originContext = self::CONSTRUCTOR_CONTEXT[$type];
|
$originContext = self::CONSTRUCTOR_CONTEXT[$type];
|
||||||
$this->API->logger->logger("Adding origin context {$originContext} for {$type}!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
//$this->API->logger->logger("Adding origin context {$originContext} for {$type}!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||||
$this->cacheContexts[] = $originContext;
|
$this->cacheContexts[] = $originContext;
|
||||||
}
|
}
|
||||||
public function addOrigin(array $data = []): \Generator
|
public function addOrigin(array $data = []): \Generator
|
||||||
@ -211,7 +211,7 @@ class ReferenceDatabase implements TLCallback
|
|||||||
}
|
}
|
||||||
$originType = \array_pop($this->cacheContexts);
|
$originType = \array_pop($this->cacheContexts);
|
||||||
if (!isset($this->cache[$key])) {
|
if (!isset($this->cache[$key])) {
|
||||||
$this->API->logger->logger("Removing origin context {$originType} for {$data['_']}, nothing in the reference cache!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
//$this->API->logger->logger("Removing origin context {$originType} for {$data['_']}, nothing in the reference cache!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$cache = $this->cache[$key];
|
$cache = $this->cache[$key];
|
||||||
@ -412,7 +412,7 @@ class ReferenceDatabase implements TLCallback
|
|||||||
\ksort($locationValue['origins']);
|
\ksort($locationValue['origins']);
|
||||||
$this->db[$location] = $locationValue;
|
$this->db[$location] = $locationValue;
|
||||||
$count = 0;
|
$count = 0;
|
||||||
foreach ((yield $this->db[$location]['origins']) as $originType => &$origin) {
|
foreach ((yield $this->db[$location])['origins'] as $originType => &$origin) {
|
||||||
$count++;
|
$count++;
|
||||||
$this->API->logger->logger("Try {$count} refreshing file reference with origin type {$originType}", \danog\MadelineProto\Logger::VERBOSE);
|
$this->API->logger->logger("Try {$count} refreshing file reference with origin type {$originType}", \danog\MadelineProto\Logger::VERBOSE);
|
||||||
switch ($originType) {
|
switch ($originType) {
|
||||||
@ -492,7 +492,7 @@ class ReferenceDatabase implements TLCallback
|
|||||||
}
|
}
|
||||||
return (yield $this->db[$locationString])['reference'];
|
return (yield $this->db[$locationString])['reference'];
|
||||||
}
|
}
|
||||||
private function serializeLocation(int $locationType, array $location): string
|
private static function serializeLocation(int $locationType, array $location): string
|
||||||
{
|
{
|
||||||
switch ($locationType) {
|
switch ($locationType) {
|
||||||
case self::DOCUMENT_LOCATION:
|
case self::DOCUMENT_LOCATION:
|
||||||
|
@ -70,7 +70,7 @@ trait AuthKeyHandler
|
|||||||
$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => false, 'user_id' => $params['admin_id'], 'InputEncryptedChat' => ['_' => 'inputEncryptedChat', 'chat_id' => $params['id'], 'access_hash' => $params['access_hash']], 'in_seq_no_x' => 1, 'out_seq_no_x' => 0, 'in_seq_no' => 0, 'out_seq_no' => 0, 'layer' => 8, 'ttl' => 0, 'ttr' => 100, 'updated' => \time(), 'incoming' => [], 'outgoing' => [], 'created' => \time(), 'rekeying' => [0], 'key_x' => 'from server', 'mtproto' => 1];
|
$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => false, 'user_id' => $params['admin_id'], 'InputEncryptedChat' => ['_' => 'inputEncryptedChat', 'chat_id' => $params['id'], 'access_hash' => $params['access_hash']], 'in_seq_no_x' => 1, 'out_seq_no_x' => 0, 'in_seq_no' => 0, 'out_seq_no' => 0, 'layer' => 8, 'ttl' => 0, 'ttr' => 100, 'updated' => \time(), 'incoming' => [], 'outgoing' => [], 'created' => \time(), 'rekeying' => [0], 'key_x' => 'from server', 'mtproto' => 1];
|
||||||
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
|
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
|
||||||
Crypt::checkG($g_b, $dh_config['p']);
|
Crypt::checkG($g_b, $dh_config['p']);
|
||||||
yield from $this->methodCallAsyncRead('messages.acceptEncryption', ['peer' => $params['id'], 'g_b' => $g_b->toBytes(), 'key_fingerprint' => $key['fingerprint']], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.acceptEncryption', ['peer' => $params['id'], 'g_b' => $g_b->toBytes(), 'key_fingerprint' => $key['fingerprint']]);
|
||||||
yield from $this->notifyLayer($params['id']);
|
yield from $this->notifyLayer($params['id']);
|
||||||
$this->logger->logger('Secret chat '.$params['id'].' accepted successfully!', \danog\MadelineProto\Logger::NOTICE);
|
$this->logger->logger('Secret chat '.$params['id'].' accepted successfully!', \danog\MadelineProto\Logger::NOTICE);
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ trait AuthKeyHandler
|
|||||||
$this->logger->logger('Generating g_a...', \danog\MadelineProto\Logger::VERBOSE);
|
$this->logger->logger('Generating g_a...', \danog\MadelineProto\Logger::VERBOSE);
|
||||||
$g_a = $dh_config['g']->powMod($a, $dh_config['p']);
|
$g_a = $dh_config['g']->powMod($a, $dh_config['p']);
|
||||||
Crypt::checkG($g_a, $dh_config['p']);
|
Crypt::checkG($g_a, $dh_config['p']);
|
||||||
$res = yield from $this->methodCallAsyncRead('messages.requestEncryption', ['user_id' => $user, 'g_a' => $g_a->toBytes()], ['datacenter' => $this->datacenter->curdc]);
|
$res = yield from $this->methodCallAsyncRead('messages.requestEncryption', ['user_id' => $user, 'g_a' => $g_a->toBytes()]);
|
||||||
$this->temp_requested_secret_chats[$res['id']] = $a;
|
$this->temp_requested_secret_chats[$res['id']] = $a;
|
||||||
$this->updaters[UpdateLoop::GENERIC]->resume();
|
$this->updaters[UpdateLoop::GENERIC]->resume();
|
||||||
$this->logger->logger('Secret chat '.$res['id'].' requested successfully!', \danog\MadelineProto\Logger::NOTICE);
|
$this->logger->logger('Secret chat '.$res['id'].' requested successfully!', \danog\MadelineProto\Logger::NOTICE);
|
||||||
@ -134,7 +134,7 @@ trait AuthKeyHandler
|
|||||||
}
|
}
|
||||||
private function notifyLayer($chat): \Generator
|
private function notifyLayer($chat): \Generator
|
||||||
{
|
{
|
||||||
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNotifyLayer', 'layer' => $this->TL->getSecretLayer()]]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNotifyLayer', 'layer' => $this->TL->getSecretLayer()]]]);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Temporary rekeyed secret chats.
|
* Temporary rekeyed secret chats.
|
||||||
@ -164,7 +164,7 @@ trait AuthKeyHandler
|
|||||||
$e = \danog\MadelineProto\Tools::random(8);
|
$e = \danog\MadelineProto\Tools::random(8);
|
||||||
$this->temp_rekeyed_secret_chats[$e] = $a;
|
$this->temp_rekeyed_secret_chats[$e] = $a;
|
||||||
$this->secret_chats[$chat]['rekeying'] = [1, $e];
|
$this->secret_chats[$chat]['rekeying'] = [1, $e];
|
||||||
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionRequestKey', 'g_a' => $g_a->toBytes(), 'exchange_id' => $e]]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionRequestKey', 'g_a' => $g_a->toBytes(), 'exchange_id' => $e]]]);
|
||||||
$this->updaters[UpdateLoop::GENERIC]->resume();
|
$this->updaters[UpdateLoop::GENERIC]->resume();
|
||||||
return $e;
|
return $e;
|
||||||
}
|
}
|
||||||
@ -204,7 +204,7 @@ trait AuthKeyHandler
|
|||||||
$this->secret_chats[$chat]['rekeying'] = [2, $params['exchange_id']];
|
$this->secret_chats[$chat]['rekeying'] = [2, $params['exchange_id']];
|
||||||
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
|
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
|
||||||
Crypt::checkG($g_b, $dh_config['p']);
|
Crypt::checkG($g_b, $dh_config['p']);
|
||||||
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAcceptKey', 'g_b' => $g_b->toBytes(), 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAcceptKey', 'g_b' => $g_b->toBytes(), 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]]);
|
||||||
$this->updaters[UpdateLoop::GENERIC]->resume();
|
$this->updaters[UpdateLoop::GENERIC]->resume();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -230,10 +230,10 @@ trait AuthKeyHandler
|
|||||||
$key['visualization_orig'] = $this->secret_chats[$chat]['key']['visualization_orig'];
|
$key['visualization_orig'] = $this->secret_chats[$chat]['key']['visualization_orig'];
|
||||||
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
|
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
|
||||||
if ($key['fingerprint'] !== $params['key_fingerprint']) {
|
if ($key['fingerprint'] !== $params['key_fingerprint']) {
|
||||||
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]]);
|
||||||
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
|
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
|
||||||
}
|
}
|
||||||
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionCommitKey', 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionCommitKey', 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]]);
|
||||||
unset($this->temp_rekeyed_secret_chats[$params['exchange_id']]);
|
unset($this->temp_rekeyed_secret_chats[$params['exchange_id']]);
|
||||||
$this->secret_chats[$chat]['rekeying'] = [0];
|
$this->secret_chats[$chat]['rekeying'] = [0];
|
||||||
$this->secret_chats[$chat]['old_key'] = $this->secret_chats[$chat]['key'];
|
$this->secret_chats[$chat]['old_key'] = $this->secret_chats[$chat]['key'];
|
||||||
@ -256,7 +256,7 @@ trait AuthKeyHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->temp_rekeyed_secret_chats[$params['exchange_id']]['fingerprint'] !== $params['key_fingerprint']) {
|
if ($this->temp_rekeyed_secret_chats[$params['exchange_id']]['fingerprint'] !== $params['key_fingerprint']) {
|
||||||
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]]);
|
||||||
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
|
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
|
||||||
}
|
}
|
||||||
$this->logger->logger('Completing rekeying of secret chat '.$chat.'...', \danog\MadelineProto\Logger::VERBOSE);
|
$this->logger->logger('Completing rekeying of secret chat '.$chat.'...', \danog\MadelineProto\Logger::VERBOSE);
|
||||||
@ -266,7 +266,7 @@ trait AuthKeyHandler
|
|||||||
$this->secret_chats[$chat]['ttr'] = 100;
|
$this->secret_chats[$chat]['ttr'] = 100;
|
||||||
$this->secret_chats[$chat]['updated'] = \time();
|
$this->secret_chats[$chat]['updated'] = \time();
|
||||||
unset($this->temp_rekeyed_secret_chats[$params['exchange_id']]);
|
unset($this->temp_rekeyed_secret_chats[$params['exchange_id']]);
|
||||||
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNoop']]], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNoop']]]);
|
||||||
$this->logger->logger('Secret chat '.$chat.' rekeyed successfully!', \danog\MadelineProto\Logger::VERBOSE);
|
$this->logger->logger('Secret chat '.$chat.' rekeyed successfully!', \danog\MadelineProto\Logger::VERBOSE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -326,7 +326,7 @@ trait AuthKeyHandler
|
|||||||
unset($this->temp_requested_secret_chats[$chat]);
|
unset($this->temp_requested_secret_chats[$chat]);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
yield from $this->methodCallAsyncRead('messages.discardEncryption', ['chat_id' => $chat], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.discardEncryption', ['chat_id' => $chat]);
|
||||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||||
if ($e->rpc !== 'ENCRYPTION_ALREADY_DECLINED') {
|
if ($e->rpc !== 'ENCRYPTION_ALREADY_DECLINED') {
|
||||||
throw $e;
|
throw $e;
|
||||||
|
@ -115,7 +115,10 @@ trait MessageHandler
|
|||||||
$this->secret_chats[$message['message']['chat_id']]['mtproto'] = 2;
|
$this->secret_chats[$message['message']['chat_id']]['mtproto'] = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$deserialized = yield from $this->TL->deserialize($message_data, ['type' => '']);
|
[$deserialized, $sideEffects] = $this->TL->deserialize($message_data, ['type' => '']);
|
||||||
|
if ($sideEffects) {
|
||||||
|
yield $sideEffects;
|
||||||
|
}
|
||||||
$this->secret_chats[$message['message']['chat_id']]['ttr']--;
|
$this->secret_chats[$message['message']['chat_id']]['ttr']--;
|
||||||
if (($this->secret_chats[$message['message']['chat_id']]['ttr'] <= 0 || \time() - $this->secret_chats[$message['message']['chat_id']]['updated'] > 7 * 24 * 60 * 60) && $this->secret_chats[$message['message']['chat_id']]['rekeying'][0] === 0) {
|
if (($this->secret_chats[$message['message']['chat_id']]['ttr'] <= 0 || \time() - $this->secret_chats[$message['message']['chat_id']]['updated'] > 7 * 24 * 60 * 60) && $this->secret_chats[$message['message']['chat_id']]['rekeying'][0] === 0) {
|
||||||
yield from $this->rekey($message['message']['chat_id']);
|
yield from $this->rekey($message['message']['chat_id']);
|
||||||
|
@ -63,7 +63,7 @@ trait ResponseHandler
|
|||||||
foreach ($this->secret_chats[$update['message']['chat_id']]['outgoing'] as $seq => $message) {
|
foreach ($this->secret_chats[$update['message']['chat_id']]['outgoing'] as $seq => $message) {
|
||||||
if ($seq >= $update['message']['decrypted_message']['action']['start_seq_no'] && $seq <= $update['message']['decrypted_message']['action']['end_seq_no']) {
|
if ($seq >= $update['message']['decrypted_message']['action']['start_seq_no'] && $seq <= $update['message']['decrypted_message']['action']['end_seq_no']) {
|
||||||
//throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['resending_unsupported']);
|
//throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['resending_unsupported']);
|
||||||
yield from $this->methodCallAsyncRead('messages.sendEncrypted', ['peer' => $update['message']['chat_id'], 'message' => $update['message']['decrypted_message']], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('messages.sendEncrypted', ['peer' => $update['message']['chat_id'], 'message' => $update['message']['decrypted_message']]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -21,6 +21,7 @@ namespace danog\MadelineProto\TL;
|
|||||||
|
|
||||||
use Amp\Promise;
|
use Amp\Promise;
|
||||||
use danog\MadelineProto\MTProto;
|
use danog\MadelineProto\MTProto;
|
||||||
|
use danog\MadelineProto\MTProto\OutgoingMessage;
|
||||||
use danog\MadelineProto\Settings\TLSchema;
|
use danog\MadelineProto\Settings\TLSchema;
|
||||||
use danog\MadelineProto\Tools;
|
use danog\MadelineProto\Tools;
|
||||||
|
|
||||||
@ -812,16 +813,17 @@ class TL
|
|||||||
* @param string|resource $stream Stream
|
* @param string|resource $stream Stream
|
||||||
* @param array $type Type identifier
|
* @param array $type Type identifier
|
||||||
*
|
*
|
||||||
* @return \Generator<mixed>
|
* @return array
|
||||||
|
* @psalm-return array{0: mixed, 1: \Amp\Promise}
|
||||||
*/
|
*/
|
||||||
public function deserialize($stream, $type = ['type' => '']): \Generator
|
public function deserialize($stream, $type = ['type' => '']): array
|
||||||
{
|
{
|
||||||
$promises = [];
|
$promises = [];
|
||||||
$result = $this->deserializeInternal($stream, $promises, $type);
|
$result = $this->deserializeInternal($stream, $promises, $type);
|
||||||
if ($promises) {
|
return [
|
||||||
yield $promises;
|
$result,
|
||||||
}
|
$promises ? Tools::all($promises) : null
|
||||||
return $result;
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1009,14 +1011,14 @@ class TL
|
|||||||
if (\in_array($arg['name'], ['peer_tag', 'file_token', 'cdn_key', 'cdn_iv'])) {
|
if (\in_array($arg['name'], ['peer_tag', 'file_token', 'cdn_key', 'cdn_iv'])) {
|
||||||
$arg['type'] = 'string';
|
$arg['type'] = 'string';
|
||||||
}
|
}
|
||||||
if ($x['_'] === 'rpc_result' && $arg['name'] === 'result') {
|
if ($x['_'] === 'rpc_result' && $arg['name'] === 'result' && isset($type['connection']->outgoing_messages[$x['req_msg_id']])) {
|
||||||
if (isset($type['connection']->outgoing_messages[$x['req_msg_id']]['_']) && isset($this->callbacks[TLCallback::METHOD_BEFORE_CALLBACK][$type['connection']->outgoing_messages[$x['req_msg_id']]['_']])) {
|
/** @var OutgoingMessage */
|
||||||
foreach ($this->callbacks[TLCallback::METHOD_BEFORE_CALLBACK][$type['connection']->outgoing_messages[$x['req_msg_id']]['_']] as $callback) {
|
$message = $type['connection']->outgoing_messages[$x['req_msg_id']];
|
||||||
$callback($type['connection']->outgoing_messages[$x['req_msg_id']]['_']);
|
foreach ($this->callbacks[TLCallback::METHOD_BEFORE_CALLBACK][$message->getConstructor()] ?? [] as $callback) {
|
||||||
}
|
$callback($type['connection']->outgoing_messages[$x['req_msg_id']]['_']);
|
||||||
}
|
}
|
||||||
if (isset($type['connection']->outgoing_messages[$x['req_msg_id']]['type']) && \stripos($type['connection']->outgoing_messages[$x['req_msg_id']]['type'], '<') !== false) {
|
if ($message->getType() && \stripos($message->getType(), '<') !== false) {
|
||||||
$arg['subtype'] = \str_replace(['Vector<', '>'], '', $type['connection']->outgoing_messages[$x['req_msg_id']]['type']);
|
$arg['subtype'] = \str_replace(['Vector<', '>'], '', $message->getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isset($type['connection'])) {
|
if (isset($type['connection'])) {
|
||||||
@ -1059,8 +1061,10 @@ class TL
|
|||||||
$promises []= $promise;
|
$promises []= $promise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ($x['_'] === 'rpc_result' && isset($type['connection']->outgoing_messages[$x['req_msg_id']]['_']) && isset($this->callbacks[TLCallback::METHOD_CALLBACK][$type['connection']->outgoing_messages[$x['req_msg_id']]['_']])) {
|
} elseif ($x['_'] === 'rpc_result'
|
||||||
foreach ($this->callbacks[TLCallback::METHOD_CALLBACK][$type['connection']->outgoing_messages[$x['req_msg_id']]['_']] as $callback) {
|
&& isset($type['connection']->outgoing_messages[$x['req_msg_id']])
|
||||||
|
&& isset($this->callbacks[TLCallback::METHOD_CALLBACK][$type['connection']->outgoing_messages[$x['req_msg_id']]->getConstructor()])) {
|
||||||
|
foreach ($this->callbacks[TLCallback::METHOD_CALLBACK][$type['connection']->outgoing_messages[$x['req_msg_id']]->getConstructor()] as $callback) {
|
||||||
$callback($type['connection']->outgoing_messages[$x['req_msg_id']], $x['result']);
|
$callback($type['connection']->outgoing_messages[$x['req_msg_id']], $x['result']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,11 +132,11 @@ class ADNLConnection
|
|||||||
$buffer = yield $this->stream->getReadBuffer($length);
|
$buffer = yield $this->stream->getReadBuffer($length);
|
||||||
if ($length) {
|
if ($length) {
|
||||||
$data = yield $buffer->bufferRead($length);
|
$data = yield $buffer->bufferRead($length);
|
||||||
$data = yield from $this->TL->deserialize($data);
|
$data = $this->TL->deserialize($data)[0];
|
||||||
if ($data['_'] !== 'adnl.message.answer') {
|
if ($data['_'] !== 'adnl.message.answer') {
|
||||||
throw new Exception('Wrong answer type: '.$data['_']);
|
throw new Exception('Wrong answer type: '.$data['_']);
|
||||||
}
|
}
|
||||||
$this->requests[$data['query_id']]->resolve(yield from $this->TL->deserialize((string) $data['answer']));
|
$this->requests[$data['query_id']]->resolve($this->TL->deserialize((string) $data['answer'])[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})());
|
})());
|
||||||
|
@ -89,7 +89,7 @@ class Lite
|
|||||||
$config['_'] = 'liteclient.config.global';
|
$config['_'] = 'liteclient.config.global';
|
||||||
$config = Tools::convertJsonTL($config);
|
$config = Tools::convertJsonTL($config);
|
||||||
$config['validator']['init_block'] = $config['validator']['init_block'] ?? $config['validator']['zero_state'];
|
$config['validator']['init_block'] = $config['validator']['init_block'] ?? $config['validator']['zero_state'];
|
||||||
$this->config = yield from $this->TL->deserialize(yield from $this->TL->serializeObject(['type' => ''], $config, 'cleanup'));
|
[$this->config] = $this->TL->deserialize(yield from $this->TL->serializeObject(['type' => ''], $config, 'cleanup'));
|
||||||
foreach ($this->config['liteservers'] as $lite) {
|
foreach ($this->config['liteservers'] as $lite) {
|
||||||
$this->connections[] = $connection = new ADNLConnection($this->TL);
|
$this->connections[] = $connection = new ADNLConnection($this->TL);
|
||||||
yield from $connection->connect($lite);
|
yield from $connection->connect($lite);
|
||||||
|
@ -108,7 +108,7 @@ trait AuthKeyHandler
|
|||||||
Crypt::checkG($g_a, $dh_config['p']);
|
Crypt::checkG($g_a, $dh_config['p']);
|
||||||
$controller = new \danog\MadelineProto\VoIP(true, $user['user_id'], $this, \danog\MadelineProto\VoIP::CALL_STATE_REQUESTED);
|
$controller = new \danog\MadelineProto\VoIP(true, $user['user_id'], $this, \danog\MadelineProto\VoIP::CALL_STATE_REQUESTED);
|
||||||
$controller->storage = ['a' => $a, 'g_a' => \str_pad($g_a->toBytes(), 256, \chr(0), \STR_PAD_LEFT)];
|
$controller->storage = ['a' => $a, 'g_a' => \str_pad($g_a->toBytes(), 256, \chr(0), \STR_PAD_LEFT)];
|
||||||
$res = yield from $this->methodCallAsyncRead('phone.requestCall', ['user_id' => $user, 'g_a_hash' => \hash('sha256', $g_a->toBytes(), true), 'protocol' => ['_' => 'phoneCallProtocol', 'udp_p2p' => true, 'udp_reflector' => true, 'min_layer' => 65, 'max_layer' => \danog\MadelineProto\VoIP::getConnectionMaxLayer()]], ['datacenter' => $this->datacenter->curdc]);
|
$res = yield from $this->methodCallAsyncRead('phone.requestCall', ['user_id' => $user, 'g_a_hash' => \hash('sha256', $g_a->toBytes(), true), 'protocol' => ['_' => 'phoneCallProtocol', 'udp_p2p' => true, 'udp_reflector' => true, 'min_layer' => 65, 'max_layer' => \danog\MadelineProto\VoIP::getConnectionMaxLayer()]]);
|
||||||
$controller->setCall($res['phone_call']);
|
$controller->setCall($res['phone_call']);
|
||||||
$this->calls[$res['phone_call']['id']] = $controller;
|
$this->calls[$res['phone_call']['id']] = $controller;
|
||||||
yield $this->updaters[UpdateLoop::GENERIC]->resume();
|
yield $this->updaters[UpdateLoop::GENERIC]->resume();
|
||||||
@ -137,7 +137,7 @@ trait AuthKeyHandler
|
|||||||
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
|
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
|
||||||
Crypt::checkG($g_b, $dh_config['p']);
|
Crypt::checkG($g_b, $dh_config['p']);
|
||||||
try {
|
try {
|
||||||
$res = yield from $this->methodCallAsyncRead('phone.acceptCall', ['peer' => $call, 'g_b' => $g_b->toBytes(), 'protocol' => ['_' => 'phoneCallProtocol', 'udp_reflector' => true, 'udp_p2p' => true, 'min_layer' => 65, 'max_layer' => \danog\MadelineProto\VoIP::getConnectionMaxLayer()]], ['datacenter' => $this->datacenter->curdc]);
|
$res = yield from $this->methodCallAsyncRead('phone.acceptCall', ['peer' => $call, 'g_b' => $g_b->toBytes(), 'protocol' => ['_' => 'phoneCallProtocol', 'udp_reflector' => true, 'udp_p2p' => true, 'min_layer' => 65, 'max_layer' => \danog\MadelineProto\VoIP::getConnectionMaxLayer()]]);
|
||||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||||
if ($e->rpc === 'CALL_ALREADY_ACCEPTED') {
|
if ($e->rpc === 'CALL_ALREADY_ACCEPTED') {
|
||||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['call_already_accepted'], $call['id']));
|
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['call_already_accepted'], $call['id']));
|
||||||
@ -176,7 +176,7 @@ trait AuthKeyHandler
|
|||||||
Crypt::checkG($params['g_b'], $dh_config['p']);
|
Crypt::checkG($params['g_b'], $dh_config['p']);
|
||||||
$key = \str_pad($params['g_b']->powMod($this->calls[$params['id']]->storage['a'], $dh_config['p'])->toBytes(), 256, \chr(0), \STR_PAD_LEFT);
|
$key = \str_pad($params['g_b']->powMod($this->calls[$params['id']]->storage['a'], $dh_config['p'])->toBytes(), 256, \chr(0), \STR_PAD_LEFT);
|
||||||
try {
|
try {
|
||||||
$res = (yield from $this->methodCallAsyncRead('phone.confirmCall', ['key_fingerprint' => \substr(\sha1($key, true), -8), 'peer' => ['id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputPhoneCall'], 'g_a' => $this->calls[$params['id']]->storage['g_a'], 'protocol' => ['_' => 'phoneCallProtocol', 'udp_reflector' => true, 'min_layer' => 65, 'max_layer' => \danog\MadelineProto\VoIP::getConnectionMaxLayer()]], ['datacenter' => $this->datacenter->curdc]))['phone_call'];
|
$res = (yield from $this->methodCallAsyncRead('phone.confirmCall', ['key_fingerprint' => \substr(\sha1($key, true), -8), 'peer' => ['id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputPhoneCall'], 'g_a' => $this->calls[$params['id']]->storage['g_a'], 'protocol' => ['_' => 'phoneCallProtocol', 'udp_reflector' => true, 'min_layer' => 65, 'max_layer' => \danog\MadelineProto\VoIP::getConnectionMaxLayer()]]))['phone_call'];
|
||||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||||
if ($e->rpc === 'CALL_ALREADY_ACCEPTED') {
|
if ($e->rpc === 'CALL_ALREADY_ACCEPTED') {
|
||||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['call_already_accepted'], $params['id']));
|
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['call_already_accepted'], $params['id']));
|
||||||
@ -293,7 +293,7 @@ trait AuthKeyHandler
|
|||||||
}
|
}
|
||||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['call_discarding'], $call['id']), \danog\MadelineProto\Logger::VERBOSE);
|
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['call_discarding'], $call['id']), \danog\MadelineProto\Logger::VERBOSE);
|
||||||
try {
|
try {
|
||||||
$res = yield from $this->methodCallAsyncRead('phone.discardCall', ['peer' => $call, 'duration' => \time() - $this->calls[$call['id']]->whenCreated(), 'connection_id' => $this->calls[$call['id']]->getPreferredRelayID(), 'reason' => $reason], ['datacenter' => $this->datacenter->curdc]);
|
$res = yield from $this->methodCallAsyncRead('phone.discardCall', ['peer' => $call, 'duration' => \time() - $this->calls[$call['id']]->whenCreated(), 'connection_id' => $this->calls[$call['id']]->getPreferredRelayID(), 'reason' => $reason]);
|
||||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||||
if (!\in_array($e->rpc, ['CALL_ALREADY_DECLINED', 'CALL_ALREADY_ACCEPTED'])) {
|
if (!\in_array($e->rpc, ['CALL_ALREADY_DECLINED', 'CALL_ALREADY_ACCEPTED'])) {
|
||||||
throw $e;
|
throw $e;
|
||||||
@ -301,11 +301,11 @@ trait AuthKeyHandler
|
|||||||
}
|
}
|
||||||
if (!empty($rating)) {
|
if (!empty($rating)) {
|
||||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['call_set_rating'], $call['id']), \danog\MadelineProto\Logger::VERBOSE);
|
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['call_set_rating'], $call['id']), \danog\MadelineProto\Logger::VERBOSE);
|
||||||
yield from $this->methodCallAsyncRead('phone.setCallRating', ['peer' => $call, 'rating' => $rating['rating'], 'comment' => $rating['comment']], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('phone.setCallRating', ['peer' => $call, 'rating' => $rating['rating'], 'comment' => $rating['comment']]);
|
||||||
}
|
}
|
||||||
if ($need_debug && isset($this->calls[$call['id']])) {
|
if ($need_debug && isset($this->calls[$call['id']])) {
|
||||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['call_debug_saving'], $call['id']), \danog\MadelineProto\Logger::VERBOSE);
|
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['call_debug_saving'], $call['id']), \danog\MadelineProto\Logger::VERBOSE);
|
||||||
yield from $this->methodCallAsyncRead('phone.saveCallDebug', ['peer' => $call, 'debug' => $this->calls[$call['id']]->getDebugLog()], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('phone.saveCallDebug', ['peer' => $call, 'debug' => $this->calls[$call['id']]->getDebugLog()]);
|
||||||
}
|
}
|
||||||
$update = ['_' => 'updatePhoneCall', 'phone_call' => $this->calls[$call['id']]];
|
$update = ['_' => 'updatePhoneCall', 'phone_call' => $this->calls[$call['id']]];
|
||||||
$this->updates[$this->updates_key++] = $update;
|
$this->updates[$this->updates_key++] = $update;
|
||||||
|
@ -34,7 +34,7 @@ trait TOS
|
|||||||
if ($this->authorized === self::LOGGED_IN && !$this->authorization['user']['bot']) {
|
if ($this->authorized === self::LOGGED_IN && !$this->authorization['user']['bot']) {
|
||||||
if ($this->tos['expires'] < \time()) {
|
if ($this->tos['expires'] < \time()) {
|
||||||
$this->logger->logger('Fetching TOS...');
|
$this->logger->logger('Fetching TOS...');
|
||||||
$this->tos = yield from $this->methodCallAsyncRead('help.getTermsOfServiceUpdate', [], ['datacenter' => $this->datacenter->curdc]);
|
$this->tos = yield from $this->methodCallAsyncRead('help.getTermsOfServiceUpdate', []);
|
||||||
$this->tos['accepted'] = $this->tos['_'] === 'help.termsOfServiceUpdateEmpty';
|
$this->tos['accepted'] = $this->tos['_'] === 'help.termsOfServiceUpdateEmpty';
|
||||||
}
|
}
|
||||||
if (!$this->tos['accepted']) {
|
if (!$this->tos['accepted']) {
|
||||||
@ -55,7 +55,7 @@ trait TOS
|
|||||||
*/
|
*/
|
||||||
public function acceptTos(): \Generator
|
public function acceptTos(): \Generator
|
||||||
{
|
{
|
||||||
$this->tos['accepted'] = yield from $this->methodCallAsyncRead('help.acceptTermsOfService', ['id' => $this->tos['terms_of_service']['id']], ['datacenter' => $this->datacenter->curdc]);
|
$this->tos['accepted'] = yield from $this->methodCallAsyncRead('help.acceptTermsOfService', ['id' => $this->tos['terms_of_service']['id']]);
|
||||||
if ($this->tos['accepted']) {
|
if ($this->tos['accepted']) {
|
||||||
$this->logger->logger('TOS accepted successfully');
|
$this->logger->logger('TOS accepted successfully');
|
||||||
} else {
|
} else {
|
||||||
@ -71,7 +71,7 @@ trait TOS
|
|||||||
*/
|
*/
|
||||||
public function declineTos(): \Generator
|
public function declineTos(): \Generator
|
||||||
{
|
{
|
||||||
yield from $this->methodCallAsyncRead('account.deleteAccount', ['reason' => 'Decline ToS update'], ['datacenter' => $this->datacenter->curdc]);
|
yield from $this->methodCallAsyncRead('account.deleteAccount', ['reason' => 'Decline ToS update']);
|
||||||
yield from $this->logout();
|
yield from $this->logout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ trait Webhook
|
|||||||
$result = \json_decode($result, true);
|
$result = \json_decode($result, true);
|
||||||
if (\is_array($result) && isset($result['method']) && $result['method'] != '' && \is_string($result['method'])) {
|
if (\is_array($result) && isset($result['method']) && $result['method'] != '' && \is_string($result['method'])) {
|
||||||
try {
|
try {
|
||||||
$this->logger->logger('Reverse webhook command returned', yield from $this->methodCallAsyncRead($result['method'], $result, ['datacenter' => $this->datacenter->curdc]));
|
$this->logger->logger('Reverse webhook command returned', yield from $this->methodCallAsyncRead($result['method'], $result));
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->logger->logger("Reverse webhook command returned: {$e}");
|
$this->logger->logger("Reverse webhook command returned: {$e}");
|
||||||
}
|
}
|
||||||
|
@ -116,4 +116,3 @@ PhpDocBuilder::fromNamespace()
|
|||||||
->setOutput(__DIR__.'/../docs/docs/PHP/')
|
->setOutput(__DIR__.'/../docs/docs/PHP/')
|
||||||
->setImage("https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png")
|
->setImage("https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png")
|
||||||
->run();
|
->run();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user