tlobject.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. const fs = require('fs');
  2. const util = require('util');
  3. const {crc32} = require('crc');
  4. const SourceBuilder = require('../sourcebuilder');
  5. const {snakeToCamelCase} = require("../utils");
  6. const AUTO_GEN_NOTICE =
  7. "/*! File generated by TLObjects' generator. All changes will be ERASED !*/";
  8. const AUTO_CASTS = {
  9. InputPeer: 'utils.get_input_peer(await client.get_input_entity(%s))',
  10. InputChannel: 'utils.get_input_channel(await client.get_input_entity(%s))',
  11. InputUser: 'utils.get_input_user(await client.get_input_entity(%s))',
  12. InputDialogPeer: 'await client._get_input_dialog(%s)',
  13. InputNotifyPeer: 'await client._get_input_notify(%s)',
  14. InputMedia: 'utils.get_input_media(%s)',
  15. InputPhoto: 'utils.get_input_photo(%s)',
  16. InputMessage: 'utils.get_input_message(%s)',
  17. InputDocument: 'utils.get_input_document(%s)',
  18. InputChatPhoto: 'utils.get_input_chat_photo(%s)',
  19. };
  20. const NAMED_AUTO_CASTS = {
  21. 'chat_id,int': 'await client.get_peer_id(%s, add_mark=False)',
  22. };
  23. // Secret chats have a chat_id which may be negative.
  24. // With the named auto-cast above, we would break it.
  25. // However there are plenty of other legit requests
  26. // with `chat_id:int` where it is useful.
  27. //
  28. // NOTE: This works because the auto-cast is not recursive.
  29. // There are plenty of types that would break if we
  30. // did recurse into them to resolve them.
  31. const NAMED_BLACKLIST = new Set(['messages.discardEncryption']);
  32. const BASE_TYPES = [
  33. 'string',
  34. 'bytes',
  35. 'int',
  36. 'long',
  37. 'int128',
  38. 'int256',
  39. 'double',
  40. 'Bool',
  41. 'true',
  42. ];
  43. // Patched types {fullname: custom.ns.Name}
  44. const PATCHED_TYPES = {
  45. messageEmpty: 'message.Message',
  46. message: 'message.Message',
  47. messageService: 'message.Message',
  48. };
  49. const writeModules = (
  50. outDir,
  51. depth,
  52. kind,
  53. namespaceTlobjects,
  54. typeConstructors
  55. ) => {
  56. // namespace_tlobjects: {'namespace', [TLObject]}
  57. fs.mkdirSync(outDir, {recursive: true});
  58. for (const [ns, tlobjects] of Object.entries(namespaceTlobjects)) {
  59. const file = `${outDir}/${ns === 'null' ? 'index' : ns}.js`;
  60. const stream = fs.createWriteStream(file);
  61. const builder = new SourceBuilder(stream);
  62. const dotDepth = '.'.repeat(depth || 1);
  63. builder.writeln(AUTO_GEN_NOTICE);
  64. builder.writeln(
  65. `const { TLObject } = require('${dotDepth}/tlobject');`
  66. );
  67. if (kind !== 'TLObject') {
  68. builder.writeln(
  69. `const { ${kind} } = require('${dotDepth}/tlobject');`
  70. );
  71. }
  72. // Add the relative imports to the namespaces,
  73. // unless we already are in a namespace.
  74. if (!ns) {
  75. const imports = Object.keys(namespaceTlobjects)
  76. .filter(Boolean)
  77. .join(`, `);
  78. builder.writeln(`const { ${imports} } = require('.');`);
  79. }
  80. // Import struct for the .__bytes__(self) serialization
  81. builder.writeln("const struct = require('python-struct');");
  82. const typeNames = new Set();
  83. const typeDefs = [];
  84. // Find all the types in this file and generate type definitions
  85. // based on the types. The type definitions are written to the
  86. // file at the end.
  87. for (const t of tlobjects) {
  88. if (!t.isFunction) {
  89. let typeName = t.result;
  90. if (typeName.includes('.')) {
  91. typeName = typeName.slice(typeName.lastIndexOf('.'));
  92. }
  93. if (typeNames.has(typeName)) {
  94. continue;
  95. }
  96. typeNames.add(typeName);
  97. const constructors = typeConstructors[typeName];
  98. if (!constructors) {
  99. } else if (constructors.length === 1) {
  100. typeDefs.push(
  101. `Type${typeName} = ${constructors[0].className}`
  102. );
  103. } else {
  104. typeDefs.push(
  105. `Type${typeName} = Union[${constructors
  106. .map(x => constructors.className)
  107. .join(',')}]`
  108. );
  109. }
  110. }
  111. }
  112. const imports = {};
  113. const primitives = new Set([
  114. 'int',
  115. 'long',
  116. 'int128',
  117. 'int256',
  118. 'double',
  119. 'string',
  120. 'bytes',
  121. 'Bool',
  122. 'true',
  123. ]);
  124. // Find all the types in other files that are used in this file
  125. // and generate the information required to import those types.
  126. for (const t of tlobjects) {
  127. for (const arg of t.args) {
  128. let name = arg.type;
  129. if (!name || primitives.has(name)) {
  130. continue;
  131. }
  132. let importSpace = `${dotDepth}/tl/types`;
  133. if (name.includes('.')) {
  134. const [namespace] = name.split('.');
  135. name = name.split('.');
  136. importSpace += `/${namespace}`;
  137. }
  138. if (!typeNames.has(name)) {
  139. typeNames.add(name);
  140. if (name === 'date') {
  141. imports.datetime = ['datetime'];
  142. continue;
  143. } else if (!(importSpace in imports)) {
  144. imports[importSpace] = new Set();
  145. }
  146. imports[importSpace].add(`Type${name}`);
  147. }
  148. }
  149. }
  150. // Add imports required for type checking
  151. if (imports) {
  152. builder.writeln('if (false) { // TYPE_CHECKING {');
  153. for (const [namespace, names] of Object.entries(imports)) {
  154. builder.writeln(
  155. `const { ${[...names.values()].join(
  156. ', '
  157. )} } = require('${namespace}');`
  158. );
  159. }
  160. builder.endBlock();
  161. }
  162. // Generate the class for every TLObject
  163. for (const t of tlobjects) {
  164. if (t.fullname in PATCHED_TYPES) {
  165. builder.writeln(`const ${t.className} = null; // Patched`);
  166. } else {
  167. writeSourceCode(t, kind, builder, typeConstructors);
  168. builder.currentIndent = 0;
  169. }
  170. }
  171. // Write the type definitions generated earlier.
  172. builder.writeln();
  173. for (const line of typeDefs) {
  174. builder.writeln(line);
  175. }
  176. writeModuleExports(tlobjects, builder);
  177. }
  178. };
  179. const writeReadResult = (tlobject, builder) => {
  180. // Only requests can have a different response that's not their
  181. // serialized body, that is, we'll be setting their .result.
  182. //
  183. // The default behaviour is reading a TLObject too, so no need
  184. // to override it unless necessary.
  185. if (!tlobject.isFunction)
  186. return;
  187. // https://core.telegram.org/mtproto/serialize#boxed-and-bare-types
  188. // TL;DR; boxed types start with uppercase always, so we can use
  189. // this to check whether everything in it is boxed or not.
  190. //
  191. // Currently only un-boxed responses are Vector<int>/Vector<long>.
  192. // If this weren't the case, we should check upper case after
  193. // max(index('<'), index('.')) (and if it is, it's boxed, so return).
  194. let m = tlobject.result.match(/Vector<(int|long)>/);
  195. if (!m) {
  196. return
  197. }
  198. builder.endBlock();
  199. builder.writeln('static readResult(reader){');
  200. builder.writeln('reader.readInt() // Vector ID');
  201. builder.writeln('let temp = [];');
  202. builder.writeln('for (let i=0;i<reader.readInt();i++){');
  203. builder.writeln('temp.push(reader.read%s', m[0]);
  204. builder.writeln("}");
  205. builder.writeln('return temp');
  206. };
  207. /**
  208. * Writes the source code corresponding to the given TLObject
  209. * by making use of the ``builder`` `SourceBuilder`.
  210. *
  211. * Additional information such as file path depth and
  212. * the ``Type: [Constructors]`` must be given for proper
  213. * importing and documentation strings.
  214. */
  215. const writeSourceCode = (tlobject, kind, builder, typeConstructors) => {
  216. writeClassConstructor(tlobject, kind, typeConstructors, builder);
  217. writeResolve(tlobject, builder);
  218. //writeToJson(tlobject, builder);
  219. writeToBytes(tlobject, builder);
  220. builder.currentIndent--;
  221. writeFromReader(tlobject, builder);
  222. writeReadResult(tlobject, builder);
  223. builder.writeln('}');
  224. };
  225. const writeClassConstructor = (tlobject, kind, typeConstructors, builder) => {
  226. builder.writeln();
  227. builder.writeln();
  228. builder.writeln(`class ${tlobject.className} extends ${kind} {`);
  229. // Convert the args to string parameters, flags having =None
  230. const args = tlobject.realArgs.map(
  231. a =>
  232. `${a.name}: ${a.typeHint()}${
  233. a.isFlag || a.canBeInferred ? `=None` : ''
  234. }`
  235. );
  236. // Write the __init__ function if it has any argument
  237. if (!tlobject.realArgs.length) {
  238. return;
  239. }
  240. builder.writeln('/**');
  241. if (tlobject.isFunction) {
  242. builder.write(`:returns ${tlobject.result}: `);
  243. } else {
  244. builder.write(`Constructor for ${tlobject.result}: `);
  245. }
  246. const constructors = typeConstructors[tlobject.result];
  247. if (!constructors) {
  248. builder.writeln('This type has no constructors.');
  249. } else if (constructors.length === 1) {
  250. builder.writeln(`Instance of ${constructors[0].className}`);
  251. } else {
  252. builder.writeln(
  253. `Instance of either ${constructors
  254. .map(c => c.className)
  255. .join(', ')}`
  256. );
  257. }
  258. builder.writeln('*/');
  259. builder.writeln(`constructor(args) {`);
  260. builder.writeln(`super();`);
  261. // Class-level variable to store its Telegram's constructor ID
  262. builder.writeln(
  263. `this.CONSTRUCTOR_ID = 0x${tlobject.id.toString(16).padStart(8, '0')};`
  264. );
  265. builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(tlobject.result)};`);
  266. builder.writeln();
  267. // Set the arguments
  268. for (const arg of tlobject.realArgs) {
  269. if (!arg.canBeInferred) {
  270. builder.writeln(`this.${arg.name} = args.${arg.name};`);
  271. }
  272. // Currently the only argument that can be
  273. // inferred are those called 'random_id'
  274. else if (arg.name === 'random_id') {
  275. // Endianness doesn't really matter, and 'big' is shorter
  276. let code = `int.from_bytes(Helpers.generateRandomBytes(${
  277. arg.type === 'long' ? 8 : 4
  278. }))`;
  279. if (arg.isVector) {
  280. // Currently for the case of "messages.forwardMessages"
  281. // Ensure we can infer the length from id:Vector<>
  282. if (!tlobject.realArgs.find(a => a.name === 'id').isVector) {
  283. throw new Error(
  284. `Cannot infer list of random ids for ${tlobject}`
  285. );
  286. }
  287. code = `new Array(id.length).fill().map(_ => ${code})`;
  288. }
  289. builder.writeln(
  290. `this.randomId = randomId !== null ? randomId : ${code}`
  291. );
  292. } else {
  293. throw new Error(`Cannot infer a value for ${arg}`);
  294. }
  295. }
  296. builder.endBlock();
  297. };
  298. const writeResolve = (tlobject, builder) => {
  299. if (
  300. tlobject.isFunction &&
  301. tlobject.realArgs.some(
  302. arg =>
  303. arg.type in AUTO_CASTS ||
  304. (`${arg.name},${arg.type}` in NAMED_AUTO_CASTS &&
  305. !NAMED_BLACKLIST.has(tlobject.fullname))
  306. )
  307. ) {
  308. builder.writeln('async resolve(client, utils) {');
  309. for (const arg of tlobject.realArgs) {
  310. let ac = AUTO_CASTS[arg.type];
  311. if (!ac) {
  312. ac = NAMED_AUTO_CASTS[`${arg.name},${arg.type}`];
  313. if (!ac) {
  314. continue;
  315. }
  316. }
  317. if (arg.isFlag) {
  318. builder.writeln(`if (this.${arg.name}) {`);
  319. }
  320. if (arg.isVector) {
  321. builder.write(`const _tmp = [];`);
  322. builder.writeln(`for (const _x of this.${arg.name}) {`);
  323. builder.writeln(`_tmp.push(%s);`, util.format(ac, '_x'));
  324. builder.endBlock();
  325. builder.writeln(`this.${arg.name} = _tmp;`);
  326. } else {
  327. builder.writeln(
  328. `this.${arg.name} = %s`,
  329. util.format(ac, `this.${arg.name}`)
  330. );
  331. }
  332. if (arg.isFlag) {
  333. builder.currentIndent--;
  334. builder.writeln('}');
  335. }
  336. }
  337. builder.endBlock();
  338. }
  339. };
  340. /**
  341. const writeToJson = (tlobject, builder) => {
  342. builder.writeln('toJson() {');
  343. builder.writeln('return {');
  344. builder.write("_: '%s'", tlobject.className);
  345. for (const arg of tlobject.realArgs) {
  346. builder.writeln(',');
  347. builder.write('%s: ', arg.name);
  348. if (BASE_TYPES.includes(arg.type)) {
  349. if (arg.isVector) {
  350. builder.write(
  351. 'this.%s === null ? [] : this.%s.slice()',
  352. arg.name,
  353. arg.name
  354. );
  355. } else {
  356. builder.write('this.%s', arg.name);
  357. }
  358. } else {
  359. if (arg.isVector) {
  360. builder.write(
  361. 'this.%s === null ? [] : this.%s.map(x => x instanceof TLObject ? x.toJson() : x)',
  362. arg.name,
  363. arg.name
  364. );
  365. } else {
  366. builder.write(
  367. 'this.%s instanceof TLObject ? this.%s.toJson() : this.%s',
  368. arg.name,
  369. arg.name,
  370. arg.name
  371. );
  372. }
  373. }
  374. }
  375. builder.writeln();
  376. builder.endBlock();
  377. builder.currentIndent--;
  378. builder.writeln('}');
  379. };
  380. */
  381. const writeToBytes = (tlobject, builder) => {
  382. builder.writeln('get bytes() {');
  383. // Some objects require more than one flag parameter to be set
  384. // at the same time. In this case, add an assertion.
  385. const repeatedArgs = {};
  386. for (const arg of tlobject.args) {
  387. if (arg.isFlag) {
  388. if (!repeatedArgs[arg.flagIndex]) {
  389. repeatedArgs[arg.flagIndex] = [];
  390. }
  391. repeatedArgs[arg.flagIndex].push(arg);
  392. }
  393. }
  394. for (const ra of Object.values(repeatedArgs)) {
  395. if (ra.length > 1) {
  396. const cnd1 = ra.map(
  397. a => `(this.${a.name} || this.${a.name} !== null)`
  398. );
  399. const cnd2 = ra.map(
  400. a => `(this.${a.name} === null || this.${a.name} === false)`
  401. );
  402. builder.writeln(
  403. 'if (!(%s || %s)) {',
  404. cnd1.join(' && '),
  405. cnd2.join(' && ')
  406. );
  407. builder.writeln(
  408. "throw new Error('%s parameters must all be false-y (like null) or all me true-y');",
  409. ra.map(a => a.name).join(', ')
  410. );
  411. builder.endBlock();
  412. }
  413. }
  414. const bytes = Buffer.from(
  415. parseInt(tlobject.id)
  416. .toString(16)
  417. .padStart(8, `0`),
  418. `hex`
  419. )
  420. .readUInt32LE(0)
  421. .toString(16)
  422. .padStart(8, `0`);
  423. builder.writeln('return Buffer.concat([');
  424. builder.currentIndent++;
  425. builder.writeln("'%s',", bytes);
  426. for (const arg of tlobject.args) {
  427. if (writeArgToBytes(builder, arg, tlobject.args)) {
  428. builder.writeln(',');
  429. }
  430. }
  431. builder.currentIndent--;
  432. builder.writeln(']);');
  433. builder.endBlock();
  434. };
  435. // writeFromReader
  436. const writeFromReader = (tlobject, builder) => {
  437. builder.writeln("static fromReader(reader) {");
  438. for (const arg of tlobject.args) {
  439. writeArgReadCode(builder, arg, tlobject.args, "_" + arg.name);
  440. }
  441. let temp = [];
  442. for (let a of tlobject.realArgs) {
  443. temp.push(`${a.name}:_${a.name}`)
  444. }
  445. builder.writeln("return this({%s})", temp.join(",\n\t"));
  446. builder.writeln("}");
  447. };
  448. // writeReadResult
  449. /**
  450. * Writes the .__bytes__() code for the given argument
  451. * @param builder: The source code builder
  452. * @param arg: The argument to write
  453. * @param args: All the other arguments in TLObject same __bytes__.
  454. * This is required to determine the flags value
  455. * @param name: The name of the argument. Defaults to "self.argname"
  456. * This argument is an option because it's required when
  457. * writing Vectors<>
  458. */
  459. const writeArgToBytes = (builder, arg, args, name = null) => {
  460. if (arg.genericDefinition) {
  461. return; // Do nothing, this only specifies a later type
  462. }
  463. if (name === null) {
  464. name = `this.${arg.name}`;
  465. }
  466. // The argument may be a flag, only write if it's not None AND
  467. // if it's not a True type.
  468. // True types are not actually sent, but instead only used to
  469. // determine the flags.
  470. if (arg.isFlag) {
  471. if (arg.type === 'true') {
  472. return; // Exit, since true type is never written
  473. } else if (arg.isVector) {
  474. // Vector flags are special since they consist of 3 values,
  475. // so we need an extra join here. Note that empty vector flags
  476. // should NOT be sent either!
  477. builder.write(
  478. "%s === null || %s === false ? Buffer.alloc(0) :Buffer.concat([",
  479. name,
  480. name
  481. );
  482. } else {
  483. builder.write("%s === null || %s === false ? Buffer.alloc(0) : [", name, name);
  484. }
  485. }
  486. if (arg.isVector) {
  487. if (arg.useVectorId) {
  488. builder.write("'15c4b51c',");
  489. }
  490. builder.write("struct.pack('<i', %s.length),", name);
  491. // Cannot unpack the values for the outer tuple through *[(
  492. // since that's a Python >3.5 feature, so add another join.
  493. builder.write('Buffer.concat([%s.map(x => ', name);
  494. // Temporary disable .is_vector, not to enter this if again
  495. // Also disable .is_flag since it's not needed per element
  496. const oldFlag = arg.isFlag;
  497. arg.isVector = arg.isFlag = false;
  498. writeArgToBytes(builder, arg, args, 'x');
  499. arg.isVector = true;
  500. arg.isFlag = oldFlag;
  501. builder.write(')]),', name);
  502. } else if (arg.flagIndicator) {
  503. // Calculate the flags with those items which are not None
  504. if (!args.some(f => f.isFlag)) {
  505. // There's a flag indicator, but no flag arguments so it's 0
  506. builder.write('Buffer.alloc(4)');
  507. } else {
  508. builder.write("struct.pack('<I', ");
  509. builder.write(
  510. args
  511. .filter(flag => flag.isFlag)
  512. .map(
  513. flag =>
  514. `(this.${flag.name} === null || this.${
  515. flag.name
  516. } === false ? 0 : ${1 << flag.flagIndex})`
  517. )
  518. .join(' | ')
  519. );
  520. builder.write(')');
  521. }
  522. } else if (arg.type === 'int') {
  523. builder.write("struct.pack('<i', %s)", name);
  524. } else if (arg.type === 'long') {
  525. builder.write("struct.pack('<q', %s)", name);
  526. } else if (arg.type === 'int128') {
  527. builder.write("BigIntBuffer.toBufferLE(BigInt(%s,16))", name);
  528. } else if (arg.type === 'int256') {
  529. builder.write("BigIntBuffer.toBufferLE(BigInt(%s,32))", name);
  530. } else if (arg.type === 'double') {
  531. builder.write("struct.pack('<d', %s)", name);
  532. } else if (arg.type === 'string') {
  533. builder.write('this.serializeBytes(%s)', name);
  534. } else if (arg.type === 'Bool') {
  535. builder.write('%s ? 0xb5757299 : 0x379779bc', name);
  536. } else if (arg.type === 'true') {
  537. // These are actually NOT written! Only used for flags
  538. } else if (arg.type === 'bytes') {
  539. builder.write('this.serializeBytes(%s)', name);
  540. } else if (arg.type === 'date') {
  541. builder.write('this.serializeDatetime(%s)', name);
  542. } else {
  543. // Else it may be a custom type
  544. builder.write('%s.bytes', name);
  545. // If the type is not boxed (i.e. starts with lowercase) we should
  546. // not serialize the constructor ID (so remove its first 4 bytes).
  547. let boxed = arg.type.charAt(arg.type.indexOf('.') + 1);
  548. boxed = boxed === boxed.toUpperCase();
  549. if (!boxed) {
  550. builder.write('.slice(4)');
  551. }
  552. }
  553. if (arg.isFlag) {
  554. builder.write(']');
  555. if (arg.isVector) {
  556. builder.write(']');
  557. }
  558. }
  559. return true;
  560. };
  561. /**
  562. * Writes the read code for the given argument, setting the
  563. * arg.name variable to its read value.
  564. * @param builder The source code builder
  565. * @param arg The argument to write
  566. * @param args All the other arguments in TLObject same on_send.
  567. * This is required to determine the flags value
  568. * @param name The name of the argument. Defaults to "self.argname"
  569. * This argument is an option because it's required when
  570. * writing Vectors<>
  571. */
  572. const writeArgReadCode = (builder, arg, args, name) => {
  573. if (arg.genericDefinition) {
  574. return // Do nothing, this only specifies a later type
  575. }
  576. //The argument may be a flag, only write that flag was given!
  577. let wasFlag = false;
  578. if (arg.isFlag) {
  579. // Treat 'true' flags as a special case, since they're true if
  580. // they're set, and nothing else needs to actually be read.
  581. if (arg.type === "true") {
  582. builder.writeln("let %s = Boolean(flags & %s)", name, 1 << arg.flagIndex);
  583. return;
  584. }
  585. wasFlag = true;
  586. builder.writeln("if (flags & %s)", 1 << arg.flagIndex);
  587. // Temporary disable .is_flag not to enter this if
  588. // again when calling the method recursively
  589. arg.isFlag = false;
  590. }
  591. if (arg.isVector) {
  592. if (arg.useVectorId) {
  593. // We have to read the vector's constructor ID
  594. builder.writeln("reader.readInt()");
  595. }
  596. builder.writeln("let %s = []", name);
  597. builder.writeln("for (let i=0;i<reader.readInt();i++){");
  598. // Temporary disable .is_vector, not to enter this if again
  599. arg.isVector = false;
  600. writeArgReadCode(builder, arg, args, "_x");
  601. builder.writeln("%s.push(_x)", name);
  602. arg.isVector = true;
  603. } else if (arg.flagIndicator) {
  604. //Read the flags, which will indicate what items we should read next
  605. builder.writeln("let flags = reader.readInt()");
  606. builder.writeln();
  607. } else if (arg.type === "int") {
  608. builder.writeln("let %s = reader.readInt()", name)
  609. } else if (arg.type === "long") {
  610. builder.writeln("let %s = reader.readInt()", name);
  611. } else if (arg.type === "int128") {
  612. builder.writeln('let %s = reader.readLargeInt(bits=128)', name);
  613. } else if (arg.type === "int256") {
  614. builder.writeln('let %s = reader.readLargeInt(bits=256)', name);
  615. } else if (arg.type === "double") {
  616. builder.writeln('let %s = reader.readDouble()', name);
  617. } else if (arg.type === "string") {
  618. builder.writeln('let %s = reader.tgReadString()', name);
  619. } else if (arg.type === "Bool") {
  620. builder.writeln('let %s = reader.tgReadBool()', name);
  621. } else if (arg.type === "true") {
  622. builder.writeln('let %s = true', name);
  623. } else if (arg.type === "bytes") {
  624. builder.writeln('let %s = reader.tgReadBytes()', name);
  625. } else if (arg.type === "date") {
  626. builder.writeln('let %s = reader.tgReadDate()', name);
  627. } else {
  628. // Else it may be a custom type
  629. if (!arg.skipConstructorId) {
  630. builder.writeln('let %s = reader.tgReadObject()', name);
  631. } else {
  632. // Import the correct type inline to avoid cyclic imports.
  633. // There may be better solutions so that we can just access
  634. // all the types before the files have been parsed, but I
  635. // don't know of any.
  636. let sepIndex = arg.type.indexOf(".");
  637. let ns, t;
  638. if (sepIndex === -1) {
  639. ns = ".";
  640. t = arg.type;
  641. } else {
  642. ns = "." + arg.type.slice(0, sepIndex);
  643. t = arg.type.slice(sepIndex + 1);
  644. }
  645. let className = snakeToCamelCase(t);
  646. // There would be no need to import the type if we're in the
  647. // file with the same namespace, but since it does no harm
  648. // and we don't have information about such thing in the
  649. // method we just ignore that case.
  650. builder.writeln('const %s = require("%S")', className, ns);
  651. builder.writeln("let %s = %s.fromReader(reader)", name, className);
  652. }
  653. }
  654. // End vector and flag blocks if required (if we opened them before)
  655. if (arg.isVector) {
  656. builder.endBlock();
  657. }
  658. if (wasFlag) {
  659. builder.currentIndent -= 1;
  660. builder.writeln("else");
  661. builder.writeln("let %s = null", name);
  662. builder.currentIndent -= 1;
  663. // Restore .isFlag;
  664. arg.isFlag = true;
  665. }
  666. };
  667. const writePatched = (outDir, namespaceTlobjects) => {
  668. fs.mkdirSync(outDir, {recursive: true});
  669. for (const [ns, tlobjects] of Object.entries(namespaceTlobjects)) {
  670. const file = `${outDir}/${ns === 'null' ? 'index' : ns}.js`;
  671. const stream = fs.createWriteStream(file);
  672. const builder = new SourceBuilder(stream);
  673. builder.writeln(AUTO_GEN_NOTICE);
  674. builder.writeln("const struct = require('python-struct');");
  675. builder.writeln(`const { TLObject, types, custom } = require('..');`);
  676. builder.writeln();
  677. for (const t of tlobjects) {
  678. builder.writeln(
  679. 'class %s extends custom.%s {',
  680. t.className,
  681. PATCHED_TYPES[t.fullname]
  682. );
  683. builder.writeln('constructor() {');
  684. builder.writeln('super();');
  685. builder.writeln(`this.CONSTRUCTOR_ID = 0x${t.id.toString(16)}`);
  686. builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(t.result)}`);
  687. builder.endBlock();
  688. //writeToJson(t, builder);
  689. writeToBytes(t, builder);
  690. writeFromReader(t, builder);
  691. builder.writeln();
  692. builder.writeln(
  693. 'types.%s%s = %s',
  694. t.namespace ? `${t.namespace}.` : '',
  695. t.className,
  696. t.className
  697. );
  698. builder.writeln();
  699. }
  700. }
  701. };
  702. const writeAllTLObjects = (tlobjects, layer, builder) => {
  703. builder.writeln(AUTO_GEN_NOTICE);
  704. builder.writeln();
  705. builder.writeln("const { types, functions, patched } = require('.');");
  706. builder.writeln();
  707. // Create a constant variable to indicate which layer this is
  708. builder.writeln(`const LAYER = %s;`, layer);
  709. builder.writeln();
  710. // Then create the dictionary containing constructor_id: class
  711. builder.writeln('const tlobjects = {');
  712. // Fill the dictionary (0x1a2b3c4f: tl.full.type.path.Class)
  713. for (const tlobject of tlobjects) {
  714. builder.write('0x0%s: ', tlobject.id.toString(16).padStart(8, '0'));
  715. if (tlobject.fullname in PATCHED_TYPES) {
  716. builder.write('patched');
  717. } else {
  718. builder.write(tlobject.isFunction ? 'functions' : 'types');
  719. }
  720. if (tlobject.namespace) {
  721. builder.write('.%s', tlobject.namespace);
  722. }
  723. builder.writeln('.%s,', tlobject.className);
  724. }
  725. builder.endBlock(true);
  726. builder.writeln('');
  727. builder.writeln('module.exports = {');
  728. builder.writeln('LAYER,');
  729. builder.writeln('tlobjects');
  730. builder.endBlock(true);
  731. };
  732. const generateTLObjects = (tlobjects, layer, importDepth, outputDir) => {
  733. // Group everything by {namespace :[tlobjects]} to generate index.js
  734. const namespaceFunctions = {};
  735. const namespaceTypes = {};
  736. const namespacePatched = {};
  737. // Group {type: [constructors]} to generate the documentation
  738. const typeConstructors = {};
  739. for (const tlobject of tlobjects) {
  740. if (tlobject.isFunction) {
  741. if (!namespaceFunctions[tlobject.namespace]) {
  742. namespaceFunctions[tlobject.namespace] = [];
  743. }
  744. namespaceFunctions[tlobject.namespace].push(tlobject);
  745. } else {
  746. if (!namespaceTypes[tlobject.namespace]) {
  747. namespaceTypes[tlobject.namespace] = [];
  748. }
  749. if (!typeConstructors[tlobject.result]) {
  750. typeConstructors[tlobject.result] = [];
  751. }
  752. namespaceTypes[tlobject.namespace].push(tlobject);
  753. typeConstructors[tlobject.result].push(tlobject);
  754. if (tlobject.fullname in PATCHED_TYPES) {
  755. if (!namespacePatched[tlobject.namespace]) {
  756. namespacePatched[tlobject.namespace] = [];
  757. }
  758. namespacePatched[tlobject.namespace].push(tlobject);
  759. }
  760. }
  761. }
  762. writeModules(
  763. `${outputDir}/functions`,
  764. importDepth,
  765. 'TLRequest',
  766. namespaceFunctions,
  767. typeConstructors
  768. );
  769. writeModules(
  770. `${outputDir}/types`,
  771. importDepth,
  772. 'TLObject',
  773. namespaceTypes,
  774. typeConstructors
  775. );
  776. writePatched(`${outputDir}/patched`, namespacePatched);
  777. const filename = `${outputDir}/alltlobjects.js`;
  778. const stream = fs.createWriteStream(filename);
  779. const builder = new SourceBuilder(stream);
  780. writeAllTLObjects(tlobjects, layer, builder);
  781. };
  782. const cleanTLObjects = outputDir => {
  783. for (let d in ['functions', 'types', 'patched']) {
  784. d = `${outputDir}/d`;
  785. if (fs.statSync(d).isDirectory()) {
  786. fs.rmdirSync(d);
  787. }
  788. }
  789. const tl = `${outputDir}/alltlobjects.js`;
  790. if (fs.statSync(tl).isFile()) {
  791. fs.unlinkSync(tl);
  792. }
  793. };
  794. const writeModuleExports = (tlobjects, builder) => {
  795. builder.writeln('module.exports = {');
  796. for (const t of tlobjects) {
  797. builder.writeln(`${t.className},`);
  798. }
  799. builder.currentIndent--;
  800. builder.writeln('};');
  801. };
  802. module.exports = {
  803. generateTLObjects,
  804. cleanTLObjects,
  805. };