diff --git a/composer.json b/composer.json index ac41d7c..dacd64a 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": ">=8.1" + "php-64bit": ">=8.1" }, "require-dev": { "phpunit/phpunit": "^9", diff --git a/psalm-baseline.xml b/psalm-baseline.xml deleted file mode 100644 index a0e23a0..0000000 --- a/psalm-baseline.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/psalm.xml b/psalm.xml index 839a987..8ecbfb5 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,11 +1,13 @@ diff --git a/src/FileId.php b/src/FileId.php index 3ddb30b..dd2d84c 100644 --- a/src/FileId.php +++ b/src/FileId.php @@ -19,6 +19,8 @@ namespace danog\Decoder; use danog\Decoder\PhotoSizeSource\PhotoSizeSourceDialogPhoto; +use danog\Decoder\PhotoSizeSource\PhotoSizeSourceDialogPhotoBig; +use danog\Decoder\PhotoSizeSource\PhotoSizeSourceDialogPhotoSmall; use danog\Decoder\PhotoSizeSource\PhotoSizeSourceLegacy; use danog\Decoder\PhotoSizeSource\PhotoSizeSourceStickersetThumbnail; use danog\Decoder\PhotoSizeSource\PhotoSizeSourceStickersetThumbnailVersion; @@ -31,73 +33,73 @@ use danog\Decoder\PhotoSizeSource\PhotoSizeSourceThumbnail; */ final class FileId { - /** - * Bot API file ID version. - * - */ - private int $version = 4; - - /** - * Bot API file ID subversion. - * - */ - private int $subVersion = 47; - - /** - * DC ID. - * - */ - private int $dcId = 0; - - /** - * File type. - * - */ - private FileIdType $type = FileIdType::NONE; - - /** - * File reference. - * - */ - private string $fileReference = ''; - /** - * File URL for weblocation. - * - */ - private string $url = ''; - - /** - * File id. - * - */ - private int $id; - /** - * File access hash. - * - */ - private int $accessHash; - - /** - * Photo volume ID. - * - */ - private int $volumeId; - /** - * Photo local ID. - * - */ - private int $localId; - - /** - * Photo size source. - * - */ - private PhotoSizeSource $photoSizeSource; /** * Basic constructor function. */ - public function __construct() - { + public function __construct( + /** + * DC ID. + * + */ + public readonly int $dcId, + + /** + * File type. + * + */ + public readonly FileIdType $type, + + /** + * File id. + * + */ + public readonly ?int $id, + /** + * File access hash. + * + */ + public readonly int $accessHash, + + /** + * Photo size source. + * + */ + public readonly ?PhotoSizeSource $photoSizeSource = null, + + /** + * Photo volume ID. + * + */ + public readonly ?int $volumeId = null, + /** + * Photo local ID. + * + */ + public readonly ?int $localId = null, + + /** + * File reference. + * + */ + public readonly ?string $fileReference = null, + /** + * File URL for weblocation. + * + */ + public readonly ?string $url = null, + + /** + * Bot API file ID version. + * + */ + public readonly int $version = 4, + + /** + * Bot API file ID subversion. + * + */ + public readonly int $subVersion = 47, + ) { } /** @@ -108,66 +110,139 @@ final class FileId */ public static function fromBotAPI(string $fileId): self { - $result = new self; - $resultArray = internalDecode($fileId); - $result->setVersion($resultArray['version']); - $result->setSubVersion($resultArray['subVersion']); - $result->setType($resultArray['typeId']); - $result->setDcId($resultArray['dc_id']); - $result->setAccessHash($resultArray['access_hash']); + $orig = $fileId; + $fileId = rleDecode(base64urlDecode($fileId)); + $version = \ord($fileId[\strlen($fileId) - 1]); + $subVersion = $version === 4 ? \ord($fileId[\strlen($fileId) - 2]) : 0; - if ($resultArray['hasReference']) { - $result->setFileReference($resultArray['fileReference']); - } - if ($resultArray['hasWebLocation']) { - $result->setUrl($resultArray['url']); - return $result; - } - $result->setId($resultArray['id']); + $res = \fopen('php://memory', 'rw+b'); + \assert($res !== false); + \fwrite($res, $fileId); + \fseek($res, 0); + $fileId = $res; + $read = function (int $length) use (&$fileId): string { + $res = \stream_get_contents($fileId, $length); + \assert($res !== false); + return $res; + }; - if ($result->getType()->value <= FileIdType::PHOTO->value) { - if (isset($resultArray['volume_id'])) { - $result->setVolumeId($resultArray['volume_id']); + $typeId = unpackInt($read(4)); + $dc_id = unpackInt($read(4)); + $fileReference = $typeId & FILE_REFERENCE_FLAG ? readTLString($fileId) : null; + $hasWebLocation = (bool) ($typeId & WEB_LOCATION_FLAG); + $typeId &= ~FILE_REFERENCE_FLAG; + $typeId &= ~WEB_LOCATION_FLAG; + + if ($hasWebLocation) { + $url = readTLString($fileId); + $access_hash = unpackLong($read(8)); + return new self( + $dc_id, + FileIdType::from($typeId), + null, + $access_hash, + fileReference: $fileReference, + url: $url, + version: $version, + subVersion: $subVersion + ); + } + $id = unpackLong($read(8)); + $access_hash = unpackLong($read(8)); + + $volume_id = null; + $local_id = null; + $photoSizeSource = null; + if ($typeId <= FileIdType::PHOTO->value) { + if ($subVersion < 32) { + $volume_id = unpackLong($read(8)); + $local_id = unpackInt($read(4)); } - if (isset($resultArray['local_id'])) { - $result->setLocalId($resultArray['local_id']); - } - switch ($resultArray['photosize_source']) { + + /** @psalm-suppress MixedArgument */ + $photosize_source = PhotoSizeSourceType::from($subVersion >= 4 ? \unpack('V', $read(4))[1] : 0); + switch ($photosize_source) { case PhotoSizeSourceType::LEGACY: + $photoSizeSource = new PhotoSizeSourceLegacy(unpackLong($read(8))); + break; case PhotoSizeSourceType::FULL_LEGACY: - $photoSizeSource = new PhotoSizeSourceLegacy($resultArray['photosize_source']); - $photoSizeSource->setSecret($resultArray['secret']); + $volume_id = unpackLong($read(8)); + $photoSizeSource = new PhotoSizeSourceLegacy(unpackLong($read(8))); + $local_id = unpackInt($read(4)); break; case PhotoSizeSourceType::THUMBNAIL: - $photoSizeSource = new PhotoSizeSourceThumbnail($resultArray['photosize_source']); - $photoSizeSource->setThumbType($resultArray['thumbnail_type']); - $photoSizeSource->setThumbFileType($resultArray['file_type']); + /** @var array{file_type: int, thumbnail_type: string} */ + $result = \unpack('Vfile_type/athumbnail_type', $read(8)); + $photoSizeSource = new PhotoSizeSourceThumbnail( + FileIdType::from($result['file_type']), + $result['thumbnail_type'] + ); + break; + case PhotoSizeSourceType::DIALOGPHOTO_BIG: + case PhotoSizeSourceType::DIALOGPHOTO_SMALL: + $clazz = $photosize_source === PhotoSizeSourceType::DIALOGPHOTO_SMALL + ? PhotoSizeSourceDialogPhotoSmall::class + : PhotoSizeSourceDialogPhotoBig::class; + $photoSizeSource = new $clazz( + unpackLong($read(8)), + unpackLong($read(8)), + ); + break; + case PhotoSizeSourceType::STICKERSET_THUMBNAIL: + $photoSizeSource = new PhotoSizeSourceStickersetThumbnail( + unpackLong($read(8)), + unpackLong($read(8)) + ); break; case PhotoSizeSourceType::DIALOGPHOTO_BIG_LEGACY: case PhotoSizeSourceType::DIALOGPHOTO_SMALL_LEGACY: - case PhotoSizeSourceType::DIALOGPHOTO_BIG: - case PhotoSizeSourceType::DIALOGPHOTO_SMALL: - $photoSizeSource = new PhotoSizeSourceDialogPhoto($resultArray['photosize_source']); - $photoSizeSource->setDialogId($resultArray['dialog_id']); - $photoSizeSource->setDialogAccessHash($resultArray['dialog_access_hash']); + $clazz = $photosize_source === PhotoSizeSourceType::DIALOGPHOTO_SMALL_LEGACY + ? PhotoSizeSourceDialogPhotoSmall::class + : PhotoSizeSourceDialogPhotoBig::class; + $photoSizeSource = new $clazz( + unpackLong($read(8)), + unpackLong($read(8)) + ); + + $volume_id = unpackLong($read(8)); + $local_id = unpackInt($read(4)); break; - case PhotoSizeSourceType::STICKERSET_THUMBNAIL: case PhotoSizeSourceType::STICKERSET_THUMBNAIL_LEGACY: - $photoSizeSource = new PhotoSizeSourceStickersetThumbnail($resultArray['photosize_source']); - $photoSizeSource->setStickerSetId($resultArray['sticker_set_id']); - $photoSizeSource->setStickerSetAccessHash($resultArray['sticker_set_access_hash']); + $photoSizeSource = new PhotoSizeSourceStickersetThumbnail( + unpackLong($read(8)), + unpackLong($read(8)), + ); + + $volume_id = unpackLong($read(8)); + $local_id = unpackInt($read(4)); break; case PhotoSizeSourceType::STICKERSET_THUMBNAIL_VERSION: - $photoSizeSource = new PhotoSizeSourceStickersetThumbnailVersion($resultArray['photosize_source']); - $photoSizeSource->setStickerSetId($resultArray['sticker_set_id']); - $photoSizeSource->setStickerSetAccessHash($resultArray['sticker_set_access_hash']); - $photoSizeSource->setStickerSetVersion($resultArray['sticker_version']); + $photoSizeSource = new PhotoSizeSourceStickersetThumbnailVersion( + unpackLong($read(8)), + unpackLong($read(8)), + unpackInt($read(4)) + ); break; } - $result->setPhotoSizeSource($photoSizeSource); + } + $l = \fstat($fileId)['size'] - \ftell($fileId); + $l -= $version >= 4 ? 2 : 1; + if ($l > 0) { + \trigger_error("File ID $orig has $l bytes of leftover data"); } - return $result; + return new self( + dcId: $dc_id, + type: FileIdType::from($typeId), + id: $id, + accessHash: $access_hash, + volumeId: $volume_id, + localId: $local_id, + fileReference: $fileReference, + version: $version, + subVersion: $subVersion, + photoSizeSource: $photoSizeSource, + ); } /** @@ -176,76 +251,86 @@ final class FileId */ public function getBotAPI(): string { - $type = $this->getType()->value; - if ($this->hasFileReference()) { + $type = $this->type->value; + if ($this->fileReference !== null) { $type |= FILE_REFERENCE_FLAG; } - if ($this->hasUrl()) { + if ($this->url !== null) { $type |= WEB_LOCATION_FLAG; } - $fileId = \pack('VV', $type, $this->getDcId()); - if ($this->hasFileReference()) { - $fileId .= packTLString($this->getFileReference()); + $fileId = \pack('VV', $type, $this->dcId); + if ($this->fileReference !== null) { + $fileId .= packTLString($this->fileReference); } - if ($this->hasUrl()) { - $fileId .= packTLString($this->getUrl()); - $fileId .= packLong($this->getAccessHash()); + if ($this->url !== null) { + $fileId .= packTLString($this->url); + $fileId .= packLong($this->accessHash); return base64urlEncode(rleEncode($fileId)); } - $fileId .= packLong($this->getId()); - $fileId .= packLong($this->getAccessHash()); + \assert($this->id !== null); + $fileId .= packLong($this->id); + $fileId .= packLong($this->accessHash); - if ($this->getType()->value <= FileIdType::PHOTO->value) { - $photoSize = $this->getPhotoSizeSource(); - $fileId .= \pack('V', $photoSize->getType()); - switch ($photoSize->getType()) { - case PhotoSizeSourceType::LEGACY: - assert($photoSize instanceof PhotoSizeSourceLegacy); - $fileId .= packLong($photoSize->getSecret()); + if ($this->photoSizeSource !== null) { + $photoSize = $this->photoSizeSource; + $writeExtra = false; + switch (true) { + case $photoSize instanceof PhotoSizeSourceLegacy: + if ($this->volumeId === null) { + $writeExtra = true; + $fileId .= \pack('V', PhotoSizeSourceType::LEGACY->value); + $fileId .= packLong($photoSize->secret); + } else { + $fileId .= \pack('V', PhotoSizeSourceType::FULL_LEGACY->value); + $fileId .= packLong($this->volumeId); + $fileId .= packLong($photoSize->secret); + $fileId .= \pack('l', $this->localId); + } break; - case PhotoSizeSourceType::FULL_LEGACY: - assert($photoSize instanceof PhotoSizeSourceLegacy); - $fileId .= packLong($this->getVolumeId()); - $fileId .= packLong($photoSize->getSecret()); - $fileId .= \pack('l', $this->getLocalId()); + case $photoSize instanceof PhotoSizeSourceThumbnail: + $fileId .= \pack('V', PhotoSizeSourceType::THUMBNAIL->value); + $fileId .= \pack('Va4', $photoSize->thumbFileType->value, $photoSize->thumbType); break; - case PhotoSizeSourceType::THUMBNAIL: - assert($photoSize instanceof PhotoSizeSourceThumbnail); - $fileId .= \pack('Va4', $photoSize->getThumbFileType(), $photoSize->getThumbType()); + case $photoSize instanceof PhotoSizeSourceDialogPhoto: + $fileId .= \pack( + 'V', + ($writeExtra = $this->volumeId !== null) ? + ( + $photoSize->isSmallDialogPhoto() + ? PhotoSizeSourceType::DIALOGPHOTO_SMALL_LEGACY->value + : PhotoSizeSourceType::DIALOGPHOTO_BIG_LEGACY->value + ) : ( + $photoSize->isSmallDialogPhoto() + ? PhotoSizeSourceType::DIALOGPHOTO_SMALL->value + : PhotoSizeSourceType::DIALOGPHOTO_BIG->value + ) + ); + $fileId .= packLong($photoSize->dialogId); + $fileId .= packLong($photoSize->dialogAccessHash); break; - case PhotoSizeSourceType::DIALOGPHOTO_BIG: - case PhotoSizeSourceType::DIALOGPHOTO_SMALL: - case PhotoSizeSourceType::DIALOGPHOTO_BIG_LEGACY: - case PhotoSizeSourceType::DIALOGPHOTO_SMALL_LEGACY: - assert($photoSize instanceof PhotoSizeSourceDialogPhoto); - $fileId .= packLongBig($photoSize->getDialogId()); - $fileId .= packLong($photoSize->getDialogAccessHash()); + case $photoSize instanceof PhotoSizeSourceStickersetThumbnail: + $writeExtra = $this->volumeId !== null; + $fileId .= packLong($photoSize->stickerSetId); + $fileId .= packLong($photoSize->stickerSetAccessHash); break; - case PhotoSizeSourceType::STICKERSET_THUMBNAIL: - case PhotoSizeSourceType::STICKERSET_THUMBNAIL_LEGACY: - assert($photoSize instanceof PhotoSizeSourceStickersetThumbnail); - $fileId .= packLong($photoSize->getStickerSetId()); - $fileId .= packLong($photoSize->getStickerSetAccessHash()); - break; - case PhotoSizeSourceType::STICKERSET_THUMBNAIL_VERSION: - assert($photoSize instanceof PhotoSizeSourceStickersetThumbnailVersion); - $fileId .= packLong($photoSize->getStickerSetId()); - $fileId .= packLong($photoSize->getStickerSetAccessHash()); - $fileId .= \pack('l', $photoSize->getStickerSetVersion()); + case $photoSize instanceof PhotoSizeSourceStickersetThumbnailVersion: + $fileId .= packLong($photoSize->stickerSetId); + $fileId .= packLong($photoSize->stickerSetAccessHash); + $fileId .= \pack('l', $photoSize->stickerSetVersion); break; } - if ($photoSize->getType() >= PhotoSizeSourceType::DIALOGPHOTO_SMALL_LEGACY && $photoSize->getType()->value <= PhotoSizeSourceType::STICKERSET_THUMBNAIL_LEGACY->value) { - $fileId .= packLong($this->getVolumeId()); - $fileId .= \pack('l', $this->getLocalId()); + if ($writeExtra && $this->volumeId !== null && $this->localId !== null) { + $fileId .= packLong($this->volumeId); + $fileId .= \pack('l', $this->localId); } } - if ($this->getVersion() >= 4) { - $fileId .= \chr($this->getSubVersion()); + if ($this->version >= 4) { + $fileId .= \chr($this->subVersion); } - $fileId .= \chr($this->getVersion()); + $fileId .= \chr($this->version); return base64urlEncode(rleEncode($fileId)); } @@ -274,306 +359,4 @@ final class FileId { return $this->getBotAPI(); } - /** - * Get bot API file ID version. - * - */ - public function getVersion(): int - { - return $this->version; - } - - /** - * Set bot API file ID version. - * - * @param int $version Bot API file ID version. - * - */ - public function setVersion(int $version): self - { - $this->version = $version; - - return $this; - } - - /** - * Get bot API file ID subversion. - * - */ - public function getSubVersion(): int - { - return $this->subVersion; - } - - /** - * Set bot API file ID subversion. - * - * @param int $subVersion Bot API file ID subversion. - * - */ - public function setSubVersion(int $subVersion): self - { - $this->subVersion = $subVersion; - - return $this; - } - - /** - * Get file type. - * - */ - public function getType(): FileIdType - { - return $this->type; - } - - /** - * Set file type. - * - * @param FileIdType $type File type. - * - */ - public function setType(FileIdType $type): self - { - $this->type = $type; - - return $this; - } - - /** - * Get file reference. - * - */ - public function getFileReference(): string - { - return $this->fileReference; - } - - /** - * Set file reference. - * - * @param string $fileReference File reference. - * - */ - public function setFileReference(string $fileReference): self - { - $this->fileReference = $fileReference; - - return $this; - } - - /** - * Check if has file reference. - * - * @return boolean - */ - public function hasFileReference(): bool - { - return !empty($this->fileReference); - } - - /** - * Get file URL for weblocation. - * - */ - public function getUrl(): string - { - return $this->url; - } - - /** - * Check if has file URL. - * - * @return boolean - */ - public function hasUrl(): bool - { - return !empty($this->url); - } - - /** - * Set file URL for weblocation. - * - * @param string $url File URL for weblocation. - * - */ - public function setUrl(string $url): self - { - $this->url = $url; - - return $this; - } - - /** - * Get file id. - * - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * Set file id. - * - * @param int $id File id. - * - */ - public function setId(int $id): self - { - $this->id = $id; - - return $this; - } - - /** - * Check if has file id. - * - */ - public function hasId(): bool - { - return isset($this->id); - } - - /** - * Get file access hash. - * - * @return int - */ - public function getAccessHash() - { - return $this->accessHash; - } - - /** - * Set file access hash. - * - * @param int $accessHash File access hash. - * - */ - public function setAccessHash(int $accessHash): self - { - $this->accessHash = $accessHash; - - return $this; - } - - /** - * Get photo volume ID. - * - * @return int - */ - public function getVolumeId() - { - return $this->volumeId; - } - - /** - * Set photo volume ID. - * - * @param int $volumeId Photo volume ID. - * - */ - public function setVolumeId(int $volumeId): self - { - $this->volumeId = $volumeId; - - return $this; - } - /** - * Check if has volume ID. - * - * @return boolean - */ - public function hasVolumeId(): bool - { - return isset($this->volumeId); - } - - /** - * Get photo local ID. - * - */ - public function getLocalId(): int - { - return $this->localId; - } - - /** - * Set photo local ID. - * - * @param int $localId Photo local ID. - * - */ - public function setLocalId(int $localId): self - { - $this->localId = $localId; - - return $this; - } - - /** - * Check if has local ID. - * - * @return boolean - */ - public function hasLocalId(): bool - { - return isset($this->localId); - } - - /** - * Get photo size source. - * - */ - public function getPhotoSizeSource(): PhotoSizeSource - { - return $this->photoSizeSource; - } - - /** - * Set photo size source. - * - * @param PhotoSizeSource $photoSizeSource Photo size source. - * - */ - public function setPhotoSizeSource(PhotoSizeSource $photoSizeSource): self - { - $this->photoSizeSource = $photoSizeSource; - - return $this; - } - - /** - * Check if has photo size source. - * - * @return boolean - */ - public function hasPhotoSizeSource(): bool - { - return isset($this->photoSizeSource); - } - - /** - * Get dC ID. - * - */ - public function getDcId(): int - { - return $this->dcId; - } - - /** - * Set dC ID. - * - * @param int $dcId DC ID. - * - */ - public function setDcId(int $dcId): self - { - $this->dcId = $dcId; - - return $this; - } } diff --git a/src/PhotoSizeSource.php b/src/PhotoSizeSource.php index e839828..c253f55 100644 --- a/src/PhotoSizeSource.php +++ b/src/PhotoSizeSource.php @@ -18,50 +18,9 @@ namespace danog\Decoder; -use danog\Decoder\PhotoSizeSource\PhotoSizeSourceLegacy; - /** * Represents source of photosize. - * - * @template T */ abstract class PhotoSizeSource { - /** - * Source type. - * - */ - private PhotoSizeSourceType $type; - - /** - * Set photosize source type. - * - * @param PhotoSizeSourceType $type Type - */ - public function __construct(PhotoSizeSourceType $type) - { - $this->type = $type; - } - /** - * Get photosize source type. - * - * @return integer - * - * @psalm-return ( - * T is PhotoSizeSourceLegacy ? - * ? \danog\Decoder\PhotoSizeSourceType::LEGACY - * : (T is PhotoSizeSourceDialogPhoto - * ? \danog\Decoder\PhotoSizeSourceType::DIALOGPHOTO_* - * (T is PhotoSizeSourceStickersetThumbnail - * ? \danog\Decoder\PhotoSizeSourceType::STICKERSET_THUMBNAIL - * : \danog\Decoder\PhotoSizeSourceType::THUMBNAIL - * ) - * ) - * - * @internal Internal use - */ - public function getType(): PhotoSizeSourceType - { - return $this->type; - } } diff --git a/src/PhotoSizeSource/PhotoSizeSourceDialogPhoto.php b/src/PhotoSizeSource/PhotoSizeSourceDialogPhoto.php index 2e26520..9e94dbd 100644 --- a/src/PhotoSizeSource/PhotoSizeSourceDialogPhoto.php +++ b/src/PhotoSizeSource/PhotoSizeSourceDialogPhoto.php @@ -19,77 +19,22 @@ namespace danog\Decoder\PhotoSizeSource; use danog\Decoder\PhotoSizeSource; -use danog\Decoder\PhotoSizeSourceType; /** * Represents source of photosize. * * @api - * - * @extends PhotoSizeSource */ -final class PhotoSizeSourceDialogPhoto extends PhotoSizeSource +abstract class PhotoSizeSourceDialogPhoto extends PhotoSizeSource { - /** - * ID of dialog. - * - */ - private int $dialogId; - /** - * Access hash of dialog. - * - */ - private int $dialogAccessHash; - - /** - * Get dialog ID. - * - * @return int - */ - public function getDialogId() - { - return $this->dialogId; + public function __construct( + public readonly int $dialogId, + public readonly int $dialogAccessHash, + ) { } - /** - * Set dialog ID. - * - * @param int $id Dialog ID - * - */ - public function setDialogId(int $id): self - { - $this->dialogId = $id; - return $this; - } - /** - * Get access hash of dialog. - * - * @return int - */ - public function getDialogAccessHash() - { - return $this->dialogAccessHash; - } - - /** - * Set access hash of dialog. - * - * @param int $dialogAccessHash Access hash of dialog - * - */ - public function setDialogAccessHash(int $dialogAccessHash): self - { - $this->dialogAccessHash = $dialogAccessHash; - - return $this; - } - /** * Get whether the big or small version of the photo is being used. * */ - public function isSmallDialogPhoto(): bool - { - return \in_array($this->getType(), [PhotoSizeSourceType::DIALOGPHOTO_SMALL_LEGACY, PhotoSizeSourceType::DIALOGPHOTO_SMALL], true); - } + abstract public function isSmallDialogPhoto(): bool; } diff --git a/src/PhotoSizeSource/PhotoSizeSourceDialogPhotoBig.php b/src/PhotoSizeSource/PhotoSizeSourceDialogPhotoBig.php new file mode 100644 index 0000000..137e328 --- /dev/null +++ b/src/PhotoSizeSource/PhotoSizeSourceDialogPhotoBig.php @@ -0,0 +1,36 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2019 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * + * @link https://github.com/tg-file-decoder Documentation + */ + +namespace danog\Decoder\PhotoSizeSource; + +/** + * Represents source of photosize. + * + * @api + */ +final class PhotoSizeSourceDialogPhotoBig extends PhotoSizeSourceDialogPhoto +{ + /** + * Get whether the big or small version of the photo is being used. + * + */ + public function isSmallDialogPhoto(): bool + { + return false; + } +} diff --git a/src/PhotoSizeSource/PhotoSizeSourceDialogPhotoSmall.php b/src/PhotoSizeSource/PhotoSizeSourceDialogPhotoSmall.php new file mode 100644 index 0000000..ecbd82a --- /dev/null +++ b/src/PhotoSizeSource/PhotoSizeSourceDialogPhotoSmall.php @@ -0,0 +1,36 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2019 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * + * @link https://github.com/tg-file-decoder Documentation + */ + +namespace danog\Decoder\PhotoSizeSource; + +/** + * Represents source of photosize. + * + * @api + */ +final class PhotoSizeSourceDialogPhotoSmall extends PhotoSizeSourceDialogPhoto +{ + /** + * Get whether the big or small version of the photo is being used. + * + */ + public function isSmallDialogPhoto(): bool + { + return true; + } +} diff --git a/src/PhotoSizeSource/PhotoSizeSourceLegacy.php b/src/PhotoSizeSource/PhotoSizeSourceLegacy.php index 9ed8948..56ae157 100644 --- a/src/PhotoSizeSource/PhotoSizeSourceLegacy.php +++ b/src/PhotoSizeSource/PhotoSizeSourceLegacy.php @@ -24,37 +24,11 @@ use danog\Decoder\PhotoSizeSource; * Represents source of photosize. * * @api - * - * @extends PhotoSizeSource */ final class PhotoSizeSourceLegacy extends PhotoSizeSource { - /** - * Secret legacy ID. - * - */ - private int $secret; - - /** - * Get secret legacy ID. - * - * @return int - */ - public function getSecret() - { - return $this->secret; - } - - /** - * Set secret legacy ID. - * - * @param int $secret Secret legacy ID - * - */ - public function setSecret(int $secret): self - { - $this->secret = $secret; - - return $this; + public function __construct( + public readonly int $secret, + ) { } } diff --git a/src/PhotoSizeSource/PhotoSizeSourceStickersetThumbnail.php b/src/PhotoSizeSource/PhotoSizeSourceStickersetThumbnail.php index c3f48f3..eb055e3 100644 --- a/src/PhotoSizeSource/PhotoSizeSourceStickersetThumbnail.php +++ b/src/PhotoSizeSource/PhotoSizeSourceStickersetThumbnail.php @@ -24,65 +24,21 @@ use danog\Decoder\PhotoSizeSource; * Represents source of photosize. * * @api - * - * @extends PhotoSizeSource */ final class PhotoSizeSourceStickersetThumbnail extends PhotoSizeSource { - /** - * Stickerset ID. - * - */ - private int $stickerSetId; - /** - * Stickerset access hash. - * - */ - private int $stickerSetAccessHash; + public function __construct( + /** + * Stickerset ID. + * + */ + public readonly int $stickerSetId, + /** + * Stickerset access hash. + * + */ + public readonly int $stickerSetAccessHash + ) { - /** - * Get stickerset ID. - * - * @return int - */ - public function getStickerSetId() - { - return $this->stickerSetId; - } - - /** - * Set stickerset ID. - * - * @param int $stickerSetId Stickerset ID - * - */ - public function setStickerSetId(int $stickerSetId): self - { - $this->stickerSetId = $stickerSetId; - - return $this; - } - - /** - * Get stickerset access hash. - * - * @return int - */ - public function getStickerSetAccessHash() - { - return $this->stickerSetAccessHash; - } - - /** - * Set stickerset access hash. - * - * @param int $stickerSetAccessHash Stickerset access hash - * - */ - public function setStickerSetAccessHash(int $stickerSetAccessHash): self - { - $this->stickerSetAccessHash = $stickerSetAccessHash; - - return $this; } } diff --git a/src/PhotoSizeSource/PhotoSizeSourceStickersetThumbnailVersion.php b/src/PhotoSizeSource/PhotoSizeSourceStickersetThumbnailVersion.php index a0c1eb6..7ff55f9 100644 --- a/src/PhotoSizeSource/PhotoSizeSourceStickersetThumbnailVersion.php +++ b/src/PhotoSizeSource/PhotoSizeSourceStickersetThumbnailVersion.php @@ -24,92 +24,26 @@ use danog\Decoder\PhotoSizeSource; * Represents source of photosize. * * @api - * - * @extends PhotoSizeSource */ final class PhotoSizeSourceStickersetThumbnailVersion extends PhotoSizeSource { - /** - * Stickerset ID. - * - */ - private int $stickerSetId; - /** - * Stickerset access hash. - * - */ - private int $stickerSetAccessHash; - /** - * Stickerset version. - * - */ - private int $stickerSetVersion; + public function __construct( + /** + * Stickerset ID. + * + */ + public readonly int $stickerSetId, + /** + * Stickerset access hash. + * + */ + public readonly int $stickerSetAccessHash, + /** + * Stickerset version. + * + */ + public readonly int $stickerSetVersion + ) { - /** - * Get stickerset ID. - * - * @return int - */ - public function getStickerSetId() - { - return $this->stickerSetId; - } - - /** - * Set stickerset ID. - * - * @param int $stickerSetId Stickerset ID - * - */ - public function setStickerSetId(int $stickerSetId): self - { - $this->stickerSetId = $stickerSetId; - - return $this; - } - - /** - * Get stickerset access hash. - * - * @return int - */ - public function getStickerSetAccessHash() - { - return $this->stickerSetAccessHash; - } - - /** - * Set stickerset access hash. - * - * @param int $stickerSetAccessHash Stickerset access hash - * - */ - public function setStickerSetAccessHash(int $stickerSetAccessHash): self - { - $this->stickerSetAccessHash = $stickerSetAccessHash; - - return $this; - } - - /** - * Get stickerset version. - * - */ - public function getStickerSetVersion(): int - { - return $this->stickerSetVersion; - } - - /** - * Set stickerset version. - * - * @param int $stickerSetVersion Stickerset version. - * - */ - public function setStickerSetVersion(int $stickerSetVersion): self - { - $this->stickerSetVersion = $stickerSetVersion; - - return $this; } } diff --git a/src/PhotoSizeSource/PhotoSizeSourceThumbnail.php b/src/PhotoSizeSource/PhotoSizeSourceThumbnail.php index aa7d594..348151f 100644 --- a/src/PhotoSizeSource/PhotoSizeSourceThumbnail.php +++ b/src/PhotoSizeSource/PhotoSizeSourceThumbnail.php @@ -25,62 +25,20 @@ use danog\Decoder\PhotoSizeSource; * Represents source of photosize. * * @api - * - * @extends PhotoSizeSource */ final class PhotoSizeSourceThumbnail extends PhotoSizeSource { - /** - * File type of original file. - * - */ - private FileIdType $thumbFileType; - /** - * Thumbnail size. - * - */ - private string $thumbType; - - /** - * Get file type of original file. - * - */ - public function getThumbFileType(): FileIdType - { - return $this->thumbFileType; - } - /** - * Set file type of original file. - * - * @param FileIdType $thumbFileType File type of original file - * - */ - public function setThumbFileType(FileIdType $thumbFileType): self - { - $this->thumbFileType = $thumbFileType; - - return $this; - } - - /** - * Get thumbnail size. - * - */ - public function getThumbType(): string - { - return $this->thumbType; - } - - /** - * Set thumbnail size. - * - * @param string $thumbType Thumbnail size - * - */ - public function setThumbType(string $thumbType): self - { - $this->thumbType = $thumbType; - - return $this; + public function __construct( + /** + * File type of original file. + * + */ + public readonly FileIdType $thumbFileType, + /** + * Thumbnail size. + * + */ + public readonly string $thumbType, + ) { } } diff --git a/src/PhotoSizeSourceType.php b/src/PhotoSizeSourceType.php index 0128637..aac18d9 100644 --- a/src/PhotoSizeSourceType.php +++ b/src/PhotoSizeSourceType.php @@ -19,6 +19,9 @@ namespace danog\Decoder; +/** + * @internal Not for public use + */ enum PhotoSizeSourceType: int { case LEGACY = 0; diff --git a/src/UniqueFileId.php b/src/UniqueFileId.php index 709475f..086071c 100644 --- a/src/UniqueFileId.php +++ b/src/UniqueFileId.php @@ -29,51 +29,51 @@ use danog\Decoder\PhotoSizeSource\PhotoSizeSourceThumbnail; */ final class UniqueFileId { - /** - * File type. - * - */ - private UniqueFileIdType $type; - /** - * File ID. - * - */ - private int $id; - /** - * Photo volume ID. - * - */ - private int $volumeId; - /** - * Photo local ID. - * - */ - private int $localId; - /** - * Photo subtype. - * - */ - private int $subType; - /** - * Sticker set ID. - * - */ - private int $stickerSetId; - /** - * Sticker set version. - * - */ - private int $stickerSetVersion; - /** - * Weblocation URL. - * - */ - private string $url; /** * Basic constructor function. */ - public function __construct() - { + public function __construct( + /** + * File type. + * + */ + public readonly UniqueFileIdType $type, + /** + * File ID. + * + */ + public readonly ?int $id = null, + /** + * Photo subtype. + * + */ + public readonly ?int $subType = null, + /** + * Photo volume ID. + * + */ + public readonly ?int $volumeId = null, + /** + * Photo local ID. + * + */ + public readonly ?int $localId = null, + /** + * Sticker set ID. + * + */ + public readonly ?int $stickerSetId = null, + /** + * Sticker set version. + * + */ + public readonly ?int $stickerSetVersion = null, + /** + * Weblocation URL. + * + */ + public readonly ?string $url= null, + ) { } /** @@ -91,23 +91,25 @@ final class UniqueFileId */ public function getUniqueBotAPI(): string { - $fileId = \pack('V', $this->getType()); - if ($this->getType() === UniqueFileIdType::WEB) { - $fileId .= packTLString($this->getUrl()); - } elseif ($this->getType() === UniqueFileIdType::PHOTO) { - if ($this->hasVolumeId()) { - $fileId .= packLong($this->getVolumeId()); - $fileId .= \pack('l', $this->getLocalId()); - } elseif ($this->hasStickerSetId()) { - $fileId .= \chr($this->getSubType()); - $fileId .= packLong($this->getStickerSetId()); - $fileId .= \pack('l', $this->getStickerSetVersion()); + $fileId = \pack('V', $this->type->value); + if ($this->url !== null) { + $fileId .= packTLString($this->url); + } elseif ($this->type === UniqueFileIdType::PHOTO) { + if ($this->volumeId !== null) { + $fileId .= packLong($this->volumeId); + $fileId .= \pack('l', $this->localId); + } elseif ($this->stickerSetId !== null) { + \assert($this->subType !== null); + $fileId .= \chr($this->subType); + $fileId .= packLong($this->stickerSetId); + $fileId .= \pack('l', $this->stickerSetVersion); } else { - $fileId .= packLong($this->getId()); - $fileId .= \chr($this->getSubType()); + \assert($this->subType !== null && $this->id !== null); + $fileId .= packLong($this->id); + $fileId .= \chr($this->subType); } - } elseif ($this->hasId()) { - $fileId .= packLong($this->getId()); + } elseif ($this->id !== null) { + $fileId .= packLong($this->id); } return base64urlEncode(rleEncode($fileId)); @@ -121,28 +123,61 @@ final class UniqueFileId */ public static function fromUniqueBotAPI(string $fileId): self { - $result = new self(); - $resultArray = internalDecodeUnique($fileId); - $result->setType($resultArray['typeId']); - if ($result->getType() === UniqueFileIdType::WEB) { - $result->setUrl($resultArray['url']); - } elseif ($result->getType() === UniqueFileIdType::PHOTO) { - if (isset($resultArray['volume_id'])) { - $result->setVolumeId($resultArray['volume_id']); - $result->setLocalId($resultArray['local_id']); - } elseif (isset($resultArray['id'])) { - $result->setId($resultArray['id']); - $result->setSubType($resultArray['subType']); - } elseif (isset($resultArray['sticker_set_id'])) { - $result->setStickerSetId($resultArray['sticker_set_id']); - $result->setStickerSetVersion($resultArray['sticker_set_version']); - $result->setSubType($resultArray['subType']); - } - } elseif (isset($resultArray['id'])) { - $result->setId($resultArray['id']); - } + $orig = $fileId; + $fileId = rleDecode(base64urlDecode($fileId)); - return $result; + /** @var int */ + $typeId = \unpack('V', $fileId)[1]; + $type = UniqueFileIdType::from($typeId); + $url = null; + + $subType = null; + $id = null; + $fileId = \substr($fileId, 4); + $volume_id = null; + $local_id = null; + $sticker_set_id = null; + $sticker_set_version = null; + if ($type === UniqueFileIdType::WEB) { + $res = \fopen('php://memory', 'rw+b'); + \assert($res !== false); + \fwrite($res, $fileId); + \fseek($res, 0); + $fileId = $res; + $url = readTLString($fileId); + + $l = \fstat($fileId)['size'] - \ftell($fileId); + \trigger_error("Unique file ID $orig has $l bytes of leftover data"); + } elseif (\strlen($fileId) === 12) { + // Legacy photos + $volume_id = unpackLong(\substr($fileId, 0, 8)); + $local_id = unpackInt(\substr($fileId, 8)); + } elseif (\strlen($fileId) === 9) { + // Dialog photos/thumbnails + $id = unpackLong($fileId); + $subType = \ord($fileId[8]); + } elseif (\strlen($fileId) === 13) { + // Stickerset ID/version + $subType = \ord($fileId[0]); + $sticker_set_id = unpackLong(\substr($fileId, 1, 8)); + $sticker_set_version = unpackInt(\substr($fileId, 9)); + } elseif (\strlen($fileId) === 8) { + // Any other document + $id = unpackLong($fileId); + } else { + $l = \strlen($fileId); + \trigger_error("Unique file ID $orig has $l bytes of leftover data"); + } + return new self( + type: $type, + id: $id, + subType: $subType, + volumeId: $volume_id, + localId: $local_id, + stickerSetId: $sticker_set_id, + stickerSetVersion: $sticker_set_version, + url: $url + ); } /** @@ -164,287 +199,45 @@ final class UniqueFileId */ public static function fromFileId(FileId $fileId): self { - $result = new self(); - $result->setType($fileId->getType()->toUnique()); - if ($result->hasUrl()) { - $result->setType(UniqueFileIdType::WEB); + if ($fileId->url !== null) { + return new self( + UniqueFileIdType::WEB, + url: $fileId->url + ); } - if ($result->getType() === UniqueFileIdType::WEB) { - $result->setUrl($fileId->getUrl()); - } elseif ($result->getType() === UniqueFileIdType::PHOTO) { - if ($fileId->hasVolumeId()) { - $result->setVolumeId($fileId->getVolumeId()); - $result->setLocalId($fileId->getLocalId()); - } elseif ($fileId->hasId()) { - $result->setId($fileId->getId()); - $photoSize = $fileId->getPhotoSizeSource(); - if ($photoSize instanceof PhotoSizeSourceThumbnail) { - $type = $photoSize->getThumbType(); - if ($type === 'a') { - $type = \chr(0); - } elseif ($type === 'c') { - $type = \chr(1); - } else { - $type = \chr(\ord($type)+5); - } - $result->setSubType(\ord($type)); - } elseif ($photoSize instanceof PhotoSizeSourceDialogPhoto) { - $result->setSubType($photoSize->isSmallDialogPhoto() ? 0 : 1); - } elseif ($photoSize instanceof PhotoSizeSourceStickersetThumbnailVersion) { - $result->setSubType(2); - $result->setStickerSetId($photoSize->getStickerSetId()); - $result->setStickerSetVersion($photoSize->getStickerSetVersion()); + $type = $fileId->type->toUnique(); + if ($type === UniqueFileIdType::PHOTO) { + $photoSize = $fileId->photoSizeSource; + $subType = null; + if ($photoSize instanceof PhotoSizeSourceThumbnail) { + $subType = \ord($photoSize->thumbType); + if ($subType === 97) { + $subType = 0; + } elseif ($subType === 99) { + $subType = 1; + } else { + $subType = $subType+5; } + $subType = $subType; + } elseif ($photoSize instanceof PhotoSizeSourceDialogPhoto) { + $subType = $photoSize->isSmallDialogPhoto() ? 0 : 1; + } elseif ($photoSize instanceof PhotoSizeSourceStickersetThumbnailVersion) { + return new self( + $type, + $fileId->id, + 2, + stickerSetId: $photoSize->stickerSetId, + stickerSetVersion: $photoSize->stickerSetVersion, + ); } - } elseif ($fileId->hasId()) { - $result->setId($fileId->getId()); + return new self( + $type, + $fileId->id, + $subType, + $fileId->volumeId, + $fileId->localId, + ); } - - return $result; - } - - /** - * Get unique file type. - * - */ - public function getType(): UniqueFileIdType - { - return $this->type; - } - - /** - * Set file type. - * - * @param UniqueFileIdType $type File type. - * - */ - public function setType(UniqueFileIdType $type): self - { - $this->type = $type; - - return $this; - } - - /** - * Get file ID. - * - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * Set file ID. - * - * @param int $id File ID. - * - */ - public function setId(int $id): self - { - $this->id = $id; - - return $this; - } - - /** - * Check if has ID. - * - * @return boolean - */ - public function hasId(): bool - { - return isset($this->id); - } - - /** - * Get photo volume ID. - * - * @return int - */ - public function getVolumeId() - { - return $this->volumeId; - } - - /** - * Set photo volume ID. - * - * @param int $volumeId Photo volume ID. - * - */ - public function setVolumeId(int $volumeId): self - { - $this->volumeId = $volumeId; - - return $this; - } - /** - * Check if has volume ID. - * - * @return boolean - */ - public function hasVolumeId(): bool - { - return isset($this->volumeId); - } - - /** - * Get photo local ID. - * - */ - public function getLocalId(): int - { - return $this->localId; - } - - /** - * Set photo local ID. - * - * @param int $localId Photo local ID. - * - */ - public function setLocalId(int $localId): self - { - $this->localId = $localId; - - return $this; - } - - /** - * Check if has local ID. - * - * @return boolean - */ - public function hasLocalId(): bool - { - return isset($this->localId); - } - - /** - * Get weblocation URL. - * - */ - public function getUrl(): string - { - return $this->url; - } - - /** - * Set weblocation URL. - * - * @param string $url Weblocation URL - * - */ - public function setUrl(string $url): self - { - $this->url = $url; - - return $this; - } - - /** - * Check if has weblocation URL. - * - * @return boolean - */ - public function hasUrl(): bool - { - return isset($this->url); - } - - /** - * Get photo subtype. - * - */ - public function getSubType(): int - { - return $this->subType; - } - - /** - * Has photo subtype? - * - */ - public function hasSubType(): bool - { - return isset($this->subType); - } - - /** - * Set photo subtype. - * - * @param int $subType Photo subtype - * - */ - public function setSubType(int $subType): self - { - $this->subType = $subType; - - return $this; - } - - /** - * Get sticker set ID. - * - * @return int - */ - public function getStickerSetId() - { - return $this->stickerSetId; - } - - /** - * Has sticker set ID? - * - */ - public function hasStickerSetId(): bool - { - return isset($this->stickerSetId); - } - - /** - * Set sticker set ID. - * - * @param int $stickerSetId Sticker set ID - * - */ - public function setStickerSetId(int $stickerSetId): self - { - $this->stickerSetId = $stickerSetId; - - return $this; - } - - /** - * Get sticker set version. - * - */ - public function getStickerSetVersion(): int - { - return $this->stickerSetVersion; - } - - /** - * Has sticker set version. - * - */ - public function hasStickerSetVersion(): bool - { - return isset($this->stickerSetVersion); - } - - /** - * Set sticker set version. - * - * @param int $stickerSetVersion Sticker set version - * - */ - public function setStickerSetVersion(int $stickerSetVersion): self - { - $this->stickerSetVersion = $stickerSetVersion; - - return $this; + return new self($type, $fileId->id); } } diff --git a/src/functions.php b/src/functions.php index 97a1828..6faecf8 100644 --- a/src/functions.php +++ b/src/functions.php @@ -4,92 +4,45 @@ namespace danog\Decoder; const WEB_LOCATION_FLAG = 1 << 24; const FILE_REFERENCE_FLAG = 1 << 25; -const LONG = PHP_INT_SIZE === 8 ? 'Q' : 'l2'; -/** @psalm-suppress UnusedVariable */ $BIG_ENDIAN = \pack('L', 1) === \pack('N', 1); /** * Unpack long properly, returns an actual number in any case. * - * @param string $field Field to unpack + * @internal * - * @return string|int + * @param string $field Field to unpack */ -function unpackLong(string $field) +function unpackLong(string $field): int { - if (PHP_INT_SIZE === 8) { - /** @psalm-suppress InvalidGlobal */ - global $BIG_ENDIAN; // Evil - return \unpack('q', $BIG_ENDIAN ? \strrev($field) : $field)[1]; - } - if (\class_exists(\tgseclib\Math\BigInteger::class)) { - return (string) new \tgseclib\Math\BigInteger(\strrev($field), -256); - } - if (\class_exists(\phpseclib\Math\BigInteger::class)) { - return (string) new \phpseclib\Math\BigInteger(\strrev($field), -256); - } - throw new \Error('Please install phpseclib to unpack bot API file IDs'); + global $BIG_ENDIAN; // Evil + /** @psalm-suppress MixedReturnStatement */ + return \unpack('q', $BIG_ENDIAN ? \strrev($field) : $field)[1]; +} +/** + * Unpack integer. + * @internal + * + * @param string $field Field to unpack + */ +function unpackInt(string $field): int +{ + /** @psalm-suppress MixedReturnStatement */ + return \unpack('l', $field)[1]; } /** * Pack string long. * + * @internal * */ -function packLongBig(string|int $field): string +function packLong(int $field): string { - if (PHP_INT_SIZE === 8) { - /** @psalm-suppress InvalidGlobal */ - global $BIG_ENDIAN; // Evil - $res = \pack('q', $field); - return $BIG_ENDIAN ? \strrev($res) : $res; - } - - if (\class_exists(\tgseclib\Math\BigInteger::class)) { - return (new \tgseclib\Math\BigInteger($field))->toBytes(); - } - if (\class_exists(\phpseclib\Math\BigInteger::class)) { - return (new \phpseclib\Math\BigInteger($field))->toBytes(); - } - throw new \Error('Please install phpseclib to unpack bot API file IDs'); + global $BIG_ENDIAN; // Evil + $res = \pack('q', $field); + return $BIG_ENDIAN ? \strrev($res) : $res; } -/** - * Fix long parameters in case of 32 bit systems. - * - * @param array $params Parameters - * @param string $field 64-bit field - * - * @return void - */ -function fixLong(array &$params, string $field) -{ - if (PHP_INT_SIZE === 8) { - return; - } - $params[$field] = [ - $params[$field.'1'], - $params[$field.'2'], - ]; - unset($params[$field.'1'], $params[$field.'2']); -} - -/** - * Encode long to string. - * - * @param string|int|int[] $fields Fields to encode - * - */ -function packLong(string|int|array $fields): string -{ - if (\is_string($fields)) { // Already encoded, we hope - return $fields; - } - if (PHP_INT_SIZE === 8) { - return \pack(LONG, $fields); - } - return \pack(LONG, ...$fields); -} - /** * Base64URL decode. * @@ -195,7 +148,7 @@ function posmod(int $a, int $b): int /** * Read TL string. * - * @param mixed $stream Byte stream + * @param resource $stream Byte stream * * @internal * @@ -207,6 +160,7 @@ function readTLString(mixed $stream): string throw new \InvalidArgumentException("Length too big!"); } if ($l === 254) { + /** @var int */ $long_len = \unpack('V', \stream_get_contents($stream, 3).\chr(0))[1]; $x = \stream_get_contents($stream, $long_len); $resto = posmod(-$long_len, 4); @@ -229,6 +183,7 @@ function readTLString(mixed $stream): string * * @param string $string String * + * @internal */ function packTLString(string $string): string { @@ -246,186 +201,3 @@ function packTLString(string $string): string } return $concat; } - -/** - * Internal decode function. - * - * I know that you will use this directly giuseppe - * - * @param string $fileId Bot API file ID - * - * @internal - * - */ -function internalDecode(string $fileId): array -{ - $orig = $fileId; - $fileId = rleDecode(base64urlDecode($fileId)); - $result = []; - $result['version'] = \ord($fileId[\strlen($fileId) - 1]); - $result['subVersion'] = $result['version'] === 4 ? \ord($fileId[\strlen($fileId) - 2]) : 0; - - $result += \unpack('VtypeId/Vdc_id', $fileId); - $result['hasReference'] = (bool) ($result['typeId'] & FILE_REFERENCE_FLAG); - $result['hasWebLocation'] = (bool) ($result['typeId'] & WEB_LOCATION_FLAG); - $result['typeId'] &= ~FILE_REFERENCE_FLAG; - $result['typeId'] &= ~WEB_LOCATION_FLAG; - $result['type'] = FileIdType::from($result['typeId']); - $res = \fopen('php://memory', 'rw+b'); - \fwrite($res, \substr($fileId, 8)); - \fseek($res, 0); - $fileId = $res; - - if ($result['hasReference']) { - $result['fileReference'] = readTLString($fileId); - } - if ($result['hasWebLocation']) { - $result['url'] = readTLString($fileId); - $result['access_hash'] = \unpack(LONG.'access_hash', \stream_get_contents($fileId, 8)); - fixLong($result, 'access_hash'); - return $result; - } - - $result += \unpack(LONG.'id/'.LONG.'access_hash', \stream_get_contents($fileId, 16)); - fixLong($result, 'id'); - fixLong($result, 'access_hash'); - - if ($result['typeId'] <= FileIdType::PHOTO->value) { - $parsePhotoSize = function () use (&$result, &$fileId) { - $result['photosize_source'] = $result['subVersion'] >= 4 ? \unpack('V', \stream_get_contents($fileId, 4))[1] : 0; - switch ($result['photosize_source']) { - case PhotoSizeSourceType::LEGACY: - $result += \unpack(LONG.'secret', \stream_get_contents($fileId, 8)); - fixLong($result, 'secret'); - break; - case PhotoSizeSourceType::THUMBNAIL: - $result += \unpack('Vfile_type/athumbnail_type', \stream_get_contents($fileId, 8)); - break; - case PhotoSizeSourceType::DIALOGPHOTO_BIG: - case PhotoSizeSourceType::DIALOGPHOTO_SMALL: - $result['photo_size'] = $result['photosize_source'] === PhotoSizeSourceType::DIALOGPHOTO_SMALL ? 'photo_small' : 'photo_big'; - $result['dialog_id'] = unpackLong(\stream_get_contents($fileId, 8)); - $result['dialog_access_hash'] = \unpack(LONG, \stream_get_contents($fileId, 8))[1]; - fixLong($result, 'dialog_access_hash'); - break; - case PhotoSizeSourceType::STICKERSET_THUMBNAIL: - $result += \unpack(LONG.'sticker_set_id/'.LONG.'sticker_set_access_hash', \stream_get_contents($fileId, 16)); - fixLong($result, 'sticker_set_id'); - fixLong($result, 'sticker_set_access_hash'); - break; - - case PhotoSizeSourceType::FULL_LEGACY: - $result += \unpack(LONG.'volume_id/'.LONG.'secret/llocal_id', \stream_get_contents($fileId, 20)); - fixLong($result, 'volume_id'); - fixLong($result, 'secret'); - break; - case PhotoSizeSourceType::DIALOGPHOTO_BIG_LEGACY: - case PhotoSizeSourceType::DIALOGPHOTO_SMALL_LEGACY: - $result['photo_size'] = $result['photosize_source'] === PhotoSizeSourceType::DIALOGPHOTO_SMALL_LEGACY ? 'photo_small' : 'photo_big'; - $result['dialog_id'] = unpackLong(\stream_get_contents($fileId, 8)); - $result['dialog_access_hash'] = \unpack(LONG, \stream_get_contents($fileId, 8))[1]; - fixLong($result, 'dialog_access_hash'); - - $result += \unpack(LONG.'volume_id/llocal_id', \stream_get_contents($fileId, 12)); - fixLong($result, 'volume_id'); - break; - case PhotoSizeSourceType::STICKERSET_THUMBNAIL_LEGACY: - $result += \unpack(LONG.'sticker_set_id/'.LONG.'sticker_set_access_hash', \stream_get_contents($fileId, 16)); - fixLong($result, 'sticker_set_id'); - fixLong($result, 'sticker_set_access_hash'); - - $result += \unpack(LONG.'volume_id/llocal_id', \stream_get_contents($fileId, 12)); - fixLong($result, 'volume_id'); - break; - - case PhotoSizeSourceType::STICKERSET_THUMBNAIL_VERSION: - $result += \unpack(LONG.'sticker_set_id/'.LONG.'sticker_set_access_hash/lsticker_version', \stream_get_contents($fileId, 20)); - fixLong($result, 'sticker_set_id'); - fixLong($result, 'sticker_set_access_hash'); - break; - } - }; - if ($result['subVersion'] >= 32) { - $parsePhotoSize(); - } else { - $result += \unpack(LONG.'volume_id', \stream_get_contents($fileId, 8)); - fixLong($result, 'volume_id'); - - if ($result['subVersion'] >= 22) { - $parsePhotoSize(); - $result += \unpack('llocal_id', \stream_get_contents($fileId, 4)); - } else { - $result += \unpack(LONG.'secret/llocal_id', \stream_get_contents($fileId, 12)); - fixLong($result, 'volume_id'); - fixLong($result, 'secret'); - } - } - } - $l = \fstat($fileId)['size'] - \ftell($fileId); - $l -= $result['version'] >= 4 ? 2 : 1; - if ($l > 0) { - \trigger_error("File ID $orig has $l bytes of leftover data"); - } - return $result; -} -/** - * Internal decode function. - * - * I know that you will use this directly giuseppe - * - * @param string $fileId Bot API file ID - * - * @internal - * - */ -function internalDecodeUnique(string $fileId): array -{ - $orig = $fileId; - $fileId = rleDecode(base64urlDecode($fileId)); - - $result = \unpack('VtypeId', $fileId); - $result['type'] = UniqueFileIdType::from($result['typeId']); - - $fileId = \substr($fileId, 4); - if ($result['typeId'] === UniqueFileIdType::WEB) { - $res = \fopen('php://memory', 'rw+b'); - \fwrite($res, $fileId); - \fseek($res, 0); - $fileId = $res; - $result['url'] = readTLString($fileId); - - $l = \fstat($fileId)['size'] - \ftell($fileId); - } elseif (\strlen($fileId) === 12) { - // Legacy photos - $result += \unpack(LONG.'volume_id/llocal_id', $fileId); - fixLong($result, 'volume_id'); - - $l = 0; - } elseif (\strlen($fileId) === 9) { - // Dialog photos/thumbnails - $result += \unpack(LONG.'id/CsubType', $fileId); - fixLong($result, 'id'); - - $l = 0; - } elseif (\strlen($fileId) === 13) { - // Stickerset ID/version - $result += \unpack('CsubType/'.LONG.'sticker_set_id/lsticker_set_version', $fileId); - fixLong($result, 'sticker_set_id'); - - $l = 0; - } elseif (\strlen($fileId) === 8) { - // Any other document - $result += \unpack(LONG.'id', $fileId); - fixLong($result, 'id'); - - $l = 0; - } else { - $l = \strlen($fileId); - } - if ($l > 0) { - \trigger_error("Unique file ID $orig has $l bytes of leftover data"); - } - - \assert($result !== false); - return $result; -}