UNPKG

@tgsnake/fileid

Version:

core framework for tgsnake for generating file id

298 lines (297 loc) 9.25 kB
/** * tgsnake - Telegram MTProto framework for nodejs. * Copyright (C) 2022 butthx <https://github.com/butthx> * * THIS FILE IS PART OF TGSNAKE * * tgsnake is a free software : you can redistribute it and/or modify * it under the terms of the MIT License as published. */ import { FileType, ThumbnailSource, FileTypeUniqueId, base64_url_decode, rle_decode, Writer, Reader, PHOTO_TYPES, Options, } from './index.ts'; import { Buffer } from 'node:buffer'; export class Decode implements Options { /** * The major version of bot api file id. Usually is 4. */ version!: number; /** * The minor version of bot api file id. Usually same with tdlib version or 32. */ subVersion!: number; /** * The data center id, where that file is stored. */ dcId!: number; /** * The enum/number of FileType. recommend to use enum. * ```ts * import { FileType, FileId } from "@tgsnake/fileid" * const fileId = FileId.encode({ * fileType : FileType.PHOTO * }) * ``` */ fileType!: FileType; /** * The id of file. */ id!: bigint; /** * The hash to access that file. */ accessHash!: bigint; /** * File reference of that file. */ fileReference?: Buffer; /** * If the file has web location, fill this with url of that web location. */ url?: string; /** * If the file has volume id, fill this with it. or if file doesn't have a volume id, fill this with BigInt(0). This is required when you try to make file id of photo/thumbnail. */ volumeId?: bigint; /** * If the file has local id, fill this with it. or if file doesn't have a local id, fill this with 0. This is required when you try to make file id of photo/thumbnail. */ localId?: number; /** * The secret key from file, if file doesn't have a secret key fill this with BigInt(0). This is required when you try to make ThumbnailSource.LEGACY */ secret?: bigint; /** * If you want to make a file id of photo profil, fill this with BigInt of chatId. */ chatId?: bigint; /** * If you want to make a file id of photo profil, fill this with BigInt of accessHash that chat, or BigInt(0) it must be work when you doesn't have a accessHash of that chat. */ chatAccessHash?: bigint; /** * The id of that sticker set. */ stickerSetId?: bigint; /** * The accessHash of that sticker set. BigInt(0) ot must be work when you doesn't have a accessHash of that sticker set. */ stickerSetAccessHash?: bigint; /** * The enum/number of ThumbnailSource. recommended to use enum. * ```ts * import { FileId, ThumbnailSource } from "@tgsnake/fileid" * const fileId = FileId.encode({ * thumbnailSource : ThumbnailSource.DOCUMENT * }) * ``` */ thumbnailSource?: ThumbnailSource; /** * The enum/number of FileType. recommend to use enum. * ```ts * import { FileType, FileId } from "@tgsnake/fileid" * const fileId = FileId.encode({ * thumbnailFileType : FileType.PHOTO * }) * ``` */ thumbnailFileType?: FileType; /** * The size of that thumbnail. * see : https://core.telegram.org/api/files#image-thumbnail-types */ thumbnailSize?: string; /** * Only for generating uniqueFileId. * The enum/number of FileTypeUniqueId. recommended to use enum. * ```ts * import { FileTypeUniqueId, FileId } from "@tgsnake/fileid" * const fileId = FileId.encode({ * fileType : FileTypeUniqueId.PHOTO * }) * ``` */ fileTypeUniqueId?: FileTypeUniqueId; constructor({ version, subVersion, dcId, fileType, id, accessHash, fileReference, url, volumeId, localId, secret, chatId, chatAccessHash, stickerSetId, stickerSetAccessHash, thumbnailSource, thumbnailFileType, thumbnailSize, fileTypeUniqueId, }: Options) { this.version = version; this.subVersion = subVersion; this.dcId = dcId; this.fileType = fileType; this.id = id; this.accessHash = accessHash; this.fileReference = fileReference; this.url = url; this.volumeId = volumeId ?? BigInt(0); this.localId = localId ?? 0; this.secret = secret; this.chatId = chatId; this.chatAccessHash = chatAccessHash; this.stickerSetId = stickerSetId; this.stickerSetAccessHash = stickerSetAccessHash; this.thumbnailSource = thumbnailSource; this.thumbnailFileType = thumbnailFileType; this.thumbnailSize = thumbnailSize; this.fileTypeUniqueId = fileTypeUniqueId; } static fileId(fileId: string): Decode { const buffer = base64_url_decode(fileId); const version = buffer[buffer.length - 1]; const subVersion = version >= 4 ? buffer[buffer.length - 2] : 0; const rle = rle_decode(version >= 4 ? buffer.slice(0, -2) : buffer.slice(0, -1)); const reader = new Reader(rle); let fileType = reader.readInt(); const dcId = reader.readInt(); const hasWebLocation = Boolean(fileType & FileType.WEB_LOCATION_FLAG); const hasFileReference = Boolean(fileType & FileType.FILE_REFERENCE_FLAG); fileType &= ~FileType.WEB_LOCATION_FLAG; fileType &= ~FileType.FILE_REFERENCE_FLAG; const FileTypes = Object.values(FileType).filter((v) => typeof v === 'number'); if (!FileTypes.includes(fileType)) { throw new Error(`unknown fileType ${fileType} of fileId ${fileId}`); } let obj: Options = { //@ts-ignore version, subVersion, dcId, fileType, id: BigInt(0), accessHash: BigInt(0), }; if (hasWebLocation) { obj.url = reader.readString(); obj.id = reader.readBigInt(); obj.accessHash = reader.readBigInt(); return new Decode(obj); } if (hasFileReference) obj.fileReference = reader.readBuffer(); obj.id = reader.readBigInt(); obj.accessHash = reader.readBigInt(); if (PHOTO_TYPES.includes(fileType)) { obj.volumeId = reader.readBigInt(); obj.thumbnailSource = reader.readInt(); switch (obj.thumbnailSource) { case ThumbnailSource.LEGACY: obj.secret = reader.readBigInt(); obj.localId = reader.readInt(); return new Decode(obj); break; case ThumbnailSource.THUMBNAIL: obj.thumbnailFileType = reader.readInt(); obj.thumbnailSize = String.fromCharCode(reader.readInt()); obj.localId = reader.readInt(); return new Decode(obj); case ThumbnailSource.CHAT_PHOTO_BIG: case ThumbnailSource.CHAT_PHOTO_SMALL: obj.chatId = reader.readBigInt(); obj.chatAccessHash = reader.readBigInt(); obj.localId = reader.readInt(); return new Decode(obj); break; case ThumbnailSource.STICKER_SET_THUMBNAIL: obj.stickerSetId = reader.readBigInt(); obj.stickerSetAccessHash = reader.readBigInt(); obj.localId = reader.readInt(); return new Decode(obj); break; default: throw new Error(`unknown ThumbnailSource ${obj.thumbnailSource} of fileId ${fileId}`); } } return new Decode(obj); } static uniqueId(uniqueId: string): Decode { const rle = rle_decode(base64_url_decode(uniqueId)); const reader = new Reader(rle); const fileTypeUniqueId = reader.readInt(); switch (fileTypeUniqueId) { case FileTypeUniqueId.WEB: //@ts-ignore return new Decode({ fileTypeUniqueId, url: reader.readString(), }); break; case FileTypeUniqueId.PHOTO: //@ts-ignore return new Decode({ fileTypeUniqueId, volumeId: reader.readBigInt(), localId: reader.readInt(), }); break; case FileTypeUniqueId.DOCUMENT: //@ts-ignore return new Decode({ fileTypeUniqueId, id: reader.readBigInt(), }); break; default: throw new Error( `unknown decoder for fileTypeUniqueId ${fileTypeUniqueId} of uniqueFileId ${uniqueId}` ); } } [Symbol.for('nodejs.util.inspect.custom')](): { [key: string]: any } { const toPrint: { [key: string]: any } = { _: this.constructor.name, }; for (const key in this) { if (this.hasOwnProperty(key)) { const value = this[key]; if (!key.startsWith('_')) { toPrint[key] = value; } } } return toPrint; } toJSON(): { [key: string]: any } { const toPrint: { [key: string]: any } = { _: this.constructor.name, }; for (const key in this) { if (this.hasOwnProperty(key)) { const value = this[key]; if (!key.startsWith('_')) { toPrint[key] = typeof value === 'bigint' ? String(value) : value; } } } return toPrint; } toString() { return `[constructor of ${this.constructor.name}] ${JSON.stringify(this, null, 2)}`; } }