From 47228712a618c6c87af76968d84d58475a14e545 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 9 Feb 2020 16:24:57 +0100 Subject: [PATCH] Improve tests --- composer.json | 4 +- examples/downloadRenameBot.php | 32 ++++- src/danog/MadelineProto/Exception.php | 23 ++- src/danog/MadelineProto/MTProto.php | 7 + .../MTProtoSession/ResponseHandler.php | 2 +- .../MadelineProto/MTProtoTools/Files.php | 14 +- .../MTProtoTools/PeerHandler.php | 35 ++++- src/danog/MadelineProto/RSA.php | 1 - .../MadelineProto/TL/Conversion/BotAPI.php | 59 ++++---- .../TL/Conversion/BotAPIFiles.php | 41 ++++-- tests/danog/MadelineProto/FileIdTest.php | 132 ++++++++++++++++-- 11 files changed, 276 insertions(+), 74 deletions(-) diff --git a/composer.json b/composer.json index a15a6714..2ae17352 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "amphp/http-client-cookies": "^1", "amphp/uri": "^0.1", "danog/tg-file-decoder": "^0.1", - "danog/magicalserializer": "^1.0", + "danog/magicalserializer": "^1.0" }, "require-dev": { "vlucas/phpdotenv": "^3", @@ -41,7 +41,7 @@ "amphp/php-cs-fixer-config": "dev-master", "haydenpierce/class-finder": "^0.4", "amphp/http-server": "dev-master", - "amphp/http": "^1.6" + "amphp/http": "^1.6", "amphp/websocket-client": "dev-master as 1", "amphp/websocket": "dev-master as 1", "ext-ctype": "*", diff --git a/examples/downloadRenameBot.php b/examples/downloadRenameBot.php index f8f9447a..9e0daac1 100755 --- a/examples/downloadRenameBot.php +++ b/examples/downloadRenameBot.php @@ -19,6 +19,8 @@ * @link https://docs.madelineproto.xyz MadelineProto documentation */ +use Amp\Http\Server\HttpServer; +use danog\MadelineProto\API; use danog\MadelineProto\Logger; use danog\MadelineProto\RPCErrorException; use League\Uri\Contracts\UriException; @@ -47,17 +49,32 @@ class EventHandler extends \danog\MadelineProto\EventHandler "Max 1.5GB, parallel upload and download powered by @MadelineProto."; const ADMIN = 'danogentili'; + /** + * Whether to allow uploads. + */ + private $UPLOAD; + /** * Array of media objects. * * @var array */ private $states = []; + /** + * Constructor. + * + * @param API $API API + */ + public function __construct($API) + { + $this->UPLOAD = \class_exists(HttpServer::class); + parent::__construct($API); + } public function onUpdateNewChannelMessage($update) { //yield $this->onUpdateNewMessage($update); } - public function report($message) + public function report(string $message) { try { $this->messages->sendMessage(['peer' => self::ADMIN, 'message' => $message]); @@ -79,10 +96,21 @@ class EventHandler extends \danog\MadelineProto\EventHandler $peerId = $peer['bot_api_id']; $messageId = $update['message']['id']; + if ($this->UPLOAD && $update['message']['message'] === '/getUrl') { + yield $this->messages->sendMessage(['peer' => $peerId, 'message' => 'Give me a file: ', 'reply_to_msg_id' => $messageId]); + $this->states[$peerId] = $this->UPLOAD; + } if ($update['message']['message'] === '/start') { return $this->messages->sendMessage(['peer' => $peerId, 'message' => self::START, 'parse_mode' => 'Markdown', 'reply_to_msg_id' => $messageId]); } if (isset($update['message']['media']['_']) && $update['message']['media']['_'] !== 'messageMediaWebPage') { + if ($this->UPLOAD && ($this->states[$peerId] ?? false) === $this->UPLOAD) { + $media = yield $this->getDownloadInfo($this->states[$peerId]); + unset($media['MessageMedia']); + $media = yield + unset($this->states[$peerId]); + return; + } yield $this->messages->sendMessage(['peer' => $peerId, 'message' => 'Give me a new name for this file: ', 'reply_to_msg_id' => $messageId]); $this->states[$peerId] = $update['message']['media']; @@ -184,7 +212,7 @@ $settings = [ ] ], 'upload' => [ - 'allow_automatic_upload' => false, // IMPORTANT: for security reasons, upload by URL will still be allowed + 'allow_automatic_upload' => false // IMPORTANT: for security reasons, upload by URL will still be allowed ], ]; diff --git a/src/danog/MadelineProto/Exception.php b/src/danog/MadelineProto/Exception.php index 97e9d700..50f8a07a 100644 --- a/src/danog/MadelineProto/Exception.php +++ b/src/danog/MadelineProto/Exception.php @@ -25,7 +25,7 @@ class Exception extends \Exception public static $rollbar = true; public function __toString() { - return $this->file === 'MadelineProto' ? $this->message : '\\danog\\MadelineProto\\Exception' . ($this->message !== '' ? ': ' : '') . $this->message . ' in ' . $this->file . ':' . $this->line . PHP_EOL . \danog\MadelineProto\Magic::$revision . PHP_EOL . 'TL Trace:' . PHP_EOL . $this->getTLTrace(); + return $this->file === 'MadelineProto' ? $this->message : '\\danog\\MadelineProto\\Exception'.($this->message !== '' ? ': ' : '').$this->message.' in '.$this->file.':'.$this->line.PHP_EOL.\danog\MadelineProto\Magic::$revision.PHP_EOL.'TL Trace:'.PHP_EOL.$this->getTLTrace(); } public function __construct($message = null, $code = 0, self $previous = null, $file = null, $line = null) { @@ -37,8 +37,10 @@ class Exception extends \Exception $this->line = $line; } parent::__construct($message, $code, $previous); - if (\strpos($message, 'socket_accept') === false) { - \danog\MadelineProto\Logger::log($message . ' in ' . \basename($this->file) . ':' . $this->line, \danog\MadelineProto\Logger::FATAL_ERROR); + if (\strpos($message, 'socket_accept') === false + && !\in_array(\basename($this->file), ['PKCS8.php', 'PSS.php']) + ) { + \danog\MadelineProto\Logger::log($message.' in '.\basename($this->file).':'.$this->line, \danog\MadelineProto\Logger::FATAL_ERROR); } if (\in_array($message, ['The session is corrupted!', 'Re-executing query...', 'I had to recreate the temporary authorization key', 'This peer is not present in the internal peer database', "Couldn't get response", 'Chat forbidden', 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.', 'File does not exist', 'Please install this fork of phpseclib: https://github.com/danog/tgseclib'])) { return; @@ -50,17 +52,24 @@ class Exception extends \Exception \Rollbar\Rollbar::log(\Rollbar\Payload\Level::error(), $this, \debug_backtrace(0)); } } - public static function extension(string $extensionName) + /** + * Complain about missing extensions + * + * @param string $extensionName Extension name + * + * @return self + */ + public static function extension(string $extensionName): self { - $additional = 'Try running sudo apt-get install php' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '-' . $extensionName . '.'; + $additional = 'Try running sudo apt-get install php'.PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'-'.$extensionName.'.'; if ($extensionName === 'libtgvoip') { $additional = 'Follow the instructions @ https://voip.madelineproto.xyz to install it.'; } elseif ($extensionName === 'prime') { $additional = 'Follow the instructions @ https://prime.madelineproto.xyz to install it.'; } - $message = 'MadelineProto requires the ' . $extensionName . ' extension to run. ' . $additional; + $message = 'MadelineProto requires the '.$extensionName.' extension to run. '.$additional; if (PHP_SAPI !== 'cli') { - echo $message . '
'; + echo $message.'
'; } $file = 'MadelineProto'; $line = 1; diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index 6824bd37..a25e720e 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -194,6 +194,13 @@ class MTProto extends AsyncConstruct implements TLCallback 'msg_resend_ans_req', ]; const DEFAULT_GETUPDATES_PARAMS = ['offset' => 0, 'limit' => null, 'timeout' => 0]; + + + const POWERED_BY = "

Powered by MadelineProto

"; + const NO_CACHE = [ + 'Cache-Control' => ['no-store, no-cache, must-revalidate, max-age=0', 'post-check=0, pre-check=0'], + 'Pragma' => 'no-cache' + ]; /** * Instance of wrapper API. * diff --git a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php index 76b21e75..80755c71 100644 --- a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php @@ -513,7 +513,7 @@ trait ResponseHandler $r = isset($response['_']) ? $response['_'] : \json_encode($response); $this->logger->logger("Deferred: sent {$r} to deferred", Logger::ULTRA_VERBOSE); if ($botAPI) { - $response = (yield from $this->MTProtoToBotAPI($response)); + $response = (yield from $this->API->MTProtoToBotAPI($response)); } if (isset($this->outgoing_messages[$request_id]['promise'])) { // This should not happen but happens, should debug diff --git a/src/danog/MadelineProto/MTProtoTools/Files.php b/src/danog/MadelineProto/MTProtoTools/Files.php index 85e90fda..e306e69b 100644 --- a/src/danog/MadelineProto/MTProtoTools/Files.php +++ b/src/danog/MadelineProto/MTProtoTools/Files.php @@ -549,9 +549,8 @@ trait Files $constructor = $constructor['MessageMedia']; } elseif (isset($constructor['InputMedia'])) { return $constructor; - } else { - $constructor = (yield from $this->getPwrChat($constructor['Chat'] ?? $constructor['User'])); - $constructor = $constructor['photo']; + } else if (isset($constructor['Chat']) || isset($constructor['User'])) { + throw new Exception("Chat photo file IDs can't be reused to resend chat photos, please use getPwrChat()['photo'], instead"); } } switch ($constructor['_']) { @@ -800,9 +799,6 @@ trait Files throw new \danog\MadelineProto\Exception('Invalid constructor provided: '.$messageMedia['_']); } } - - private const POWERED_BY = "

Powered by MadelineProto

"; - /** * Download file to browser. * @@ -841,7 +837,7 @@ trait Files \header("$key: $subValue"); } } - http_response_code($result['code']); + \http_response_code($result['code']); if (!\in_array($result['code'], [Status::OK, Status::PARTIAL_CONTENT])) { yield Tools::echo(self::getExplanation($result['code'])); @@ -913,10 +909,6 @@ trait Files $body .= ""; return $body; } - private const NO_CACHE = [ - 'Cache-Control' => ['no-store, no-cache, must-revalidate, max-age=0', 'post-check=0, pre-check=0'], - 'Pragma' => 'no-cache' - ]; /** * Parse headers. * diff --git a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php index c0ecca79..eb1f1bda 100644 --- a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php @@ -20,6 +20,11 @@ namespace danog\MadelineProto\MTProtoTools; use Amp\Http\Client\Request; +use danog\Decoder\FileId; +use danog\Decoder\PhotoSizeSource\PhotoSizeSourceDialogPhoto; + +use const danog\Decoder\PHOTO; +use const danog\Decoder\PROFILE_PHOTO; /** * Manages peers. @@ -705,7 +710,7 @@ trait PeerHandler * * @return \Generator Chat object */ - public function getPwrChat($id, $fullfetch = true, $send = true): \Generator + public function getPwrChat($id, bool $fullfetch = true, bool $send = true): \Generator { $full = $fullfetch ? yield from $this->getFullInfo($id) : (yield from $this->getInfo($id)); $res = ['id' => $full['bot_api_id'], 'type' => $full['type']]; @@ -837,6 +842,34 @@ trait PeerHandler if ($fullfetch || $send) { $this->storeDb($res); } + if (isset($res['photo'])) { + $photo = []; + foreach ([ + 'small' => $res['photo']['sizes'][0], + 'big' => end($res['photo']['sizes']), + ] as $type => $size) { + $fileId = new FileId; + $fileId->setId($res['photo']['id'] ?? 0); + $fileId->setAccessHash($res['photo']['access_hash'] ?? 0); + $fileId->setFileReference($res['photo']['file_reference'] ?? ''); + $fileId->setDcId($res['photo']['dc_id']); + $fileId->setType(PROFILE_PHOTO); + + $fileId->setLocalId($size['location']['local_id']); + $fileId->setVolumeId($size['location']['volume_id']); + + $photoSize = new PhotoSizeSourceDialogPhoto; + $photoSize->setDialogId($res['id']); + $photoSize->setDialogPhotoSmall($type === 'small'); + $photoSize->setDialogAccessHash($res['access_hash'] ?? 0); + + $fileId->setPhotoSizeSource($photoSize); + + $photo[$type.'_file_id'] = (string) $fileId; + $photo[$type.'_file_unique_id'] = $fileId->getUniqueBotAPI(); + } + $res['photo'] += $photo; + } return $res; } private function recurseAlphabetSearchParticipants($channel, $filter, $q, $total_count, &$res): \Generator diff --git a/src/danog/MadelineProto/RSA.php b/src/danog/MadelineProto/RSA.php index c2d1139a..dc43338e 100644 --- a/src/danog/MadelineProto/RSA.php +++ b/src/danog/MadelineProto/RSA.php @@ -83,7 +83,6 @@ class RSA */ public function encrypt($data): string { - \danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['rsa_encrypting'], Logger::VERBOSE); return (new \tgseclib\Math\BigInteger((string) $data, 256))->powMod($this->e, $this->n)->toBytes(); } } diff --git a/src/danog/MadelineProto/TL/Conversion/BotAPI.php b/src/danog/MadelineProto/TL/Conversion/BotAPI.php index 0810bfd7..86836a70 100644 --- a/src/danog/MadelineProto/TL/Conversion/BotAPI.php +++ b/src/danog/MadelineProto/TL/Conversion/BotAPI.php @@ -19,8 +19,11 @@ namespace danog\MadelineProto\TL\Conversion; +use danog\Decoder\FileId; use danog\MadelineProto\Logger; +use const danog\Decoder\TYPES_IDS; + trait BotAPI { private function htmlEntityDecode(string $stuff): string @@ -173,17 +176,16 @@ trait BotAPI /** * Convert MTProto parameters to bot API parameters. * - * @param array $data Data - * @param array $sent_arguments Sent arguments + * @param array $data Data * * @return \Generator */ - public function MTProtoToBotAPI(array $data, array $sent_arguments = []): \Generator + public function MTProtoToBotAPI(array $data): \Generator { $newd = []; if (!isset($data['_'])) { foreach ($data as $key => $element) { - $newd[$key] = (yield from $this->MTProtoToBotAPI($element, $sent_arguments)); + $newd[$key] = (yield from $this->MTProtoToBotAPI($element)); } return $newd; } @@ -191,18 +193,20 @@ trait BotAPI case 'updateShortSentMessage': $newd['message_id'] = $data['id']; $newd['date'] = $data['date']; - $newd['text'] = $sent_arguments['message']; + $newd['text'] = $data['request']['message']; if ($data['out']) { $newd['from'] = (yield from $this->getPwrChat($this->authorization['user'])); } - $newd['chat'] = (yield from $this->getPwrChat($sent_arguments['peer'])); + $newd['chat'] = yield from $this->getPwrChat($data['request']['peer']); if (isset($data['entities'])) { - $newd['entities'] = (yield from $this->MTProtoToBotAPI($data['entities'], $sent_arguments)); + $newd['entities'] = yield from $this->MTProtoToBotAPI($data['entities']); } if (isset($data['media'])) { - $newd = \array_merge($newd, yield from $this->MTProtoToBotAPI($data['media'], $sent_arguments)); + $newd += yield from $this->MTProtoToBotAPI($data['media']); } return $newd; + case 'updates': + $data = array_values(array_filter($data['updates'], fn(array $update) => $update['_'] !== 'updateMessageID'))[0]; case 'updateNewChannelMessage': case 'updateNewMessage': return yield from $this->MTProtoToBotAPI($data['message']); @@ -217,7 +221,7 @@ trait BotAPI } $newd['chat'] = (yield from $this->getPwrChat($data['to_id'])); if (isset($data['entities'])) { - $newd['entities'] = (yield from $this->MTProtoToBotAPI($data['entities'], $sent_arguments)); + $newd['entities'] = (yield from $this->MTProtoToBotAPI($data['entities'])); } if (isset($data['views'])) { $newd['views'] = $data['views']; @@ -241,7 +245,7 @@ trait BotAPI $newd['forward_from_message_id'] = $data['fwd_from']['channel_post']; } if (isset($data['media'])) { - $newd = \array_merge($newd, yield from $this->MTProtoToBotAPI($data['media'], $sent_arguments)); + $newd = \array_merge($newd, yield from $this->MTProtoToBotAPI($data['media'])); } return $newd; case 'messageEntityMention': @@ -297,7 +301,7 @@ trait BotAPI $res['photo'] = []; foreach ($data['photo']['sizes'] as $key => $photo) { if (\in_array($photo['_'], ['photoCachedSize', 'photoSize'])) { - $res['photo'][$key] = (yield from $this->photosizeToBotAPI($photo, $data['photo'])); + $res['photo'][$key] = $this->photosizeToBotAPI($photo, $data['photo']); } } return $res; @@ -307,21 +311,18 @@ trait BotAPI $type_name = 'document'; $res = []; if (isset($data['document']['thumbs']) && $data['document']['thumbs'] && \in_array(\end($data['document']['thumbs'])['_'], ['photoCachedSize', 'photoSize'])) { - $res['thumb'] = (yield from $this->photosizeToBotAPI(\end($data['document']['thumbs']), [], true)); + $res['thumb'] = $this->photosizeToBotAPI(\end($data['document']['thumbs']), $data['document'], true); } foreach ($data['document']['attributes'] as $attribute) { switch ($attribute['_']) { case 'documentAttributeFilename': $pathinfo = \pathinfo($attribute['file_name']); - $res['ext'] = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : ''; + $res['ext'] = isset($pathinfo['extension']) ? '.'.$pathinfo['extension'] : ''; $res['file_name'] = $pathinfo['filename']; break; case 'documentAttributeAudio': $audio = $attribute; - $type_name = 'audio'; - if ($attribute['voice']) { - $type_name = 'voice'; - } + $type_name = $attribute['voice'] ? 'voice' :'audio'; $res['duration'] = $attribute['duration']; if (isset($attribute['performer'])) { $res['performer'] = $attribute['performer']; @@ -344,7 +345,7 @@ trait BotAPI $res['height'] = $attribute['h']; break; case 'documentAttributeAnimated': - $type_name = 'gif'; + $type_name = 'animation'; $res['animated'] = true; break; case 'documentAttributeHasStickers': @@ -364,24 +365,32 @@ trait BotAPI if (isset($audio) && isset($audio['title']) && !isset($res['file_name'])) { $res['file_name'] = $audio['title']; if (isset($audio['performer'])) { - $res['file_name'] .= ' - ' . $audio['performer']; + $res['file_name'] .= ' - '.$audio['performer']; } } if (!isset($res['file_name'])) { $res['file_name'] = $data['document']['access_hash']; } - $res['file_name'] .= '_' . $data['document']['id']; + $res['file_name'] .= '_'.$data['document']['id']; if (isset($res['ext'])) { $res['file_name'] .= $res['ext']; unset($res['ext']); } else { $res['file_name'] .= $this->getExtensionFromMime($data['document']['mime_type']); } - $data['document']['_'] = 'bot_' . $type_name; $res['file_size'] = $data['document']['size']; $res['mime_type'] = $data['document']['mime_type']; - $res['file_id'] = \danog\MadelineProto\Tools::base64urlEncode(\danog\MadelineProto\Tools::rleEncode(yield from $this->TL->serializeObject(['type' => 'File'], $data['document'], 'File') . \chr(2))); - return [$type_name => $res, 'caption' => isset($data['caption']) ? $data['caption'] : '']; + + $fileId = new FileId; + $fileId->setId($data['document']['id']); + $fileId->setAccessHash($data['document']['access_hash']); + $fileId->setFileReference($data['document']['file_reference'] ?? ''); + $fileId->setDcId($data['document']['dc_id']); + $fileId->setType(TYPES_IDS[$type_name]); + + $res['file_id'] = (string) $fileId; + $res['file_unique_id'] = $fileId->getUniqueBotAPI(); + return [$type_name => $res, 'caption' => $data['caption'] ?? '']; default: throw new Exception(\sprintf(\danog\MadelineProto\Lang::$current_lang['botapi_conversion_error'], $data['_'])); } @@ -586,7 +595,7 @@ trait BotAPI $multiple_args = [$multiple_args_base]; $i = 0; foreach ($text_arr as $word) { - if ($this->mbStrlen($multiple_args[$i]['message'] . $word) <= $max_length) { + if ($this->mbStrlen($multiple_args[$i]['message'].$word) <= $max_length) { $multiple_args[$i]['message'] .= $word; } else { $i++; @@ -670,7 +679,7 @@ trait BotAPI foreach ($initialArray as $item) { $delimOffset += $this->mbStrlen($item); //if ($this->mbStrlen($item) > 0) { - $finalArray[] = $item . ($delimOffset < $this->mbStrlen($string) ? $string[$delimOffset] : ''); + $finalArray[] = $item.($delimOffset < $this->mbStrlen($string) ? $string[$delimOffset] : ''); //} $delimOffset++; } diff --git a/src/danog/MadelineProto/TL/Conversion/BotAPIFiles.php b/src/danog/MadelineProto/TL/Conversion/BotAPIFiles.php index 6824084a..d18200b7 100644 --- a/src/danog/MadelineProto/TL/Conversion/BotAPIFiles.php +++ b/src/danog/MadelineProto/TL/Conversion/BotAPIFiles.php @@ -39,17 +39,38 @@ use const danog\Decoder\VOICE; trait BotAPIFiles { - private function photosizeToBotAPI($photoSize, $photo, $thumbnail = false): \Generator + private function photosizeToBotAPI($photoSize, $photo, $thumbnail = false): array { - $ext = '.jpg'; - //$this->getExtensionFromLocation(['_' => 'inputFileLocation', 'volume_id' => $photoSize['location']['volume_id'], 'local_id' => $photoSize['location']['local_id'], 'secret' => $photoSize['location']['secret'], 'dc_id' => $photoSize['location']['dc_id']], '.jpg'); - $photoSize['location']['access_hash'] = $photo['access_hash'] ?? 0; - $photoSize['location']['id'] = $photo['id'] ?? 0; - $photoSize['location']['secret'] = $photo['location']['secret'] ?? 0; - $photoSize['location']['dc_id'] = $photo['dc_id'] ?? 0; - $photoSize['location']['_'] = $thumbnail ? 'bot_thumbnail' : 'bot_photo'; - $data = (yield from $this->TL->serializeObject(['type' => 'File'], $photoSize['location'], 'File')).\chr(2); - return ['file_id' => \danog\MadelineProto\Tools::base64urlEncode(\danog\MadelineProto\Tools::rleEncode($data)), 'width' => $photoSize['w'], 'height' => $photoSize['h'], 'file_size' => isset($photoSize['size']) ? $photoSize['size'] : \strlen($photoSize['bytes']), 'mime_type' => 'image/jpeg', 'file_name' => $photoSize['location']['volume_id'].'_'.$photoSize['location']['local_id'].$ext]; + $fileId = new FileId; + $fileId->setId($photo['id'] ?? 0); + $fileId->setAccessHash($photo['access_hash'] ?? 0); + $fileId->setFileReference($photo['file_reference'] ?? ''); + $fileId->setDcId($photo['dc_id'] ?? 0); + + $fileId->setLocalId($photoSize['location']['local_id']); + $fileId->setVolumeId($photoSize['location']['volume_id']); + + $photoSizeSource = new PhotoSizeSourceThumbnail; + $photoSizeSource->setThumbType($photoSize['type']); + + if ($photo['_'] === 'photo') { + $photoSizeSource->setThumbFileType(PHOTO); + $fileId->setType(PHOTO); + } else { + $photoSizeSource->setThumbFileType(THUMBNAIL); + $fileId->setType(THUMBNAIL); + } + $fileId->setPhotoSizeSource($photoSizeSource); + + return [ + 'file_id' => (string) $fileId, + 'file_unique_id' => $fileId->getUniqueBotAPI(), + 'width' => $photoSize['w'], + 'height' => $photoSize['h'], + 'file_size' => $photoSize['size'] ?? \strlen($photoSize['bytes']), + 'mime_type' => 'image/jpeg', + 'file_name' => $photoSize['location']['volume_id'].'_'.$photoSize['location']['local_id'].'.jpg' + ]; } /** * Unpack bot API file ID. diff --git a/tests/danog/MadelineProto/FileIdTest.php b/tests/danog/MadelineProto/FileIdTest.php index be35f874..04750783 100644 --- a/tests/danog/MadelineProto/FileIdTest.php +++ b/tests/danog/MadelineProto/FileIdTest.php @@ -40,6 +40,54 @@ class FileIdTest extends TestCase self::$MadelineProto->botLogin(\getenv('BOT_TOKEN')); } + /** + * Strip file reference from file ID. + * + * @param string $fileId File ID + * + * @return string + */ + public static function stripFileReference(string $fileId): string + { + return FileId::fromBotAPI($fileId)->setFileReference(''); + } + /** + * Strip access hash (and possibly ID) from file ID. + * + * @param string $fileId File ID + * + * @return string + */ + public static function stripForChat(string $fileId): string + { + $file = FileId::fromBotAPI($fileId)->setAccessHash(0); + if ($file->getPhotoSizeSource()->getDialogId() < 0) { + $file->setId(0); + } + return $file; + } + + /** + * Asserts that two file IDs are equal. + * + * @param string $fileIdAstr File ID A + * @param string $fileIdBstr File ID B + * @param string $message Message + * + * @throws PHPUnit\Framework\AssertionFailedError + * + * @return void + */ + public static function assertFileIdEquals(string $fileIdAstr, string $fileIdBstr, $message = '') + { + $fileIdAstr = self::stripFileReference($fileIdAstr); + $fileIdBstr = self::stripFileReference($fileIdBstr); + if ($fileIdAstr !== $fileIdBstr) { + \var_dump(FileId::fromBotAPI($fileIdAstr), FileId::fromBotAPI($fileIdBstr)); + } + self::assertEquals($fileIdAstr, $fileIdBstr, $message); + } + /** * @param string $fileId File ID * @param string $type Expected type @@ -47,7 +95,7 @@ class FileIdTest extends TestCase * * @dataProvider provideFileIdsAndType */ - public function testDownload(string $type, string $fileIdStr, string $origType) + public function testDownload(string $type, string $fileIdStr, string $uniqueFileIdStr, array $fullInfo) { self::$MadelineProto->logger("Trying to download $fileIdStr"); self::$MadelineProto->downloadToFile($fileIdStr, '/dev/null'); @@ -60,16 +108,62 @@ class FileIdTest extends TestCase * * @dataProvider provideFileIdsAndType */ - public function testResend(string $type, string $fileIdStr, string $origType) + public function testResendConvert(string $type, string $fileIdStr, string $uniqueFileIdStr, array $fullInfo) { - self::$MadelineProto->logger("Trying to resend $fileIdStr"); - self::$MadelineProto->messages->sendMedia( + self::$MadelineProto->logger("Trying to resend and then reconvert $fileIdStr"); + if ($type === 'profile_photo') { + $chat = self::$MadelineProto->getPwrChat($fullInfo['chat']); + $this->assertArrayHasKey('photo', $chat); + $chat = $chat['photo']; + $this->assertArrayHasKey($fullInfo['type'].'_file_id', $chat); + $this->assertArrayHasKey($fullInfo['type'].'_file_unique_id', $chat); + + $chat[$fullInfo['type'].'_file_id'] = self::stripForChat($chat[$fullInfo['type'].'_file_id']); + + $this->assertFileIdEquals($fileIdStr, $chat[$fullInfo['type'].'_file_id']); + $this->assertEquals($uniqueFileIdStr, $chat[$fullInfo['type'].'_file_unique_id']); + + $this->expectExceptionMessage("Chat photo file IDs can't be reused to resend chat photos, please use getPwrChat()['photo'], instead"); + } + $res = self::$MadelineProto->messages->sendMedia( [ 'peer' => \getenv('DEST'), 'media' => $fileIdStr + ], + [ + 'botAPI' => true ] ); - $this->assertTrue(true); + if ($type === 'thumbnail') { + $this->assertArrayHasKey($fullInfo[0], $res); + $res = $res[$fullInfo[0]]; + $this->assertArrayHasKey('thumb', $res); + $this->assertFileIdEquals($fileIdStr, $res['thumb']['file_id']); + $this->assertEquals($uniqueFileIdStr, $res['thumb']['file_unique_id']); + + list($type, $fileIdStr, $uniqueFileIdStr) = $fullInfo; + } else { + $this->assertArrayHasKey($type, $res); + $res = $res[$type]; + } + + $hasFileId = false; + $hasFileUniqueId = false; + $res = $type === 'photo' ? $res : [$res]; + foreach ($res as $subRes) { + $this->assertArrayHasKey('file_id', $subRes); + $this->assertArrayHasKey('file_unique_id', $subRes); + $hasFileId |= self::stripFileReference($fileIdStr) === self::stripFileReference($subRes['file_id']); + $hasFileUniqueId |= $uniqueFileIdStr === $subRes['file_unique_id']; + } + + if (\count($res) === 1) { + $this->assertFileIdEquals($fileIdStr, $res[0]['file_id']); + $this->assertEquals($uniqueFileIdStr, $res[0]['file_unique_id']); + } else { + $this->assertTrue((bool) $hasFileUniqueId); + $this->assertTrue((bool) $hasFileId); + } } public function provideFileIdsAndType(): \Generator @@ -77,16 +171,21 @@ class FileIdTest extends TestCase $dest = \getenv('DEST'); $token = \getenv('BOT_TOKEN'); foreach ($this->provideChats() as $chat) { - $result = \json_decode(\file_get_contents("https://api.telegram.org/bot$token/getChat?chat_id=$chat"), true)['result']['photo']; + $result = \json_decode(\file_get_contents("https://api.telegram.org/bot$token/getChat?chat_id=$chat"), true)['result']['photo'] ?? []; + if (!$result) { + continue; + } yield [ 'profile_photo', $result['small_file_id'], - 'profile_photo', + $result['small_file_unique_id'], + ['chat' => $chat, 'type' => 'small'], ]; yield [ 'profile_photo', $result['big_file_id'], - 'profile_photo', + $result['big_file_unique_id'], + ['chat' => $chat, 'type' => 'big'], ]; } foreach ($this->provideUrls() as $type => $url) { @@ -111,16 +210,18 @@ class FileIdTest extends TestCase $botResult = [$botResult]; } foreach ($botResult as $subResult) { - yield [ + yield $full = [ $type, $subResult['file_id'], - $type, + $subResult['file_unique_id'], + [], ]; if (isset($subResult['thumb'])) { yield [ 'thumbnail', $subResult['thumb']['file_id'], - $type, + $subResult['thumb']['file_unique_id'], + $full, ]; } } @@ -129,11 +230,11 @@ class FileIdTest extends TestCase } public function provideChats(): array { - return [\getenv('DEST'), '@MadelineProto']; + return [\getenv('DEST'), '@MadelineProto', -382346236]; } public function provideUrls(): array { - return [ + $res = [ 'sticker' => 'https://github.com/danog/MadelineProto/blob/master/tests/lel.webp?raw=true', 'photo' => 'https://github.com/danog/MadelineProto/blob/master/tests/faust.jpg', 'audio' => 'https://github.com/danog/MadelineProto/blob/master/tests/mosconi.mp3?raw=true', @@ -141,7 +242,10 @@ class FileIdTest extends TestCase 'animation' => 'https://github.com/danog/MadelineProto/blob/master/tests/pony.mp4?raw=true', 'document' => 'https://github.com/danog/danog.github.io/raw/master/lol/index_htm_files/0.gif', 'voice' => 'https://daniil.it/audio_2020-02-01_18-09-08.ogg', - 'video_note' => 'https://daniil.it/round.mp4' ]; + if (\getenv('TRAVIS_COMMIT')) { + $res['video_note'] = 'https://daniil.it/round.mp4'; + } + return $res; } }