UNPKG

@tgsnake/fileid

Version:

core framework for tgsnake for generating file id

338 lines (335 loc) 9.41 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 { Buffer } from 'node:buffer'; export enum FileType { THUMBNAIL = 0, CHAT_PHOTO = 1, // ProfilePhoto PHOTO = 2, VOICE = 3, // VoiceNote VIDEO = 4, DOCUMENT = 5, ENCRYPTED = 6, TEMP = 7, STICKER = 8, AUDIO = 9, ANIMATION = 10, ENCRYPTED_THUMBNAIL = 11, WALLPAPER = 12, VIDEO_NOTE = 13, SECURE_RAW = 14, SECURE = 15, BACKGROUND = 16, DOCUMENT_AS_FILE = 17, // InternalOnly! Don't pass with this!! WEB_LOCATION_FLAG = 1 << 24, FILE_REFERENCE_FLAG = 1 << 25, } export enum ThumbnailSource { LEGACY = 0, THUMBNAIL = 1, CHAT_PHOTO_SMALL = 2, // DialogPhotoSmall CHAT_PHOTO_BIG = 3, // DialogPhotoBig STICKER_SET_THUMBNAIL = 4, } export enum FileTypeUniqueId { WEB = 0, PHOTO = 1, DOCUMENT = 2, SECURE = 3, ENCRYPTED = 4, TEMP = 5, } export const PHOTO_TYPES = [ FileType.THUMBNAIL, FileType.CHAT_PHOTO, FileType.PHOTO, FileType.WALLPAPER, FileType.ENCRYPTED_THUMBNAIL, ]; export const DOCUMENT_TYPES = [ FileType.VOICE, FileType.VIDEO, FileType.DOCUMENT, FileType.ENCRYPTED, FileType.TEMP, FileType.STICKER, FileType.AUDIO, FileType.ANIMATION, FileType.VIDEO_NOTE, FileType.SECURE_RAW, FileType.SECURE, FileType.BACKGROUND, FileType.DOCUMENT_AS_FILE, ]; export interface 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; } export function base64_url_encode(base: string | Buffer): string { return typeof base === 'string' ? Buffer.from(base).toString('base64url') : base.toString('base64url'); } export function base64_url_decode(base: string | Buffer): Buffer { return typeof base === 'string' ? Buffer.from(base64urlTobase64(base), 'base64') : base; } export function base64urlTobase64(text: string): string { const pad = text.length % 4; if (pad === 1) { throw new Error('Invalid base64url'); } return (pad === 2 || pad === 3 ? text.padEnd(4 - pad, '=') : text) .replace(/\-/g, '+') .replace(/_/g, '/'); } export function rle_encode(base: string | Buffer): Buffer { let buffer: Buffer = typeof base === 'string' ? Buffer.from(base) : base; let r: Array<number> = []; let n: number = 0; for (let b of buffer) { if (!b) { n++; } else { if (n) { r.push(0, n); n = 0; } r.push(b); } } if (n) { r.push(0, n); } const res = Buffer.from(r); return res; } export function rle_decode(base: string | Buffer): Buffer { let buffer: Buffer = typeof base === 'string' ? Buffer.from(base) : base; let r: Array<number> = []; let z: boolean = false; for (let b of buffer) { if (!b) { z = true; continue; } if (z) { for (let i = 0; i < b; i++) { r.push(0); } z = false; } else { r.push(b); } } return Buffer.from(r); } export class Writer { private buffer!: Buffer; constructor() { this.buffer = Buffer.alloc(0); } writeInt(int: number): Writer { const buf = Buffer.alloc(4); buf.writeInt32LE(int); this.buffer = Buffer.concat([this.buffer, buf]); return this; } writeBigInt(int: bigint): Writer { this.buffer = Buffer.concat([this.buffer, packLong(BigInt.asUintN(64, int))]); return this; } writeString(str: string): Writer { return this.writeBuffer(Buffer.from(str, 'utf8')); } writeBuffer(buffer: Buffer): Writer { const length = buffer.length; let buf = Buffer.alloc(0); if (length <= 253) { buf = Buffer.concat([Buffer.from([length]), buffer, Buffer.alloc(mod(-(length + 1), 4))]); } else { buf = Buffer.concat([ Buffer.from([254]), Buffer.from([length & 0xff]), Buffer.from([(length >> 8) & 0xff]), Buffer.from([(length >> 16) & 0xff]), buffer, Buffer.alloc(mod(-length, 4)), ]); } this.buffer = Buffer.concat([this.buffer, buf]); return this; } results(): Buffer { return this.buffer; } } export class Reader { private buffer!: Buffer; private start!: number; private cur!: number; constructor(buffer: Buffer) { this.buffer = buffer; this.start = 0; this.cur = this.start; } readInt(): number { const res = this.buffer.readInt32LE(this.cur); this.cur += 4; return res; } readBigInt(signed: boolean = true): bigint { let res = BigInt( `0x${this.buffer .slice(this.cur, this.cur + 8) .reverse() .toString('hex')}` ); this.cur += 8; return BigInt.asIntN(64, res); } readString(): string { return this.readBuffer().toString('utf8'); } readBuffer(): Buffer { const firstBuff = this.buffer[this.cur++]; let length, padding; if (firstBuff === 254) { length = this.buffer[this.cur++] | (this.buffer[this.cur++] << 8) | (this.buffer[this.cur++] << 16); padding = mod(-length, 4); } else { length = firstBuff; padding = mod(-(length + 1), 4); } const data = this.buffer.slice( this.cur, (this.cur += length === -1 ? this.buffer.length - this.cur : length) ); if (padding > 0) this.cur += 4 - padding; return data; } } function packLong(long: bigint, little: boolean = true, signed: boolean = false) { const bytes = Buffer.alloc(8); const shift = BigInt((1 << 16) * (1 << 16)); if (signed) { bytes.writeInt32LE(Number(String(long % shift)), 0); bytes.writeInt32LE(Number(String(long / shift)), 4); return little ? bytes : bytes.reverse(); } else { bytes.writeUInt32LE(Number(String(long % shift)), 0); bytes.writeUInt32LE(Number(String(long / shift)), 4); return little ? bytes : bytes.reverse(); } } // https://stackoverflow.com/questions/4467539/javascript-modulo-gives-a-negative-result-for-negative-numbers export function mod(n: number, m: number): number { return ((n % m) + m) % m; }