tlobject.js 32 KB

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