123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- const fs = require('fs');
- const { TLArg } = require('./tlarg');
- const { TLObject } = require('./tlobject');
- const { Usability } = require('../methods');
- const CORE_TYPES = new Set([
- 0xbc799737, // boolFalse#bc799737 = Bool;
- 0x997275b5, // boolTrue#997275b5 = Bool;
- 0x3fedd339, // true#3fedd339 = True;
- 0xc4b9f9bb, // error#c4b9f9bb code:int text:string = Error;
- 0x56730bcc, // null#56730bcc = Null;
- ]);
- // Telegram Desktop (C++) doesn't care about string/bytes, and the .tl files
- // don't either. However in Python we *do*, and we want to deal with bytes
- // for the authorization key process, not UTF-8 strings (they won't be).
- //
- // Every type with an ID that's in here should get their attribute types
- // with string being replaced with bytes.
- const AUTH_KEY_TYPES = new Set([
- 0x05162463, // resPQ,
- 0x83c95aec, // p_q_inner_data
- 0xa9f55f95, // p_q_inner_data_dc
- 0x3c6a84d4, // p_q_inner_data_temp
- 0x56fddf88, // p_q_inner_data_temp_dc
- 0xd0e8075c, // server_DH_params_ok
- 0xb5890dba, // server_DH_inner_data
- 0x6643b654, // client_DH_inner_data
- 0xd712e4be, // req_DH_params
- 0xf5045f1f, // set_client_DH_params
- 0x3072cfa1, // gzip_packed
- ]);
- const findall = (regex, str, matches) => {
- if (!matches) {
- matches = [];
- }
- if (!regex.flags.includes(`g`)) {
- regex = new RegExp(regex.source, `g`);
- }
- const res = regex.exec(str);
- if (res) {
- matches.push(res.slice(1));
- findall(regex, str, matches);
- }
- return matches;
- };
- const fromLine = (line, isFunction, methodInfo, layer) => {
- const match = line.match(
- /([\w.]+)(?:#([0-9a-fA-F]+))?(?:\s{?\w+:[\w\d<>#.?!]+}?)*\s=\s([\w\d<>#.?]+);$/
- );
- if (!match) {
- // Probably "vector#1cb5c415 {t:Type} # [ t ] = Vector t;"
- throw new Error(`Cannot parse TLObject ${line}`);
- }
- const argsMatch = findall(/({)?(\w+):([\w\d<>#.?!]+)}?/, line);
- const [, name] = match;
- methodInfo = methodInfo[name];
- let usability, friendly;
- if (methodInfo) {
- usability = methodInfo.usability;
- friendly = methodInfo.friendly;
- } else {
- usability = Usability.UNKNOWN;
- friendly = null;
- }
- return new TLObject(
- name,
- match[2],
- argsMatch.map(
- ([brace, name, argType]) =>
- new TLArg(name, argType, brace !== undefined)
- ),
- match[3],
- isFunction,
- usability,
- friendly,
- layer
- );
- };
- /**
- * This method yields TLObjects from a given .tl file.
- *
- * Note that the file is parsed completely before the function yields
- * because references to other objects may appear later in the file.
- */
- const parseTl = function*(filePath, layer, methods, ignoreIds = CORE_TYPES) {
- const methodInfo = (methods || []).reduce(
- (o, m) => ({ ...o, [m.name]: m }),
- {}
- );
- const objAll = [];
- const objByName = {};
- const objByType = {};
- const file = fs.readFileSync(filePath, { encoding: 'utf-8' });
- let isFunction = false;
- for (let line of file.split('\n')) {
- const commentIndex = line.indexOf('//');
- if (commentIndex !== -1) {
- line = line.slice(0, commentIndex);
- }
- line = line.trim();
- if (!line) {
- continue;
- }
- const match = line.match(/---(\w+)---/);
- if (match) {
- const [, followingTypes] = match;
- isFunction = followingTypes === 'functions';
- continue;
- }
- try {
- const result = fromLine(line, isFunction, methodInfo, layer);
- if (ignoreIds.has(result.id)) {
- continue;
- }
- objAll.push(result);
- if (!result.isFunction) {
- if (!objByType[result.result]) {
- objByType[result.result] = [];
- }
- objByName[result.fullname] = result;
- objByType[result.result].push(result);
- }
- } catch (e) {
- if (!e.toString().includes('vector#1cb5c415')) {
- throw e;
- }
- }
- }
- // Once all objects have been parsed, replace the
- // string type from the arguments with references
- for (const obj of objAll) {
- if (AUTH_KEY_TYPES.has(obj.id)) {
- for (const arg of obj.args) {
- if (arg.type === 'string') {
- arg.type = 'bytes';
- }
- }
- }
- for (const arg of obj.args) {
- arg.cls =
- objByType[arg.type] ||
- (arg.type in objByName ? [objByName[arg.type]] : []);
- }
- }
- for (const obj of objAll) {
- yield obj;
- }
- };
- /**
- * Finds the layer used on the specified scheme.tl file.
- */
- const findLayer = filePath => {
- const layerRegex = /^\/\/\s*LAYER\s*(\d+)$/;
- const file = fs.readFileSync(filePath, { encoding: 'utf-8' });
- for (const line of file.split('\n')) {
- const match = line.match(layerRegex);
- if (match) {
- return Number(match[1]);
- }
- }
- };
- module.exports = {
- parseTl,
- findLayer,
- };
|