TelegramPWA/src/lib/gramjs/tl/apiHelpers.ts
2026-03-31 11:31:22 +02:00

324 lines
11 KiB
TypeScript

import type { BinaryReader } from '../extensions';
import tlContent from './apiTl';
import {
type GenerationArgConfig, type GenerationEntryConfig, parseTl, serializeBytes, serializeDate,
} from './generationHelpers';
import schemeContent from './schemaTl';
import { toSignedLittleBuffer } from '../Helpers';
// eslint-disable-next-line no-restricted-globals
const CACHING_SUPPORTED = typeof self !== 'undefined' && self.localStorage !== undefined;
const CACHE_KEY = 'GramJs:apiCache';
type UnsaveVirtualClass = Record<string, any>;
export function buildApiFromTlSchema() {
let definitions;
const fromCache = CACHING_SUPPORTED && loadFromCache();
if (fromCache) {
definitions = fromCache;
} else {
definitions = loadFromTlSchemas();
if (CACHING_SUPPORTED) {
localStorage.setItem(CACHE_KEY, JSON.stringify(definitions));
}
}
return mergeWithNamespaces(
createClasses('constructor', definitions.constructors),
createClasses('request', definitions.requests),
);
}
function loadFromCache(): { constructors: GenerationEntryConfig[]; requests: GenerationEntryConfig[] } {
const jsonCache = localStorage.getItem(CACHE_KEY);
return jsonCache && JSON.parse(jsonCache);
}
function loadFromTlSchemas() {
const [constructorParamsApi, functionParamsApi] = extractParams(tlContent);
const [constructorParamsSchema, functionParamsSchema] = extractParams(schemeContent);
const constructors = ([] as GenerationEntryConfig[]).concat(constructorParamsApi, constructorParamsSchema);
const requests = ([] as GenerationEntryConfig[]).concat(functionParamsApi, functionParamsSchema);
return {
constructors,
requests,
};
}
function mergeWithNamespaces<T extends unknown>(obj1: Record<string, T>, obj2: Record<string, T>): Record<string, T> {
const result: Record<string, any> = { ...obj1 };
Object.keys(obj2)
.forEach((key) => {
if (typeof obj2[key] === 'function' || !result[key]) {
result[key] = obj2[key];
} else {
Object.assign(result[key], obj2[key]);
}
});
return result;
}
function extractParams(fileContent: string) {
const f = parseTl(fileContent);
const constructors = [];
const functions = [];
for (const d of f) {
if (d.isFunction) {
functions.push(d);
} else {
constructors.push(d);
}
}
return [constructors, functions];
}
function argToBytes(x: any, type: string) {
switch (type) {
case 'int': {
const i = Buffer.alloc(4);
i.writeInt32LE(x, 0);
return i;
}
case 'long':
return toSignedLittleBuffer(x, 8);
case 'int128':
return toSignedLittleBuffer(x, 16);
case 'int256':
return toSignedLittleBuffer(x, 32);
case 'double': {
const d = Buffer.alloc(8);
d.writeDoubleLE(x, 0);
return d;
}
case 'string':
return serializeBytes(x);
case 'Bool':
return x ? Buffer.from('b5757299', 'hex') : Buffer.from('379779bc', 'hex');
case 'true':
return Buffer.alloc(0);
case 'bytes':
return serializeBytes(x);
case 'date':
return serializeDate(x);
default:
return x.getBytes();
}
}
function getArgFromReader(reader: BinaryReader, arg: GenerationArgConfig): any {
if (arg.isVector) {
if (arg.useVectorId) {
reader.readInt();
}
const temp = [];
const len = reader.readInt();
arg.isVector = false;
for (let i = 0; i < len; i++) {
temp.push(getArgFromReader(reader, arg));
}
arg.isVector = true;
return temp;
} else if (arg.flagIndicator) {
return reader.readInt();
} else {
switch (arg.type) {
case 'int':
return reader.readInt();
case 'long':
return reader.readLong();
case 'int128':
return reader.readLargeInt(128);
case 'int256':
return reader.readLargeInt(256);
case 'double':
return reader.readDouble();
case 'string':
return reader.tgReadString();
case 'Bool':
return reader.tgReadBool();
case 'true':
return true;
case 'bytes':
return reader.tgReadBytes();
case 'date':
return reader.tgReadDate();
default:
if (!arg.skipConstructorId) {
return reader.tgReadObject();
} else {
throw new Error(`Unknown type ${arg}`);
}
}
}
}
function createClasses(classesType: 'constructor' | 'request', params: GenerationEntryConfig[]) {
const classes: Record<string, any> = {};
for (const classParams of params) {
const {
name,
constructorId,
subclassOfId,
argsConfig,
namespace,
result,
} = classParams;
const fullName = [namespace, name].join('.')
.replace(/^\./, '');
class VirtualClass {
static CONSTRUCTOR_ID = constructorId;
static SUBCLASS_OF_ID = subclassOfId;
static className = fullName;
static classType = classesType;
CONSTRUCTOR_ID = constructorId;
SUBCLASS_OF_ID = subclassOfId;
className = fullName;
classType = classesType;
constructor(args: Record<string, any>) {
args = args || {};
Object.keys(args)
.forEach((argName) => {
(this as UnsaveVirtualClass)[argName] = args[argName];
});
}
static fromReader(reader: BinaryReader) {
const args: Record<string, any> = {};
for (const argName in argsConfig) {
if (argsConfig.hasOwnProperty(argName)) {
const arg = argsConfig[argName];
if (arg.isFlag) {
const flagGroupSuffix = arg.flagGroup > 1 ? arg.flagGroup : '';
const flagValue = args[`flags${flagGroupSuffix}`] & (1 << arg.flagIndex);
if (arg.type === 'true') {
args[argName] = flagValue ? true : undefined;
continue;
}
args[argName] = flagValue ? getArgFromReader(reader, arg) : undefined;
} else {
args[argName] = getArgFromReader(reader, arg);
}
}
}
return new VirtualClass(args);
}
getBytes() {
// The next is pseudo-code:
const idForBytes = this.CONSTRUCTOR_ID;
const c = Buffer.alloc(4);
c.writeUInt32LE(idForBytes, 0);
const buffers = [c];
for (const arg in argsConfig) {
if (argsConfig.hasOwnProperty(arg)) {
if (argsConfig[arg].isFlag) {
if (((this as UnsaveVirtualClass)[arg] === false && argsConfig[arg].type === 'true')
|| (this as UnsaveVirtualClass)[arg] === undefined) {
continue;
}
}
if (argsConfig[arg].isVector) {
if (argsConfig[arg].useVectorId) {
buffers.push(Buffer.from('15c4b51c', 'hex'));
}
const l = Buffer.alloc(4);
l.writeInt32LE((this as UnsaveVirtualClass)[arg].length, 0);
buffers.push(l, Buffer.concat((this as UnsaveVirtualClass)[arg].map((x: any) => (
argToBytes(x, argsConfig[arg].type)
))));
} else if (argsConfig[arg].flagIndicator) {
if (!Object.values(argsConfig)
.some((f) => f.isFlag)) {
buffers.push(Buffer.alloc(4));
} else {
let flagCalculate = 0;
for (const f in argsConfig) {
if (argsConfig[f].isFlag) {
if (((this as UnsaveVirtualClass)[f] === false && argsConfig[f].type === 'true')
|| (this as UnsaveVirtualClass)[f] === undefined) {
flagCalculate |= 0;
} else {
flagCalculate |= 1 << argsConfig[f].flagIndex;
}
}
}
const f = Buffer.alloc(4);
f.writeUInt32LE(flagCalculate, 0);
buffers.push(f);
}
} else {
buffers.push(argToBytes((this as UnsaveVirtualClass)[arg], argsConfig[arg].type));
if ((this as UnsaveVirtualClass)[arg]
&& typeof (this as UnsaveVirtualClass)[arg].getBytes === 'function') {
const firstChar = (argsConfig[arg].type.charAt(argsConfig[arg].type.indexOf('.') + 1));
const boxed = firstChar === firstChar.toUpperCase();
if (!boxed) {
buffers.shift();
}
}
}
}
}
return Buffer.concat(buffers);
}
readResult(reader: BinaryReader) {
if (classesType !== 'request') {
throw new Error('`readResult()` called for non-request instance');
}
const m = result.match(/Vector<(int|long)>/);
if (m) {
reader.readInt();
const temp = [];
const len = reader.readInt();
if (m[1] === 'int') {
for (let i = 0; i < len; i++) {
temp.push(reader.readInt());
}
} else {
for (let i = 0; i < len; i++) {
temp.push(reader.readLong());
}
}
return temp;
} else {
return reader.tgReadObject();
}
}
}
if (namespace) {
if (!classes[namespace]) {
classes[namespace] = {};
}
classes[namespace][name] = VirtualClass;
} else {
classes[name] = VirtualClass;
}
}
return classes;
}