tlobject.js 31 KB

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