@tgsnake/fileid
Version:
core framework for tgsnake for generating file id
298 lines (297 loc) • 9.25 kB
text/typescript
/**
* 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)}`;
}
}