fix(file-id): updated file id computation for layer 130

This commit is contained in:
teidesu 2021-06-26 17:13:32 +03:00
parent 090f42e559
commit f520470fad
8 changed files with 365 additions and 33 deletions

View file

@ -28,6 +28,8 @@ export class Photo extends FileLocation {
/** Biggest available photo height */ /** Biggest available photo height */
readonly height: number readonly height: number
private _bestSize?: tl.RawPhotoSize | tl.RawPhotoSizeProgressive
constructor(client: TelegramClient, raw: tl.RawPhoto) { constructor(client: TelegramClient, raw: tl.RawPhoto) {
const location = { const location = {
_: 'inputPhotoFileLocation', _: 'inputPhotoFileLocation',
@ -38,6 +40,8 @@ export class Photo extends FileLocation {
} as tl.Mutable<tl.RawInputPhotoFileLocation> } as tl.Mutable<tl.RawInputPhotoFileLocation>
let size, width, height: number let size, width, height: number
let bestSize: tl.RawPhotoSize | tl.RawPhotoSizeProgressive
const progressive = raw.sizes.find( const progressive = raw.sizes.find(
(it) => it._ === 'photoSizeProgressive' (it) => it._ === 'photoSizeProgressive'
) as tl.RawPhotoSizeProgressive | undefined ) as tl.RawPhotoSizeProgressive | undefined
@ -46,6 +50,8 @@ export class Photo extends FileLocation {
size = Math.max(...progressive.sizes) size = Math.max(...progressive.sizes)
width = progressive.w width = progressive.w
height = progressive.h height = progressive.h
bestSize = progressive
} else { } else {
let max: tl.RawPhotoSize | null = null let max: tl.RawPhotoSize | null = null
for (const sz of raw.sizes) { for (const sz of raw.sizes) {
@ -59,6 +65,8 @@ export class Photo extends FileLocation {
size = max.size size = max.size
width = max.w width = max.w
height = max.h height = max.h
bestSize = max
} else { } else {
// does this happen at all? // does this happen at all?
throw new MtCuteArgumentError('Photo does not have any sizes') throw new MtCuteArgumentError('Photo does not have any sizes')
@ -66,6 +74,7 @@ export class Photo extends FileLocation {
} }
super(client, location, size, raw.dcId) super(client, location, size, raw.dcId)
this._bestSize = bestSize
this.raw = raw this.raw = raw
this.width = width this.width = width
this.height = height this.height = height
@ -106,6 +115,44 @@ export class Photo extends FileLocation {
return this.thumbnails.find((it) => it.raw.type === type) ?? null return this.thumbnails.find((it) => it.raw.type === type) ?? null
} }
private _fileId?: string
/**
* Get TDLib and Bot API compatible File ID
* representing this photo's best thumbnail.
*/
get fileId(): string {
if (!this._fileId) {
if (!this._bestSize) {
throw new MtCuteArgumentError(
'Cannot get File ID for this photo'
)
}
this._fileId = this.getThumbnail(this._bestSize.type)!.fileId
}
return this._fileId
}
private _uniqueFileId?: string
/**
* Get TDLib and Bot API compatible Unique File ID
* representing this photo's best thumbnail.
*/
get uniqueFileId(): string {
if (!this._uniqueFileId) {
if (!this._bestSize) {
throw new MtCuteArgumentError(
'Cannot get File ID for this photo'
)
}
this._uniqueFileId = this.getThumbnail(this._bestSize.type)!.uniqueFileId
}
return this._uniqueFileId
}
/** /**
* Input media generated from this object, * Input media generated from this object,
* to be used in {@link InputMediaLike} and * to be used in {@link InputMediaLike} and

View file

@ -168,8 +168,6 @@ export class Thumbnail extends FileLocation {
_: 'photo', _: 'photo',
id: this._media.id, id: this._media.id,
accessHash: this._media.accessHash, accessHash: this._media.accessHash,
volumeId: bigInt.zero,
localId: 0,
source: { source: {
_: 'thumbnail', _: 'thumbnail',
fileType: td.FileType.Photo, fileType: td.FileType.Photo,
@ -202,8 +200,13 @@ export class Thumbnail extends FileLocation {
? td.FileType.Photo ? td.FileType.Photo
: td.FileType.Thumbnail, : td.FileType.Thumbnail,
{ {
_: 'common', _: 'photo',
id: this._media.id, id: this._media.id,
source: {
_: 'thumbnail',
fileType: td.FileType.Photo,
thumbnailType: this.raw.type,
},
} }
) )
} }

View file

@ -76,8 +76,6 @@ export class ChatPhotoSize extends FileLocation {
_: 'photo', _: 'photo',
id: this.obj.photoId, id: this.obj.photoId,
accessHash: bigInt.zero, accessHash: bigInt.zero,
volumeId: bigInt.zero,
localId: 0,
source: { source: {
_: 'dialogPhoto', _: 'dialogPhoto',
big: this.big, big: this.big,
@ -97,12 +95,15 @@ export class ChatPhotoSize extends FileLocation {
*/ */
get uniqueFileId(): string { get uniqueFileId(): string {
if (!this._uniqueFileId) { if (!this._uniqueFileId) {
// todo: check how tdlib handles big/small photos here
this._uniqueFileId = toUniqueFileId( this._uniqueFileId = toUniqueFileId(
tdFileId.FileType.ProfilePhoto, tdFileId.FileType.ProfilePhoto,
{ {
_: 'common', _: 'photo',
id: this.obj.photoId, id: this.obj.photoId,
source: {
_: 'dialogPhoto',
big: this.big
} as any
} }
) )
} }

View file

@ -3,13 +3,14 @@ import { tdFileId, tdFileId as td } from './types'
import { parseFileId } from './parse' import { parseFileId } from './parse'
import { getBasicPeerType, markedPeerIdToBare } from '@mtcute/core' import { getBasicPeerType, markedPeerIdToBare } from '@mtcute/core'
import FileType = tdFileId.FileType import FileType = tdFileId.FileType
import bigInt from 'big-integer'
const EMPTY_BUFFER = Buffer.alloc(0) const EMPTY_BUFFER = Buffer.alloc(0)
type FileId = td.RawFullRemoteFileLocation type FileId = td.RawFullRemoteFileLocation
function dialogPhotoToInputPeer( function dialogPhotoToInputPeer(
dialog: td.RawPhotoSizeSourceDialogPhoto dialog: td.RawPhotoSizeSourceDialogPhoto | td.RawPhotoSizeSourceDialogPhotoLegacy
): tl.TypeInputPeer { ): tl.TypeInputPeer {
const markedPeerId = dialog.id.toJSNumber() const markedPeerId = dialog.id.toJSNumber()
const peerType = getBasicPeerType(markedPeerId) const peerType = getBasicPeerType(markedPeerId)
@ -78,13 +79,16 @@ export function fileIdToInputFileLocation(
'Expected legacy photo to have file reference' 'Expected legacy photo to have file reference'
) )
// for some reason tdlib removed this thing altogether
// https://github.com/tdlib/td/commit/4bb76a7b6f47bf9e0d0d01a72aac579ec73557ee#diff-8cc4f7c60a8261a8cf782e7fb51b95105bfb08710a1c2b63f80a48263ae0fb9bL401
// still leaving this, but not sure if passing 0 as volume_id and local_id is correct at all lol
return { return {
_: 'inputPhotoLegacyFileLocation', _: 'inputPhotoLegacyFileLocation',
fileReference: fileId.fileReference, fileReference: fileId.fileReference,
id: loc.id, id: loc.id,
accessHash: loc.accessHash, accessHash: loc.accessHash,
volumeId: loc.volumeId, volumeId: bigInt.zero,
localId: loc.localId, localId: 0,
secret: loc.source.secret, secret: loc.source.secret,
} }
case 'thumbnail': case 'thumbnail':
@ -119,6 +123,9 @@ export function fileIdToInputFileLocation(
photoId: loc.id, photoId: loc.id,
} }
case 'stickerSetThumbnail': case 'stickerSetThumbnail':
// this was also removed from tdlib:
// https://github.com/tdlib/td/commit/4bb76a7b6f47bf9e0d0d01a72aac579ec73557ee#diff-8cc4f7c60a8261a8cf782e7fb51b95105bfb08710a1c2b63f80a48263ae0fb9bR432
// also leaving this one though.
return { return {
_: 'inputStickerSetThumb', _: 'inputStickerSetThumb',
stickerset: { stickerset: {
@ -126,7 +133,51 @@ export function fileIdToInputFileLocation(
id: loc.source.id, id: loc.source.id,
accessHash: loc.source.accessHash, accessHash: loc.source.accessHash,
}, },
thumbVersion: 0, // todo: check how tdlib stores this thumbVersion: 0,
}
case 'fullLegacy':
if (!fileId.fileReference)
throw new td.InvalidFileIdError(
'Expected legacy photo to have file reference'
)
return {
_: 'inputPhotoLegacyFileLocation',
fileReference: fileId.fileReference,
id: loc.id,
accessHash: loc.accessHash,
volumeId: loc.source.volumeId,
localId: loc.source.localId,
secret: loc.source.secret,
}
case 'dialogPhotoLegacy':
return {
_: 'inputPeerPhotoFileLocationLegacy',
big: loc.source.big,
peer: dialogPhotoToInputPeer(loc.source),
volumeId: loc.source.volumeId,
localId: loc.source.localId,
}
case 'stickerSetThumbnailLegacy':
return {
_: 'inputStickerSetThumbLegacy',
stickerset: {
_: 'inputStickerSetID',
id: loc.source.id,
accessHash: loc.source.accessHash,
},
volumeId: loc.source.volumeId,
localId: loc.source.localId,
}
case 'stickerSetThumbnailVersion':
return {
_: 'inputStickerSetThumb',
stickerset: {
_: 'inputStickerSetID',
id: loc.source.id,
accessHash: loc.source.accessHash,
},
thumbVersion: loc.source.version
} }
} }

View file

@ -58,6 +58,59 @@ function parsePhotoSizeSource(reader: BinaryReader): td.TypePhotoSizeSource {
id: reader.long(), id: reader.long(),
accessHash: reader.long(), accessHash: reader.long(),
} }
case 5 /* FULL_LEGACY */: {
const res: td.RawPhotoSizeSourceFullLegacy = {
_: 'fullLegacy',
volumeId: reader.long(),
secret: reader.long(),
localId: reader.int32()
}
if (res.localId < 0) {
throw new td.InvalidFileIdError('Wrong local_id (< 0)')
}
return res
}
case 6 /* DIALOG_PHOTO_SMALL_LEGACY */:
case 7 /* DIALOG_PHOTO_BIG_LEGACY */: {
const res: td.RawPhotoSizeSourceDialogPhotoLegacy = {
_: 'dialogPhotoLegacy',
big: variant === 7,
id: reader.long(),
accessHash: reader.long(),
volumeId: reader.long(),
localId: reader.int32()
}
if (res.localId < 0) {
throw new td.InvalidFileIdError('Wrong local_id (< 0)')
}
return res
}
case 8 /* STICKERSET_THUMBNAIL_LEGACY */: {
const res: td.RawPhotoSizeSourceStickerSetThumbnailLegacy = {
_: 'stickerSetThumbnailLegacy',
id: reader.long(),
accessHash: reader.long(),
volumeId: reader.long(),
localId: reader.int32()
}
if (res.localId < 0) {
throw new td.InvalidFileIdError('Wrong local_id (< 0)')
}
return res
}
case 9 /* STICKERSET_THUMBNAIL_VERSION */:
return {
_: 'stickerSetThumbnailVersion',
id: reader.long(),
accessHash: reader.long(),
version: reader.int32()
}
default: default:
throw new td.UnsupportedError( throw new td.UnsupportedError(
`Unsupported photo size source ${variant} (${reader.data.toString( `Unsupported photo size source ${variant} (${reader.data.toString(
@ -72,19 +125,70 @@ function parsePhotoFileLocation(
version: number version: number
): td.RawPhotoRemoteFileLocation { ): td.RawPhotoRemoteFileLocation {
// todo: check how tdlib handles volume ids // todo: check how tdlib handles volume ids
const id = reader.long()
const accessHash = reader.long()
let source: td.TypePhotoSizeSource
if (version >= 32) {
source = parsePhotoSizeSource(reader)
} else {
const volumeId = reader.long()
let localId = 0
if (version >= 22) {
source = parsePhotoSizeSource(reader)
localId = reader.int32()
} else {
source = {
_: 'fullLegacy',
secret: reader.long(),
localId: reader.int32(),
volumeId
}
}
switch (source._) {
case 'legacy':
source = {
_: 'fullLegacy',
secret: reader.long(),
localId: reader.int32(),
volumeId
}
break
case 'fullLegacy':
case 'thumbnail':
break
case 'dialogPhoto':
source = {
_: 'dialogPhotoLegacy',
id: source.id,
accessHash: source.accessHash,
big: source.big,
localId,
volumeId
}
break
case 'stickerSetThumbnail':
source = {
_: 'stickerSetThumbnailLegacy',
id: source.id,
accessHash: source.accessHash,
localId,
volumeId
}
break
default:
throw new td.InvalidFileIdError('Invalid PhotoSizeSource in legacy PhotoRemoteFileLocation')
}
}
return { return {
_: 'photo', _: 'photo',
id: reader.long(), id,
accessHash: reader.long(), accessHash,
volumeId: reader.long(), source
source:
version >= 22
? parsePhotoSizeSource(reader)
: {
_: 'legacy',
secret: reader.long(),
},
localId: reader.int32(),
} }
} }
@ -166,6 +270,7 @@ function fromPersistentIdV23(
} }
break break
case 'dialogPhoto': case 'dialogPhoto':
case 'dialogPhotoLegacy':
if (fileType !== td.FileType.ProfilePhoto) { if (fileType !== td.FileType.ProfilePhoto) {
throw new td.InvalidFileIdError( throw new td.InvalidFileIdError(
'Invalid FileType in PhotoRemoteFileLocation DialogPhoto' 'Invalid FileType in PhotoRemoteFileLocation DialogPhoto'
@ -173,6 +278,8 @@ function fromPersistentIdV23(
} }
break break
case 'stickerSetThumbnail': case 'stickerSetThumbnail':
case 'stickerSetThumbnailLegacy':
case 'stickerSetThumbnailVersion':
if (fileType !== td.FileType.Thumbnail) { if (fileType !== td.FileType.Thumbnail) {
throw new td.InvalidFileIdError( throw new td.InvalidFileIdError(
'Invalid FileType in PhotoRemoteFileLocation StickerSetThumbnail' 'Invalid FileType in PhotoRemoteFileLocation StickerSetThumbnail'

View file

@ -5,7 +5,7 @@ import FileType = tdFileId.FileType
type InputUniqueLocation = type InputUniqueLocation =
| Pick<td.RawWebRemoteFileLocation, '_' | 'url'> | Pick<td.RawWebRemoteFileLocation, '_' | 'url'>
| Pick<td.RawPhotoRemoteFileLocation, '_' | 'volumeId' | 'localId'> | Pick<td.RawPhotoRemoteFileLocation, '_' | 'id' | 'source'>
| Pick<td.RawCommonRemoteFileLocation, '_' | 'id'> | Pick<td.RawCommonRemoteFileLocation, '_' | 'id'>
/** /**
@ -76,12 +76,69 @@ export function toUniqueFileId(
let writer: BinaryWriter let writer: BinaryWriter
switch (inputLocation._) { switch (inputLocation._) {
case 'photo': case 'photo': {
writer = BinaryWriter.alloc(16) const source = inputLocation.source
writer.int32(type) switch (source._) {
writer.long(inputLocation.volumeId) case 'legacy': {
writer.int32(inputLocation.localId) // tdlib does not implement this
writer = BinaryWriter.alloc(16)
writer.int32(type)
writer.int32(100)
writer.long(source.secret)
break
}
case 'stickerSetThumbnail': {
// tdlib does not implement this
writer = BinaryWriter.alloc(24)
writer.int32(type)
writer.int32(150)
writer.long(source.id)
writer.long(source.accessHash)
break
}
case 'dialogPhoto': {
writer = BinaryWriter.alloc(13)
writer.int32(type)
writer.long(inputLocation.id)
writer.raw(Buffer.from([+source.big]))
// it doesn't matter to which Dialog the photo belongs
break
}
case 'thumbnail': {
writer = BinaryWriter.alloc(13)
let thumbType = source.thumbnailType.charCodeAt(0)
if (thumbType === 97 /* 'a' */) {
thumbType = 0
} else if (thumbType === 99 /* 'c' */) {
thumbType = 1
} else {
thumbType += 5
}
writer.int32(type)
writer.long(inputLocation.id)
writer.raw(Buffer.from([thumbType]))
break
}
case 'fullLegacy':
case 'dialogPhotoLegacy':
case 'stickerSetThumbnailLegacy':
writer = BinaryWriter.alloc(16)
writer.int32(type)
writer.long(source.volumeId)
writer.int32(source.localId)
break
case 'stickerSetThumbnailVersion':
writer = BinaryWriter.alloc(17)
writer.int32(type)
writer.raw(Buffer.from([2]))
writer.long(source.id)
writer.int32(source.version)
break
}
break break
}
case 'web': case 'web':
writer = BinaryWriter.alloc( writer = BinaryWriter.alloc(
Buffer.byteLength(inputLocation.url, 'utf-8') + 8 Buffer.byteLength(inputLocation.url, 'utf-8') + 8

View file

@ -46,7 +46,6 @@ export function toFileId(
// todo: check how tdlib handles volume ids // todo: check how tdlib handles volume ids
writer.long(loc.id) writer.long(loc.id)
writer.long(loc.accessHash) writer.long(loc.accessHash)
writer.long(loc.volumeId)
switch (loc.source._) { switch (loc.source._) {
case 'legacy': case 'legacy':
@ -68,9 +67,34 @@ export function toFileId(
writer.long(loc.source.id) writer.long(loc.source.id)
writer.long(loc.source.accessHash) writer.long(loc.source.accessHash)
break break
case 'fullLegacy':
writer.int32(5)
writer.long(loc.source.volumeId)
writer.long(loc.source.secret)
writer.int32(loc.source.localId)
break
case 'dialogPhotoLegacy':
writer.int32(loc.source.big ? 7 : 6)
writer.long(loc.source.id)
writer.long(loc.source.accessHash)
writer.long(loc.source.volumeId)
writer.int32(loc.source.localId)
break
case 'stickerSetThumbnailLegacy':
writer.int32(8)
writer.long(loc.source.id)
writer.long(loc.source.accessHash)
writer.long(loc.source.volumeId)
writer.int32(loc.source.localId)
break
case 'stickerSetThumbnailVersion':
writer.int32(8)
writer.long(loc.source.id)
writer.long(loc.source.accessHash)
writer.int32(loc.source.version)
break
} }
writer.int32(loc.localId)
break break
case 'common': case 'common':
writer.long(loc.id) writer.long(loc.id)

View file

@ -9,7 +9,7 @@ export namespace tdFileId {
export const WEB_LOCATION_FLAG = 1 << 24 export const WEB_LOCATION_FLAG = 1 << 24
export const FILE_REFERENCE_FLAG = 1 << 25 export const FILE_REFERENCE_FLAG = 1 << 25
export const CURRENT_VERSION = 31 export const CURRENT_VERSION = 32
/** /**
* An error occurred while parsing or serializing a File ID * An error occurred while parsing or serializing a File ID
@ -114,11 +114,55 @@ export namespace tdFileId {
readonly accessHash: Long readonly accessHash: Long
} }
/**
* This photo is a legacy photo containing
* volume_id, local_id and secret
*/
export interface RawPhotoSizeSourceFullLegacy {
readonly _: 'fullLegacy'
readonly volumeId: Long
readonly localId: number
readonly secret: Long
}
/**
* This photo is a legacy dialog photo
*/
export interface RawPhotoSizeSourceDialogPhotoLegacy
extends Omit<RawPhotoSizeSourceDialogPhoto, '_'> {
readonly _: 'dialogPhotoLegacy'
readonly volumeId: Long
readonly localId: number
}
/**
* This photo is a legacy sticker set thumbnail
*/
export interface RawPhotoSizeSourceStickerSetThumbnailLegacy
extends Omit<RawPhotoSizeSourceStickerSetThumbnail, '_'> {
readonly _: 'stickerSetThumbnailLegacy'
readonly volumeId: Long
readonly localId: number
}
/**
* This photo is a legacy sticker set identified by a version
*/
export interface RawPhotoSizeSourceStickerSetThumbnailVersion
extends Omit<RawPhotoSizeSourceStickerSetThumbnail, '_'> {
readonly _: 'stickerSetThumbnailVersion'
readonly version: number
}
export type TypePhotoSizeSource = export type TypePhotoSizeSource =
| RawPhotoSizeSourceLegacy | RawPhotoSizeSourceLegacy
| RawPhotoSizeSourceThumbnail | RawPhotoSizeSourceThumbnail
| RawPhotoSizeSourceDialogPhoto | RawPhotoSizeSourceDialogPhoto
| RawPhotoSizeSourceStickerSetThumbnail | RawPhotoSizeSourceStickerSetThumbnail
| RawPhotoSizeSourceFullLegacy
| RawPhotoSizeSourceDialogPhotoLegacy
| RawPhotoSizeSourceStickerSetThumbnailLegacy
| RawPhotoSizeSourceStickerSetThumbnailVersion
/** /**
* An external web file * An external web file
@ -139,9 +183,7 @@ export namespace tdFileId {
readonly _: 'photo' readonly _: 'photo'
readonly id: Long readonly id: Long
readonly accessHash: Long readonly accessHash: Long
readonly volumeId: Long
readonly source: TypePhotoSizeSource readonly source: TypePhotoSizeSource
readonly localId: number
} }
/** /**