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; 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(obj1: Record, obj2: Record): Record { const result: Record = { ...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 = {}; 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) { args = args || {}; Object.keys(args) .forEach((argName) => { (this as UnsaveVirtualClass)[argName] = args[argName]; }); } static fromReader(reader: BinaryReader) { const args: Record = {}; 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; }