diff --git a/src/danog/MadelineProto/Loop/Update/SecretFeedLoop.php b/src/danog/MadelineProto/Loop/Update/SecretFeedLoop.php new file mode 100644 index 00000000..5f05f599 --- /dev/null +++ b/src/danog/MadelineProto/Loop/Update/SecretFeedLoop.php @@ -0,0 +1,113 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * + * @link https://docs.madelineproto.xyz MadelineProto documentation + */ + +namespace danog\MadelineProto\Loop\Update; + +use danog\Loop\ResumableSignalLoop; +use danog\MadelineProto\Loop\InternalLoop; +use danog\MadelineProto\MTProto; +use danog\MadelineProto\SecurityException; + +/** + * Secret feed loop. + * + * @author Daniil Gentili + */ +class SecretFeedLoop extends ResumableSignalLoop +{ + use InternalLoop { + __construct as private init; + } + /** + * Incoming secret updates array. + */ + private array $incomingUpdates = []; + /** + * Secret chat ID. + */ + private int $secretId; + /** + * Constructor. + * + * @param MTProto $API API instance + * @param integer $secretId Secret chat ID + */ + public function __construct(MTProto $API, int $secretId) + { + $this->init($API); + $this->secretId = $secretId; + } + /** + * Main loop. + * + * @return \Generator + */ + public function loop(): \Generator + { + $API = $this->API; + while (!$API->hasAllAuth()) { + if (yield $this->waitSignal($this->pause())) { + return; + } + } + while (true) { + while (!$API->hasAllAuth()) { + if (yield $this->waitSignal($this->pause())) { + return; + } + } + if (yield $this->waitSignal($this->pause())) { + return; + } + $API->logger->logger("Resumed {$this}"); + while ($this->incomingUpdates) { + $updates = $this->incomingUpdates; + $this->incomingUpdates = []; + foreach ($updates as $update) { + try { + if (!yield from $API->handleEncryptedUpdate($update)) { + $API->logger->logger("Secret chat deleted, exiting $this..."); + unset($API->secretFeeders[$this->secretId]); + return; + } + } catch (SecurityException $e) { + $API->logger->logger("Secret chat deleted, exiting $this..."); + unset($API->secretFeeders[$this->secretId]); + throw $e; + } + } + $updates = null; + } + } + } + /** + * Feed incoming update to loop. + * + * @param array $update + * @return void + */ + public function feed(array $update): void + { + $this->incomingUpdates []= $update; + } + public function __toString(): string + { + return "secret chat feed loop {$this->secretId}"; + } +} diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index 18f20882..e79055b7 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -34,6 +34,7 @@ use danog\MadelineProto\Db\MemoryArray; use danog\MadelineProto\Ipc\Server; use danog\MadelineProto\Loop\Generic\PeriodicLoopInternal; use danog\MadelineProto\Loop\Update\FeedLoop; +use danog\MadelineProto\Loop\Update\SecretFeedLoop; use danog\MadelineProto\Loop\Update\SeqLoop; use danog\MadelineProto\Loop\Update\UpdateLoop; use danog\MadelineProto\MTProtoTools\CombinedUpdatesState; @@ -394,6 +395,12 @@ class MTProto extends AsyncConstruct implements TLCallback * @var array<\danog\MadelineProto\Loop\Update\FeedLoop> */ public $feeders = []; + /** + * Secret chat feeder loops. + * + * @var array<\danog\MadelineProto\Loop\Update\SecretFeedLoop> + */ + public $secretFeeders = []; /** * Updater loops. * @@ -1563,6 +1570,14 @@ class MTProto extends AsyncConstruct implements TLCallback return; } $this->logger("Starting update system"); + foreach ($this->secret_chats as $id => $chat) { + if (!isset($this->secretFeeders[$id])) { + $this->secretFeeders[$id] = new SecretFeedLoop($this, $id); + } + if ($this->secretFeeders[$id]->start() && isset($this->secretFeeders[$id])) { + $this->secretFeeders[$id]->resume(); + } + } if (!isset($this->seqUpdater)) { $this->seqUpdater = new SeqLoop($this); } diff --git a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php index 051f28b7..f78e8a11 100644 --- a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php @@ -382,7 +382,7 @@ trait ResponseHandler $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); + $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; diff --git a/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php b/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php index 4a90d856..9d4e4f4e 100644 --- a/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php @@ -365,7 +365,12 @@ trait UpdateHandler $this->logger->logger('Applying qts: '.$update['qts'].' over current qts '.$cur_state->qts().', chat id: '.$update['message']['chat_id'], \danog\MadelineProto\Logger::VERBOSE); yield from $this->methodCallAsyncRead('messages.receivedQueue', ['max_qts' => $cur_state->qts($update['qts'])], $this->settings->getDefaultDcParams()); } - yield from $this->handleEncryptedUpdate($update); + if (!isset($this->secret_chats[$update['message']['chat_id']])) { + $this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['secret_chat_skipping'], $update['message']['chat_id'])); + return false; + } + $this->secretFeeders[$update['message']['chat_id']]->feed($update); + $this->secretFeeders[$update['message']['chat_id']]->resume(); return; } /* diff --git a/src/danog/MadelineProto/SecretChats/AuthKeyHandler.php b/src/danog/MadelineProto/SecretChats/AuthKeyHandler.php index bea983b8..92a15341 100644 --- a/src/danog/MadelineProto/SecretChats/AuthKeyHandler.php +++ b/src/danog/MadelineProto/SecretChats/AuthKeyHandler.php @@ -19,6 +19,7 @@ namespace danog\MadelineProto\SecretChats; +use danog\MadelineProto\Loop\Update\SecretFeedLoop; use danog\MadelineProto\Loop\Update\UpdateLoop; use danog\MadelineProto\MTProto; use danog\MadelineProto\MTProtoTools\Crypt; @@ -67,7 +68,34 @@ trait AuthKeyHandler $key['fingerprint'] = \substr(\sha1($key['auth_key'], true), -8); $key['visualization_orig'] = \substr(\sha1($key['auth_key'], true), 16); $key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20); - $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 + ]; + $this->secretFeeders[$params['id']] = new SecretFeedLoop($this, $params['id']); + if ($this->secretFeeders[$params['id']]->start()) { + $this->secretFeeders[$params['id']]->resume(); + } $g_b = $dh_config['g']->powMod($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']]); @@ -129,6 +157,10 @@ trait AuthKeyHandler $key['visualization_orig'] = \substr(\sha1($key['auth_key'], true), 16); $key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20); $this->secret_chats[$params['id']] = ['key' => $key, 'admin' => true, 'user_id' => $params['participant_id'], 'InputEncryptedChat' => ['chat_id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputEncryptedChat'], 'in_seq_no_x' => 0, 'out_seq_no_x' => 1, 'in_seq_no' => 0, 'out_seq_no' => 0, 'layer' => 8, 'ttl' => 0, 'ttr' => 100, 'updated' => \time(), 'incoming' => [], 'outgoing' => [], 'created' => \time(), 'rekeying' => [0], 'key_x' => 'to server', 'mtproto' => 1]; + $this->secretFeeders[$params['id']] = new SecretFeedLoop($this, $params['id']); + if ($this->secretFeeders[$params['id']]->start()) { + $this->secretFeeders[$params['id']]->resume(); + } yield from $this->notifyLayer($params['id']); $this->logger->logger('Secret chat '.$params['id'].' completed successfully!', \danog\MadelineProto\Logger::NOTICE); } diff --git a/src/danog/MadelineProto/SecretChats/MessageHandler.php b/src/danog/MadelineProto/SecretChats/MessageHandler.php index 1f0e74a1..017a709e 100644 --- a/src/danog/MadelineProto/SecretChats/MessageHandler.php +++ b/src/danog/MadelineProto/SecretChats/MessageHandler.php @@ -52,7 +52,8 @@ trait MessageHandler $this->secret_chats[$chat_id]['out_seq_no']++; } $this->secret_chats[$chat_id]['outgoing'][$this->secret_chats[$chat_id]['out_seq_no']] = $message; - $message = (yield from $this->TL->serializeObject(['type' => $constructor = $this->secret_chats[$chat_id]['layer'] === 8 ? 'DecryptedMessage' : 'DecryptedMessageLayer'], $message, $constructor, $this->secret_chats[$chat_id]['layer'])); + $constructor = $this->secret_chats[$chat_id]['layer'] === 8 ? 'DecryptedMessage' : 'DecryptedMessageLayer'; + $message = yield from $this->TL->serializeObject(['type' => $constructor], $message, $constructor, $this->secret_chats[$chat_id]['layer']); $message = \danog\MadelineProto\Tools::packUnsignedInt(\strlen($message)).$message; if ($this->secret_chats[$chat_id]['mtproto'] === 2) { $padding = \danog\MadelineProto\Tools::posmod(-\strlen($message), 16); @@ -70,7 +71,15 @@ trait MessageHandler $message = $this->secret_chats[$chat_id]['key']['fingerprint'].$message_key.Crypt::igeEncrypt($message, $aes_key, $aes_iv); return $message; } - private function handleEncryptedUpdate(array $message): \Generator + /** + * Handle encrypted update. + * + * @internal + * + * @param array $message + * @return \Generator + */ + public function handleEncryptedUpdate(array $message): \Generator { if (!isset($this->secret_chats[$message['message']['chat_id']])) { $this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['secret_chat_skipping'], $message['message']['chat_id'])); @@ -127,6 +136,7 @@ trait MessageHandler $message['message']['decrypted_message'] = $deserialized; $this->secret_chats[$message['message']['chat_id']]['incoming'][$this->secret_chats[$message['message']['chat_id']]['in_seq_no']] = $message['message']; yield from $this->handleDecryptedUpdate($message); + return true; } /** * @return false|string diff --git a/src/danog/MadelineProto/SecretChats/ResponseHandler.php b/src/danog/MadelineProto/SecretChats/ResponseHandler.php index e5d3866d..9621b0a5 100644 --- a/src/danog/MadelineProto/SecretChats/ResponseHandler.php +++ b/src/danog/MadelineProto/SecretChats/ResponseHandler.php @@ -24,73 +24,73 @@ namespace danog\MadelineProto\SecretChats; */ trait ResponseHandler { - private function handleDecryptedUpdate($update): \Generator + private function handleDecryptedUpdate(array $update): \Generator { - // already checked in TL.php - switch ($update['message']['decrypted_message']['_']) { - case 'decryptedMessageService': - switch ($update['message']['decrypted_message']['action']['_']) { - case 'decryptedMessageActionRequestKey': - yield from $this->acceptRekey($update['message']['chat_id'], $update['message']['decrypted_message']['action']); - return; - case 'decryptedMessageActionAcceptKey': - yield from $this->commitRekey($update['message']['chat_id'], $update['message']['decrypted_message']['action']); - return; - case 'decryptedMessageActionCommitKey': - yield from $this->completeRekey($update['message']['chat_id'], $update['message']['decrypted_message']['action']); - return; - case 'decryptedMessageActionNotifyLayer': - $this->secret_chats[$update['message']['chat_id']]['layer'] = $update['message']['decrypted_message']['action']['layer']; - if ($update['message']['decrypted_message']['action']['layer'] >= 17 && \time() - $this->secret_chats[$update['message']['chat_id']]['created'] > 15) { - yield from $this->notifyLayer($update['message']['chat_id']); - } - if ($update['message']['decrypted_message']['action']['layer'] >= 73) { - $this->secret_chats[$update['message']['chat_id']]['mtproto'] = 2; - } - return; - case 'decryptedMessageActionSetMessageTTL': - $this->secret_chats[$update['message']['chat_id']]['ttl'] = $update['message']['decrypted_message']['action']['ttl_seconds']; - yield from $this->saveUpdate($update); - return; - case 'decryptedMessageActionNoop': - return; - case 'decryptedMessageActionResend': - $update['message']['decrypted_message']['action']['start_seq_no'] -= $this->secret_chats[$update['message']['chat_id']]['out_seq_no_x']; - $update['message']['decrypted_message']['action']['end_seq_no'] -= $this->secret_chats[$update['message']['chat_id']]['out_seq_no_x']; - $update['message']['decrypted_message']['action']['start_seq_no'] /= 2; - $update['message']['decrypted_message']['action']['end_seq_no'] /= 2; - $this->logger->logger('Resending messages for secret chat '.$update['message']['chat_id'], \danog\MadelineProto\Logger::WARNING); - 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']) { - //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']]); - } - } - return; - default: - // yield $this->saveUpdate(['_' => 'updateNewDecryptedMessage', 'peer' => $this->secret_chats[$update['message']['chat_id']]['InputEncryptedChat'], 'in_seq_no' => $this->get_in_seq_no($update['message']['chat_id']), 'out_seq_no' => $this->get_out_seq_no($update['message']['chat_id']), 'message' => $update['message']['decrypted_message']]); - yield from $this->saveUpdate($update); - } - break; - case 'decryptedMessage': - yield from $this->saveUpdate($update); - break; - case 'decryptedMessageLayer': - if ((yield from $this->checkSecretOutSeqNo($update['message']['chat_id'], $update['message']['decrypted_message']['out_seq_no'])) && (yield from $this->checkSecretInSeqNo($update['message']['chat_id'], $update['message']['decrypted_message']['in_seq_no']))) { - $this->secret_chats[$update['message']['chat_id']]['in_seq_no']++; - if ($update['message']['decrypted_message']['layer'] >= 17) { - $this->secret_chats[$update['message']['chat_id']]['layer'] = $update['message']['decrypted_message']['layer']; - if ($update['message']['decrypted_message']['layer'] >= 17 && \time() - $this->secret_chats[$update['message']['chat_id']]['created'] > 15) { - yield from $this->notifyLayer($update['message']['chat_id']); + $chatId = $update['message']['chat_id']; + $decryptedMessage = $update['message']['decrypted_message']; + if ($decryptedMessage['_'] === 'decryptedMessage') { + yield from $this->saveUpdate($update); + return; + } + if ($decryptedMessage['_'] === 'decryptedMessageService') { + $action = $decryptedMessage['action']; + switch ($action['_']) { + case 'decryptedMessageActionRequestKey': + yield from $this->acceptRekey($chatId, $action); + return; + case 'decryptedMessageActionAcceptKey': + yield from $this->commitRekey($chatId, $action); + return; + case 'decryptedMessageActionCommitKey': + yield from $this->completeRekey($chatId, $action); + return; + case 'decryptedMessageActionNotifyLayer': + $this->secret_chats[$chatId]['layer'] = $action['layer']; + if ($action['layer'] >= 17 && \time() - $this->secret_chats[$chatId]['created'] > 15) { + yield from $this->notifyLayer($chatId); + } + if ($action['layer'] >= 73) { + $this->secret_chats[$chatId]['mtproto'] = 2; + } + return; + case 'decryptedMessageActionSetMessageTTL': + $this->secret_chats[$chatId]['ttl'] = $action['ttl_seconds']; + yield from $this->saveUpdate($update); + return; + case 'decryptedMessageActionNoop': + return; + case 'decryptedMessageActionResend': + $action['start_seq_no'] -= $this->secret_chats[$chatId]['out_seq_no_x']; + $action['end_seq_no'] -= $this->secret_chats[$chatId]['out_seq_no_x']; + $action['start_seq_no'] /= 2; + $action['end_seq_no'] /= 2; + $this->logger->logger('Resending messages for secret chat '.$chatId, \danog\MadelineProto\Logger::WARNING); + foreach ($this->secret_chats[$chatId]['outgoing'] as $seq => $message) { + if ($seq >= $action['start_seq_no'] && $seq <= $action['end_seq_no']) { + yield from $this->methodCallAsyncRead('messages.sendEncrypted', ['peer' => $chatId, 'message' => $message]); } } - $update['message']['decrypted_message'] = $update['message']['decrypted_message']['message']; - yield from $this->handleDecryptedUpdate($update); - } - break; - default: - throw new \danog\MadelineProto\ResponseException('Unrecognized decrypted message received: '.\var_export($update, true)); - break; + return; + default: + yield from $this->saveUpdate($update); + } + return; } + if ($decryptedMessage['_'] === 'decryptedMessageLayer') { + if ((yield from $this->checkSecretOutSeqNo($chatId, $decryptedMessage['out_seq_no'])) + && (yield from $this->checkSecretInSeqNo($chatId, $decryptedMessage['in_seq_no']))) { + $this->secret_chats[$chatId]['in_seq_no']++; + if ($decryptedMessage['layer'] >= 17) { + $this->secret_chats[$chatId]['layer'] = $decryptedMessage['layer']; + if ($decryptedMessage['layer'] >= 17 && \time() - $this->secret_chats[$chatId]['created'] > 15) { + yield from $this->notifyLayer($chatId); + } + } + $update['message']['decrypted_message'] = $decryptedMessage['message']; + yield from $this->handleDecryptedUpdate($update); + } + return; + } + throw new \danog\MadelineProto\ResponseException('Unrecognized decrypted message received: '.\var_export($update, true)); } } diff --git a/src/danog/MadelineProto/SecretChats/SeqNoHandler.php b/src/danog/MadelineProto/SecretChats/SeqNoHandler.php index b0463590..1c78d5f6 100644 --- a/src/danog/MadelineProto/SecretChats/SeqNoHandler.php +++ b/src/danog/MadelineProto/SecretChats/SeqNoHandler.php @@ -19,6 +19,8 @@ namespace danog\MadelineProto\SecretChats; +use danog\MadelineProto\Logger; + /** * Manages sequence numbers. */ @@ -31,6 +33,7 @@ trait SeqNoHandler foreach ($this->secret_chats[$chat_id]['incoming'] as $message) { if (isset($message['decrypted_message']['in_seq_no'])) { if (($message['decrypted_message']['in_seq_no'] - $this->secret_chats[$chat_id]['out_seq_no_x']) / 2 < $last) { + $this->logger->logger("Discarding secret chat $chat_id, in_seq_no is not increasing", Logger::LEVEL_FATAL); yield from $this->discardSecretChat($chat_id); throw new \danog\MadelineProto\SecurityException('in_seq_no is not increasing'); } @@ -38,6 +41,7 @@ trait SeqNoHandler } } if ($seqno > $this->secret_chats[$chat_id]['out_seq_no'] + 1) { + $this->logger->logger("Discarding secret chat $chat_id, in_seq_no is too big", Logger::LEVEL_FATAL); yield from $this->discardSecretChat($chat_id); throw new \danog\MadelineProto\SecurityException('in_seq_no is too big'); } @@ -49,9 +53,11 @@ trait SeqNoHandler $C = 0; foreach ($this->secret_chats[$chat_id]['incoming'] as $message) { if (isset($message['decrypted_message']['out_seq_no']) && $C < $this->secret_chats[$chat_id]['in_seq_no']) { - if (($message['decrypted_message']['out_seq_no'] - $this->secret_chats[$chat_id]['in_seq_no_x']) / 2 !== $C) { + $temp = ($message['decrypted_message']['out_seq_no'] - $this->secret_chats[$chat_id]['in_seq_no_x']) / 2; + if ($temp !== $C) { + $this->logger->logger("Discarding secret chat $chat_id, out_seq_no hole: should be $C, is $temp", Logger::LEVEL_FATAL); yield from $this->discardSecretChat($chat_id); - throw new \danog\MadelineProto\SecurityException('out_seq_no hole: should be '.$C.', is '.($message['decrypted_message']['out_seq_no'] - $this->secret_chats[$chat_id]['in_seq_no_x']) / 2); + throw new \danog\MadelineProto\SecurityException("out_seq_no hole: should be $C, is $temp"); } $C++; } @@ -64,6 +70,7 @@ trait SeqNoHandler } if ($seqno > $C) { // > C+1 + $this->logger->logger("Discarding secret chat $chat_id, out_seq_no gap detected: ($seqno > $C)", Logger::LEVEL_FATAL); yield from $this->discardSecretChat($chat_id); throw new \danog\MadelineProto\SecurityException('WARNING: out_seq_no gap detected ('.$seqno.' > '.$C.')!'); } diff --git a/src/danog/MadelineProto/TL/TL.php b/src/danog/MadelineProto/TL/TL.php index f60e26db..4de07d49 100644 --- a/src/danog/MadelineProto/TL/TL.php +++ b/src/danog/MadelineProto/TL/TL.php @@ -537,7 +537,6 @@ class TL return $object; } } - $auto = false; if ($type['type'] === 'InputMessage' && !\is_array($object)) { $object = ['_' => 'inputMessageID', 'id' => $object]; } elseif (isset($this->callbacks[TLCallback::TYPE_MISMATCH_CALLBACK][$type['type']]) && (!\is_array($object) || isset($object['_']) && $this->constructors->findByPredicate($object['_'])['type'] !== $type['type'])) { @@ -569,7 +568,6 @@ class TL $type['type'] = \substr($type['type'], 1); } if ($predicate === $type['type']) { - //} && !$auto) { $bare = true; } if ($predicate === 'messageEntityMentionName') { @@ -652,7 +650,7 @@ class TL } elseif (isset($arguments['id'])) { $method = 'photos.updateProfilePhoto'; } - } else if ($method === 'messages.uploadMedia') { + } elseif ($method === 'messages.uploadMedia') { if (!isset($arguments['peer']) && !$this->API->getSelf()['bot']) { $arguments['peer'] = 'me'; }