@tgsnake/fileid
Version:
core framework for tgsnake for generating file id
338 lines (335 loc) • 9.41 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 { 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;
}