Bot API file IDs v4

This commit is contained in:
Daniil Gentili 2020-01-16 20:19:03 +01:00
parent a9c35bd987
commit 170308d574
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
7 changed files with 230 additions and 45 deletions

View File

@ -85,7 +85,7 @@ class MTProto extends AsyncConstruct implements TLCallback
*
* @var int
*/
const V = 132;
const V = 134;
/**
* String release version.
*

View File

@ -608,7 +608,15 @@ trait Files
public function getFileInfo($constructor): \Generator
{
if (\is_string($constructor)) {
$constructor = $this->unpackFileId($constructor)['MessageMedia'];
$constructor = $this->unpackFileId($constructor);
if (isset($constructor['MessageMedia'])) {
$constructor = $constructor['MessageMedia'];
} elseif (isset($constructor['InputMedia'])) {
return $constructor;
} else {
$constructor = yield $this->getPwrChat($constructor['Chat'] ?? $constructor['User']);
$constructor = $constructor['photo'];
}
}
switch ($constructor['_']) {
case 'updateNewMessage':
@ -657,7 +665,11 @@ trait Files
public function getDownloadInfo($message_media): \Generator
{
if (\is_string($message_media)) {
$message_media = $this->unpackFileId($message_media)['MessageMedia'];
$message_media = $this->unpackFileId($message_media);
if (isset($message_media['InputFileLocation'])) {
return $message_media;
}
$message_media = $message_media['MessageMedia'] ?? $message_media['User'] ?? $message_media['Chat'];
}
if (!isset($message_media['_'])) {
return $message_media;
@ -765,11 +777,17 @@ trait Files
case 'updateUserPhoto':
$res = yield $this->getDownloadInfo($message_media['photo']);
if (\is_array($message_media) && ($message_media['min'] ?? false) && isset($message_media['access_hash'])) { // bot API file ID
$message_media['min'] = false;
$peer = $this->genAll($message_media)['InputPeer'];
} else {
$peer = (yield $this->getInfo($message_media))['InputPeer'];
}
$res['InputFileLocation'] = [
'_' => 'inputPeerPhotoFileLocation',
'big' => true,
'big' => $res['big'],
'dc_id' => $res['InputFileLocation']['dc_id'],
'peer' => (yield $this->getInfo($message_media))['InputPeer'],
'peer' => $peer,
'volume_id' => $res['InputFileLocation']['volume_id'],
'local_id' => $res['InputFileLocation']['local_id'],
// The peer field will be added later
@ -778,10 +796,12 @@ trait Files
case 'userProfilePhoto':
case 'chatPhoto':
$size = $message_media['photo_big'];
$size = $message_media['photo_big'] ?? $message_media['photo_small'];
$res = yield $this->getDownloadInfo($size);
$res['big'] = isset($message_media['photo_big']);
$res['InputFileLocation']['dc_id'] = $message_media['dc_id'];
return $res;
case 'photoStrippedSize':
$res['size'] = \strlen($message_media['bytes']);
@ -1213,6 +1233,7 @@ trait Files
$time = 0;
$speed = 0;
$origCb = $cb;
$cb = function () use ($cb, $count, &$time, &$speed) {
static $cur = 0;
$cur++;
@ -1224,7 +1245,11 @@ trait Files
$params[0]['previous_promise'] = new Success(true);
$start = \microtime(true);
$size = yield $this->downloadPart($message_media, $cdn, $datacenter, $old_dc, $ige, $cb, \array_shift($params), $callable, $seekable);
$size = yield $this->downloadPart($message_media, $cdn, $datacenter, $old_dc, $ige, $cb, $initParam = \array_shift($params), $callable, $seekable);
if ($initParam['part_end_at'] - $initParam['part_start_at'] !== $size) { // Premature end for undefined length files
$origCb(100);
return true;
}
if ($params) {
$previous_promise = new Success(true);
@ -1237,13 +1262,17 @@ trait Files
if ($res) {
$size += $res;
}
return (bool) $res;
});
$promises[] = $previous_promise;
if (!($key % $parallel_chunks)) { // 20 mb at a time, for a typical bandwidth of 1gbps
yield \danog\MadelineProto\Tools::all($promises);
$promises = [];
$res = array_sum(yield \danog\MadelineProto\Tools::all($promises));
if ($res !== count($promises)) {
$promises = [];
break;
}
$time = \microtime(true) - $start;
$speed = (int) (($size * 8) / $time) / 1000000;
@ -1264,6 +1293,10 @@ trait Files
$this->clearCdnHashes($message_media['file_token']);
}
if (!isset($message_media['size'])) {
$origCb(100);
}
return true;
}
@ -1371,6 +1404,10 @@ trait Files
$res = yield $this->methodCallAsyncRead('upload.getFile', $basic_param + $offset, ['heavy' => true, 'file' => true, 'FloodWaitLimit' => 0, 'datacenter' => $datacenter]);
}
if ($res['bytes'] === '') {
return 0;
}
if (isset($message_media['cdn_key'])) {
$ivec = \substr($message_media['cdn_iv'], 0, 12).\pack('N', $offset['offset'] >> 4);
$res['bytes'] = $this->ctrEncrypt($res['bytes'], $message_media['cdn_key'], $ivec);

View File

@ -640,7 +640,7 @@ trait PeerHandler
$res = [$this->TL->getConstructors()->findByPredicate($constructor['_'])['type'] => $constructor];
switch ($constructor['_']) {
case 'user':
if ($constructor['self']) {
if ($constructor['self'] ?? false) {
$res['InputPeer'] = ['_' => 'inputPeerSelf'];
$res['InputUser'] = ['_' => 'inputUserSelf'];
} elseif (isset($constructor['access_hash'])) {
@ -656,7 +656,7 @@ trait PeerHandler
$res['InputNotifyPeer'] = ['_' => 'inputNotifyPeer', 'peer' => $res['InputPeer']];
$res['user_id'] = $constructor['id'];
$res['bot_api_id'] = $constructor['id'];
$res['type'] = $constructor['bot'] ? 'bot' : 'user';
$res['type'] = ($constructor['bot'] ?? false) ? 'bot' : 'user';
break;
case 'chat':
case 'chatForbidden':
@ -683,7 +683,7 @@ trait PeerHandler
$res['InputChannel'] = ['_' => 'inputChannel', 'channel_id' => $constructor['id'], 'access_hash' => $constructor['access_hash'], 'min' => $constructor['min']];
$res['channel_id'] = $constructor['id'];
$res['bot_api_id'] = $this->toSupergroup($constructor['id']);
$res['type'] = $constructor['megagroup'] ? 'supergroup' : 'channel';
$res['type'] = ($constructor['megagroup'] ?? false) ? 'supergroup' : 'channel';
break;
case 'channelForbidden':
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
@ -775,7 +775,7 @@ trait PeerHandler
}
}
if (isset($full['full']['profile_photo']['sizes'])) {
$res['photo'] = yield $this->photosizeToBotAPI(\end($full['full']['profile_photo']['sizes']), $full['full']['profile_photo']);
$res['photo'] = $full['full']['profile_photo'];
}
break;
case 'chat':
@ -793,7 +793,7 @@ trait PeerHandler
$res['all_members_are_administrators'] = !$res['admins_enabled'];
}
if (isset($full['full']['chat_photo']['sizes'])) {
$res['photo'] = yield $this->photosizeToBotAPI(\end($full['full']['chat_photo']['sizes']), $full['full']['chat_photo']);
$res['photo'] = $full['full']['chat_photo'];
}
if (isset($full['full']['exported_invite']['link'])) {
$res['invite'] = $full['full']['exported_invite']['link'];
@ -815,7 +815,7 @@ trait PeerHandler
}
}
if (isset($full['full']['chat_photo']['sizes'])) {
$res['photo'] = yield $this->photosizeToBotAPI(\end($full['full']['chat_photo']['sizes']), $full['full']['chat_photo']);
$res['photo'] = $full['full']['chat_photo'];
}
if (isset($full['full']['exported_invite']['link'])) {
$res['invite'] = $full['full']['exported_invite']['link'];

View File

@ -244,7 +244,7 @@ class Magic
self::$has_thread = \class_exists('\\Thread') && \method_exists('\\Thread', 'getCurrentThread');
self::$BIG_ENDIAN = \pack('L', 1) === \pack('N', 1);
self::$bigint = PHP_INT_SIZE < 8;
self::$ipv6 = (bool) \strlen(@\file_get_contents('http://v6.ipv6-test.com/api/myip.php', false, \stream_context_create(['http' => ['timeout' => 1]]))) > 0;
self::$ipv6 = (bool) \strlen(@\file_get_contents('http://ipv6.google.com', false, \stream_context_create(['http' => ['timeout' => 1]]))) > 0;
\preg_match('/const V = (\\d+);/', @\file_get_contents('https://raw.githubusercontent.com/danog/MadelineProto/master/src/danog/MadelineProto/MTProto.php'), $matches);
if (isset($matches[1]) && \danog\MadelineProto\MTProto::V < (int) $matches[1]) {
throw new \danog\MadelineProto\Exception(\hex2bin(\danog\MadelineProto\Lang::$current_lang['v_error']), 0, null, 'MadelineProto', 1);

View File

@ -19,9 +19,14 @@
namespace danog\MadelineProto\TL\Conversion;
use danog\MadelineProto\Magic;
use danog\MadelineProto\MTProtoTools\PeerHandler;
use danog\MadelineProto\Tools;
use tgseclib\Math\BigInteger;
trait BotAPIFiles
{
private function photosizeToBotAPI($photoSize, $photo, $thumbnail = false)
private function photosizeToBotAPI($photoSize, $photo, $thumbnail = false): \Generator
{
$ext = '.jpg';//$this->getExtensionFromLocation(['_' => 'inputFileLocation', 'volume_id' => $photoSize['location']['volume_id'], 'local_id' => $photoSize['location']['local_id'], 'secret' => $photoSize['location']['secret'], 'dc_id' => $photoSize['location']['dc_id']], '.jpg');
$photoSize['location']['access_hash'] = $photo['access_hash'] ?? 0;
@ -50,25 +55,134 @@ trait BotAPIFiles
*/
public function unpackFileId(string $file_id): array
{
$file_id = \danog\MadelineProto\Tools::rleDecode(\danog\MadelineProto\Tools::base64urlDecode($file_id));
if ($file_id[\strlen($file_id) - 1] !== \chr(2)) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['last_byte_invalid']);
$file_id = Tools::rleDecode(Tools::base64urlDecode($file_id));
$version = \ord($file_id[\strlen($file_id) - 1]);
$subVersion = $version === 4 ? \ord($file_id[\strlen($file_id) - 2]) : 0;
$this->logger("Got file ID with version $version.$subVersion");
if (!\in_array($version, [2, 4])) {
throw new Exception("Invalid bot API file ID version $version");
}
$res = \fopen('php://memory', 'rw+b');
\fwrite($res, $file_id);
\fseek($res, 0);
$file_id = $res;
$deserialized = $this->TL->deserialize($file_id);
$res = ['type' => \str_replace('bot_', '', $deserialized['_'])];
if (\in_array($res['type'], ['profile_photo', 'thumbnail', 'photo'])) {
$deserialized['secret'] = 0;
$deserialized['photosize_source'] = $version >= 4 ? Tools::unpackSignedInt(\stream_get_contents($file_id, 4)) : 0;
// Legacy, Thumbnail, DialogPhotoSmall, DialogPhotoBig, StickerSetThumbnail
switch ($deserialized['photosize_source']) {
case 0:
$deserialized['secret'] = \stream_get_contents($file_id, 8);
break;
case 1:
$deserialized['file_type'] = Tools::unpackSignedInt(\stream_get_contents($file_id, 4));
$deserialized['thumbnail_type'] = chr(Tools::unpackSignedInt(\stream_get_contents($file_id, 4)));
break;
case 2:
case 3:
$deserialized['photo_size'] = $deserialized['photosize_source'] === 2 ? 'photo_small' : 'photo_big';
$deserialized['dialog_id'] = (string) new BigInteger(strrev(\stream_get_contents($file_id, 8)), -256);
$deserialized['dialog_access_hash'] = \stream_get_contents($file_id, 8);
break;
case 4:
$deserialized['sticker_set_id'] = Tools::unpackSignedInt(\stream_get_contents($file_id, 4));
$deserialized['sticker_set_access_hash'] = \stream_get_contents($file_id, 8);
break;
}
$deserialized['local_id'] = Tools::unpackSignedInt(\stream_get_contents($file_id, 4));
}
switch ($deserialized['_']) {
case 'bot_profile_photo':
if ($deserialized['dialog_id'] < 0) {
$res['Chat'] = [
'_' => $deserialized['dialog_id'] < -1000000000000 ? 'channel' : 'chat',
'id' => $deserialized['dialog_id'] < -1000000000000 ? PeerHandler::fromSupergroup($deserialized['dialog_id']) : -$deserialized['dialog_id'],
'access_hash' => $deserialized['dialog_access_hash'],
'photo' => [
'_' => 'chatPhoto',
'dc_id' => $deserialized['dc_id'],
$deserialized['photo_size'] => [
'_' => 'fileLocationToBeDeprecated',
'volume_id' => $deserialized['volume_id'],
'local_id' => $deserialized['local_id'],
]
],
'min' => true
];
return $res;
}
$res['User'] = [
'_' => 'user',
'id' => $deserialized['dialog_id'],
'access_hash' => $deserialized['dialog_access_hash'],
'photo' => [
'_' => 'userProfilePhoto',
'dc_id' => $deserialized['dc_id'],
'photo_id' => $deserialized['id'],
$deserialized['photo_size'] => [
'_' => 'fileLocationToBeDeprecated',
'volume_id' => $deserialized['volume_id'],
'local_id' => $deserialized['local_id'],
]
],
'min' => true
];
return $res;
case 'bot_thumbnail':
$res['InputFileLocation'] = [
'_' => $deserialized['file_type'] >= 3 ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation',
'id' => $deserialized['id'],
'access_hash' => $deserialized['access_hash'],
'file_reference' => '',
'thumb_size' => (string) $deserialized['thumbnail_type']
];
$res['name'] = $deserialized['id'].'_'.$deserialized['thumbnail_type'];
$res['ext'] = 'jpg';
$res['mime'] = 'image/jpeg';
$res['InputMedia'] = [
'_' => $deserialized['file_type'] >= 3 ? 'inputMediaDocument' : 'inputMediaPhoto',
'id' => [
'_' => $deserialized['file_type'] >= 3 ? 'inputDocument' : 'inputPhoto',
'id' => $deserialized['id'],
'access_hash' => $deserialized['access_hash'],
]
];
return $res;
case 'bot_photo':
$constructor = ['_' => 'photo', 'sizes' => [], 'dc_id' => $deserialized['dc_id']];
$constructor['id'] = $deserialized['id'];
$constructor['access_hash'] = $deserialized['access_hash'];
unset($deserialized['id'], $deserialized['access_hash'], $deserialized['_']);
$deserialized['_'] = 'fileLocation';
$constructor['sizes'][0] = ['_' => 'photoSize', 'location' => $deserialized];
$res['MessageMedia'] = ['_' => 'messageMediaPhoto', 'photo' => $constructor, 'caption' => ''];
if ($deserialized['photosize_source'] === 0) {
$constructor['id'] = $deserialized['id'];
$constructor['access_hash'] = $deserialized['access_hash'];
unset($deserialized['id'], $deserialized['access_hash']);
$deserialized['_'] = $deserialized['secret'] ? 'fileLocation' : 'fileLocationToBeDeprecated';
$constructor['sizes'][0] = ['_' => 'photoSize', 'type' => '', 'location' => $deserialized];
$res['MessageMedia'] = ['_' => 'messageMediaPhoto', 'photo' => $constructor, 'caption' => ''];
return $res;
}
$res['MessageMedia'] = [
'_' => 'photo',
'id' => $deserialized['id'],
'access_hash' => $deserialized['access_hash'],
'sizes' => [
[
'_' => 'photoSize',
'type' => $deserialized['thumbnail_type'],
'location' => [
'_' => 'fileLocationToBeDeprecated',
'local_id' => $deserialized['local_id'],
'volume_id' => $deserialized['local_id'],
]
]
],
'dc_id' => $deserialized['dc_id']
];
return $res;
case 'bot_voice':
unset($deserialized['_']);
@ -81,12 +195,6 @@ trait BotAPIFiles
$constructor = \array_merge($deserialized, ['_' => 'document', 'mime_type' => '', 'attributes' => [['_' => 'documentAttributeVideo', 'round_message' => false]]]);
$res['MessageMedia'] = ['_' => 'messageMediaDocument', 'document' => $constructor, 'caption' => ''];
return $res;
case 'bot_video_note':
unset($deserialized['_']);
$constructor = \array_merge($deserialized, ['_' => 'document', 'mime_type' => '', 'attributes' => [['_' => 'documentAttributeVideo', 'round_message' => true]]]);
$res['MessageMedia'] = ['_' => 'messageMediaDocument', 'document' => $constructor, 'caption' => ''];
return $res;
case 'bot_document':
unset($deserialized['_']);
@ -99,18 +207,24 @@ trait BotAPIFiles
$constructor = \array_merge($deserialized, ['_' => 'document', 'mime_type' => '', 'attributes' => [['_' => 'documentAttributeSticker']]]);
$res['MessageMedia'] = ['_' => 'messageMediaDocument', 'document' => $constructor, 'caption' => ''];
return $res;
case 'bot_gif':
unset($deserialized['_']);
$constructor = \array_merge($deserialized, ['_' => 'document', 'mime_type' => '', 'attributes' => [['_' => 'documentAttributeAnimated']]]);
$res['MessageMedia'] = ['_' => 'messageMediaDocument', 'document' => $constructor, 'caption' => ''];
return $res;
case 'bot_audio':
unset($deserialized['_']);
$constructor = \array_merge($deserialized, ['_' => 'document', 'mime_type' => '', 'attributes' => [['_' => 'documentAttributeAudio', 'voice' => false]]]);
$res['MessageMedia'] = ['_' => 'messageMediaDocument', 'document' => $constructor, 'caption' => ''];
return $res;
case 'bot_gif':
unset($deserialized['_']);
$constructor = \array_merge($deserialized, ['_' => 'document', 'mime_type' => '', 'attributes' => [['_' => 'documentAttributeAnimated']]]);
$res['MessageMedia'] = ['_' => 'messageMediaDocument', 'document' => $constructor, 'caption' => ''];
return $res;
case 'bot_video_note':
unset($deserialized['_']);
$constructor = \array_merge($deserialized, ['_' => 'document', 'mime_type' => '', 'attributes' => [['_' => 'documentAttributeVideo', 'round_message' => true]]]);
$res['MessageMedia'] = ['_' => 'messageMediaDocument', 'document' => $constructor, 'caption' => ''];
return $res;
default:
throw new Exception(\sprintf(\danog\MadelineProto\Lang::$current_lang['file_type_invalid'], $type));

View File

@ -19,7 +19,7 @@
namespace danog\MadelineProto\TL\Types;
class Bytes implements \JsonSerializable
class Bytes implements \JsonSerializable, \ArrayAccess
{
use \danog\Serializable;
private $bytes = [];
@ -43,4 +43,28 @@ class Bytes implements \JsonSerializable
{
return ['_' => 'bytes', 'bytes' => \base64_encode($this->bytes)];
}
public function offsetSet($name, $value)
{
if ($name === null) {
$this->bytes .= $value;
} else {
$this->bytes[$name] = $value;
}
}
public function offsetGet($name)
{
return $this->bytes[$name];
}
public function offsetUnset($name)
{
unset($this->bytes[$name]);
}
public function offsetExists($name)
{
return isset($this->bytes[$name]);
}
}

View File

@ -1,9 +1,19 @@
bot_thumbnail#0 dc_id:int id:long access_hash:long volume_id:long secret:long local_id:int = File;
bot_photo#2 dc_id:int id:long access_hash:long volume_id:long secret:long local_id:int = File;
bot_thumbnail#0 dc_id:int id:long access_hash:long volume_id:long = File;
bot_profile_photo#1 dc_id:int id:long access_hash:long volume_id:long = File;
bot_photo#2 dc_id:int id:long access_hash:long volume_id:long = File;
bot_voice#3 dc_id:int id:long access_hash:long = File;
bot_video#4 dc_id:int id:long access_hash:long = File;
bot_document#5 dc_id:int id:long access_hash:long = File;
bot_encrypted#6 dc_id:int id:long access_hash:long = File;
bot_temp#7 dc_id:int id:long access_hash:long = File;
bot_sticker#8 dc_id:int id:long access_hash:long = File;
bot_audio#9 dc_id:int id:long access_hash:long = File;
bot_gif#A dc_id:int id:long access_hash:long = File;
bot_encrypted_thumbnail#B dc_id:int id:long access_hash:long = File;
bot_wallpaper#C dc_id:int id:long access_hash:long = File;
bot_video_note#D dc_id:int id:long access_hash:long = File;
bot_secure_raw#F dc_id:int id:long access_hash:long = File;
bot_secure#10 dc_id:int id:long access_hash:long = File;
bot_background#11 dc_id:int id:long access_hash:long = File;
bot_size#12 dc_id:int id:long access_hash:long = File;