Refactor API

This commit is contained in:
Daniil Gentili 2024-04-01 17:30:50 +02:00
parent 464800a3c5
commit 9d9c253848
15 changed files with 545 additions and 1402 deletions

View File

@ -11,7 +11,7 @@
}
],
"require": {
"php": ">=8.1"
"php-64bit": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^9",

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="dev-master@ef3b018e89c4ffc157332c13e2ebf9cf22320d17">
<file src="src/functions.php">
<UnusedVariable>
<code><![CDATA[$BIG_ENDIAN]]></code>
</UnusedVariable>
</file>
</files>

View File

@ -1,11 +1,13 @@
<?xml version="1.0"?>
<psalm
errorLevel="5"
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorBaseline="psalm-baseline.xml"
findUnusedCode="true"
findUnusedPsalmSuppress="true"
ignoreInternalFunctionFalseReturn="true"
>
<projectFiles>
<directory name="src" />

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -19,77 +19,22 @@
namespace danog\Decoder\PhotoSizeSource;
use danog\Decoder\PhotoSizeSource;
use danog\Decoder\PhotoSizeSourceType;
/**
* Represents source of photosize.
*
* @api
*
* @extends PhotoSizeSource<PhotoSizeSourceDialogPhoto>
*/
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;
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
/**
* Photosize source class.
*
* This file is part of tg-file-decoder.
* tg-file-decoder is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* tg-file-decoder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with tg-file-decoder.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
* @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;
}
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
/**
* Photosize source class.
*
* This file is part of tg-file-decoder.
* tg-file-decoder is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* tg-file-decoder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with tg-file-decoder.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
* @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;
}
}

View File

@ -24,37 +24,11 @@ use danog\Decoder\PhotoSizeSource;
* Represents source of photosize.
*
* @api
*
* @extends PhotoSizeSource<PhotoSizeSourceLegacy>
*/
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,
) {
}
}

View File

@ -24,65 +24,21 @@ use danog\Decoder\PhotoSizeSource;
* Represents source of photosize.
*
* @api
*
* @extends PhotoSizeSource<PhotoSizeSourceStickersetThumbnail>
*/
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;
}
}

View File

@ -24,92 +24,26 @@ use danog\Decoder\PhotoSizeSource;
* Represents source of photosize.
*
* @api
*
* @extends PhotoSizeSource<PhotoSizeSourceStickersetThumbnailVersion>
*/
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;
}
}

View File

@ -25,62 +25,20 @@ use danog\Decoder\PhotoSizeSource;
* Represents source of photosize.
*
* @api
*
* @extends PhotoSizeSource<PhotoSizeSourceThumbnail>
*/
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,
) {
}
}

View File

@ -19,6 +19,9 @@
namespace danog\Decoder;
/**
* @internal Not for public use
*/
enum PhotoSizeSourceType: int
{
case LEGACY = 0;

View File

@ -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);
}
}

View File

@ -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;
}