1
0

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. builder.writeln(`args = args || {}`)
  256. // Class-level variable to store its Telegram's constructor ID
  257. builder.writeln(`this.CONSTRUCTOR_ID = 0x${tlobject.id.toString(16).padStart(8, '0')};`)
  258. builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(tlobject.result).toString(16)};`)
  259. builder.writeln()
  260. // Set the arguments
  261. for (const arg of tlobject.realArgs) {
  262. if (!arg.canBeInferred) {
  263. builder.writeln(
  264. `this.${variableSnakeToCamelCase(arg.name)} = args.${variableSnakeToCamelCase(
  265. arg.name,
  266. )}${arg.isFlag || arg.canBeInferred ? ' || null' : ''};`,
  267. )
  268. // Currently the only argument that can be
  269. // inferred are those called 'random_id'
  270. } else if (arg.name === 'random_id') {
  271. // Endianness doesn't really matter, and 'big' is shorter
  272. let code = `readBigIntFromBuffer(generateRandomBytes(${
  273. arg.type === 'long' ? 8 : 4
  274. }),false,true)`
  275. if (arg.isVector) {
  276. // Currently for the case of "messages.forwardMessages"
  277. // Ensure we can infer the length from id:Vector<>
  278. if (!tlobject.realArgs.find((a) => a.name === 'id').isVector) {
  279. throw new Error(`Cannot infer list of random ids for ${tlobject}`)
  280. }
  281. code = `new Array(id.length).fill().map(_ => ${code})`
  282. }
  283. builder.writeln(`this.randomId = args.randomId !== undefined ? args.randomId : ${code};`)
  284. } else {
  285. throw new Error(`Cannot infer a value for ${arg}`)
  286. }
  287. }
  288. builder.endBlock()
  289. }
  290. const writeResolve = (tlobject, builder) => {
  291. if (
  292. tlobject.isFunction &&
  293. tlobject.realArgs.some(
  294. (arg) =>
  295. arg.type in AUTO_CASTS ||
  296. (`${arg.name},${arg.type}` in NAMED_AUTO_CASTS && !NAMED_BLACKLIST.has(tlobject.fullname)),
  297. )
  298. ) {
  299. builder.writeln('async resolve(client, utils) {')
  300. for (const arg of tlobject.realArgs) {
  301. let ac = AUTO_CASTS[arg.type]
  302. if (!ac) {
  303. ac = NAMED_AUTO_CASTS[`${variableSnakeToCamelCase(arg.name)},${arg.type}`]
  304. if (!ac) {
  305. continue
  306. }
  307. }
  308. if (arg.isFlag) {
  309. builder.writeln(`if (this.${variableSnakeToCamelCase(arg.name)}) {`)
  310. }
  311. if (arg.isVector) {
  312. builder.write(`const _tmp = [];`)
  313. builder.writeln(`for (const _x of this.${variableSnakeToCamelCase(arg.name)}) {`)
  314. builder.writeln(`_tmp.push(%s);`, util.format(ac, '_x'))
  315. builder.endBlock()
  316. builder.writeln(`this.${variableSnakeToCamelCase(arg.name)} = _tmp;`)
  317. } else {
  318. builder.writeln(`this.${arg.name} = %s`,
  319. util.format(ac, `this.${variableSnakeToCamelCase(arg.name)}`))
  320. }
  321. if (arg.isFlag) {
  322. builder.currentIndent--
  323. builder.writeln('}')
  324. }
  325. }
  326. builder.endBlock()
  327. }
  328. }
  329. /**
  330. const writeToJson = (tlobject, builder) => {
  331. builder.writeln('toJson() {');
  332. builder.writeln('return {');
  333. builder.write("_: '%s'", tlobject.className);
  334. for (const arg of tlobject.realArgs) {
  335. builder.writeln(',');
  336. builder.write('%s: ', arg.name);
  337. if (BASE_TYPES.includes(arg.type)) {
  338. if (arg.isVector) {
  339. builder.write(
  340. 'this.%s === null ? [] : this.%s.slice()',
  341. arg.name,
  342. arg.name
  343. );
  344. } else {
  345. builder.write('this.%s', arg.name);
  346. }
  347. } else {
  348. if (arg.isVector) {
  349. builder.write(
  350. 'this.%s === null ? [] : this.%s.map(x => x instanceof TLObject ? x.toJson() : x)',
  351. arg.name,
  352. arg.name
  353. );
  354. } else {
  355. builder.write(
  356. 'this.%s instanceof TLObject ? this.%s.toJson() : this.%s',
  357. arg.name,
  358. arg.name,
  359. arg.name
  360. );
  361. }
  362. }
  363. }
  364. builder.writeln();
  365. builder.endBlock();
  366. builder.currentIndent--;
  367. builder.writeln('}');
  368. };
  369. */
  370. const writeToBytes = (tlobject, builder) => {
  371. builder.writeln('getBytes() {')
  372. // Some objects require more than one flag parameter to be set
  373. // at the same time. In this case, add an assertion.
  374. const repeatedArgs = {}
  375. for (const arg of tlobject.args) {
  376. if (arg.isFlag) {
  377. if (!repeatedArgs[arg.flagIndex]) {
  378. repeatedArgs[arg.flagIndex] = []
  379. }
  380. repeatedArgs[arg.flagIndex].push(arg)
  381. }
  382. }
  383. for (const ra of Object.values(repeatedArgs)) {
  384. if (ra.length > 1) {
  385. const cnd1 = []
  386. const cnd2 = []
  387. const names = []
  388. for (const a of ra) {
  389. cnd1.push(`this.${a.name} || this.${a.name}!==null`)
  390. cnd2.push(`this.${a.name}===null || this.${a.name}===false`)
  391. names.push(a.name)
  392. }
  393. builder.writeln(
  394. 'if (!((%s) && (%s)))\n\t throw new Error(\'%s paramaters must all be false-y or all true\')',
  395. cnd1.join(' && '),
  396. cnd2.join(' && '),
  397. names.join(', '),
  398. )
  399. }
  400. }
  401. builder.writeln('return Buffer.concat([')
  402. builder.currentIndent++
  403. const b = Buffer.alloc(4)
  404. b.writeUInt32LE(tlobject.id, 0)
  405. // First constructor code, we already know its bytes
  406. builder.writeln('Buffer.from("%s","hex"),', b.toString('hex'))
  407. for (const arg of tlobject.args) {
  408. if (writeArgToBytes(builder, arg, tlobject.args)) {
  409. builder.writeln(',')
  410. }
  411. }
  412. builder.writeln('])')
  413. builder.endBlock()
  414. }
  415. // writeFromReader
  416. const writeFromReader = (tlobject, builder) => {
  417. builder.writeln('static fromReader(reader) {')
  418. for (const arg of tlobject.args) {
  419. if (arg.name !== 'flag') {
  420. if (arg.name !== 'x') {
  421. builder.writeln('let %s', '_' + arg.name + ';')
  422. }
  423. }
  424. }
  425. // TODO fix this really
  426. builder.writeln('let _x;')
  427. builder.writeln('let len;')
  428. for (const arg of tlobject.args) {
  429. writeArgReadCode(builder, arg, tlobject.args, '_' + arg.name)
  430. }
  431. const temp = []
  432. for (const a of tlobject.realArgs) {
  433. temp.push(`${variableSnakeToCamelCase(a.name)}:_${a.name}`)
  434. }
  435. builder.writeln('return new this({%s})', temp.join(',\n\t'))
  436. builder.endBlock()
  437. }
  438. // writeReadResult
  439. /**
  440. * Writes the .__bytes__() code for the given argument
  441. * @param builder: The source code builder
  442. * @param arg: The argument to write
  443. * @param args: All the other arguments in TLObject same __bytes__.
  444. * This is required to determine the flags value
  445. * @param name: The name of the argument. Defaults to "self.argname"
  446. * This argument is an option because it's required when
  447. * writing Vectors<>
  448. */
  449. const writeArgToBytes = (builder, arg, args, name = null) => {
  450. if (arg.genericDefinition) {
  451. return // Do nothing, this only specifies a later type
  452. }
  453. if (name === null) {
  454. name = `this.${arg.name}`
  455. }
  456. name = variableSnakeToCamelCase(name)
  457. // The argument may be a flag, only write if it's not None AND
  458. // if it's not a True type.
  459. // True types are not actually sent, but instead only used to
  460. // determine the flags.
  461. if (arg.isFlag) {
  462. if (arg.type === 'true') {
  463. return // Exit, since true type is never written
  464. } else if (arg.isVector) {
  465. // Vector flags are special since they consist of 3 values,
  466. // so we need an extra join here. Note that empty vector flags
  467. // should NOT be sent either!
  468. builder.write(
  469. '(%s === undefined || %s === false || %s ===null) ? Buffer.alloc(0) :Buffer.concat([',
  470. name,
  471. name,
  472. name,
  473. )
  474. } else {
  475. builder.write('(%s === undefined || %s === false || %s ===null) ? Buffer.alloc(0) : [', name, name, name)
  476. }
  477. }
  478. if (arg.isVector) {
  479. if (arg.useVectorId) {
  480. builder.write('Buffer.from(\'15c4b51c\', \'hex\'),')
  481. }
  482. builder.write('struct.pack(\'<i\', %s.length),', name)
  483. // Cannot unpack the values for the outer tuple through *[(
  484. // since that's a Python >3.5 feature, so add another join.
  485. builder.write('Buffer.concat(%s.map(x => ', name)
  486. // Temporary disable .is_vector, not to enter this if again
  487. // Also disable .is_flag since it's not needed per element
  488. const oldFlag = arg.isFlag
  489. arg.isVector = arg.isFlag = false
  490. writeArgToBytes(builder, arg, args, 'x')
  491. arg.isVector = true
  492. arg.isFlag = oldFlag
  493. builder.write('))')
  494. } else if (arg.flagIndicator) {
  495. // Calculate the flags with those items which are not None
  496. if (!args.some((f) => f.isFlag)) {
  497. // There's a flag indicator, but no flag arguments so it's 0
  498. builder.write('Buffer.alloc(4)')
  499. } else {
  500. builder.write('struct.pack(\'<I\', ')
  501. builder.write(
  502. args
  503. .filter((flag) => flag.isFlag)
  504. .map(
  505. (flag) =>
  506. `(this.${variableSnakeToCamelCase(
  507. flag.name,
  508. )} === undefined || this.${variableSnakeToCamelCase(
  509. flag.name,
  510. )} === false || this.${variableSnakeToCamelCase(flag.name)} === null) ? 0 : ${1 <<
  511. flag.flagIndex}`,
  512. )
  513. .join(' | '),
  514. )
  515. builder.write(')')
  516. }
  517. } else if (arg.type === 'int') {
  518. builder.write('struct.pack(\'<i\', %s)', name)
  519. } else if (arg.type === 'long') {
  520. builder.write('readBufferFromBigInt(%s,8,true,true)', name)
  521. } else if (arg.type === 'int128') {
  522. builder.write('readBufferFromBigInt(%s,16,true,true)', name)
  523. } else if (arg.type === 'int256') {
  524. builder.write('readBufferFromBigInt(%s,32,true,true)', name)
  525. } else if (arg.type === 'double') {
  526. builder.write('struct.pack(\'<d\', %s.toString())', name)
  527. } else if (arg.type === 'string') {
  528. builder.write('TLObject.serializeBytes(%s)', name)
  529. } else if (arg.type === 'Bool') {
  530. builder.write('%s ? 0xb5757299 : 0x379779bc', name)
  531. } else if (arg.type === 'true') {
  532. // These are actually NOT written! Only used for flags
  533. } else if (arg.type === 'bytes') {
  534. builder.write('TLObject.serializeBytes(%s)', name)
  535. } else if (arg.type === 'date') {
  536. builder.write('TLObject.serializeDatetime(%s)', name)
  537. } else {
  538. // Else it may be a custom type
  539. builder.write('%s.getBytes()', name)
  540. // If the type is not boxed (i.e. starts with lowercase) we should
  541. // not serialize the constructor ID (so remove its first 4 bytes).
  542. let boxed = arg.type.charAt(arg.type.indexOf('.') + 1)
  543. boxed = boxed === boxed.toUpperCase()
  544. if (!boxed) {
  545. builder.write('.slice(4)')
  546. }
  547. }
  548. if (arg.isFlag) {
  549. builder.write(']')
  550. if (arg.isVector) {
  551. builder.write(')')
  552. }
  553. }
  554. return true
  555. }
  556. /**
  557. * Writes the read code for the given argument, setting the
  558. * arg.name variable to its read value.
  559. * @param builder The source code builder
  560. * @param arg The argument to write
  561. * @param args All the other arguments in TLObject same on_send.
  562. * This is required to determine the flags value
  563. * @param name The name of the argument. Defaults to "self.argname"
  564. * This argument is an option because it's required when
  565. * writing Vectors<>
  566. */
  567. const writeArgReadCode = (builder, arg, args, name) => {
  568. if (arg.genericDefinition) {
  569. return // Do nothing, this only specifies a later type
  570. }
  571. // The argument may be a flag, only write that flag was given!
  572. let wasFlag = false
  573. if (arg.isFlag) {
  574. // Treat 'true' flags as a special case, since they're true if
  575. // they're set, and nothing else needs to actually be read.
  576. if (arg.type === 'true') {
  577. builder.writeln('%s = Boolean(flags & %s);', name, 1 << arg.flagIndex)
  578. return
  579. }
  580. wasFlag = true
  581. builder.writeln('if (flags & %s) {', 1 << arg.flagIndex)
  582. // Temporary disable .is_flag not to enter this if
  583. // again when calling the method recursively
  584. arg.isFlag = false
  585. }
  586. if (arg.isVector) {
  587. if (arg.useVectorId) {
  588. // We have to read the vector's constructor ID
  589. builder.writeln('reader.readInt();')
  590. }
  591. builder.writeln('%s = [];', name)
  592. builder.writeln('len = reader.readInt();')
  593. builder.writeln('for (let i=0;i<len;i++){')
  594. // Temporary disable .is_vector, not to enter this if again
  595. arg.isVector = false
  596. writeArgReadCode(builder, arg, args, '_x')
  597. builder.writeln('%s.push(_x);', name)
  598. arg.isVector = true
  599. } else if (arg.flagIndicator) {
  600. // Read the flags, which will indicate what items we should read next
  601. builder.writeln('let flags = reader.readInt();')
  602. builder.writeln()
  603. } else if (arg.type === 'int') {
  604. builder.writeln('%s = reader.readInt();', name)
  605. } else if (arg.type === 'long') {
  606. builder.writeln('%s = reader.readLong();', name)
  607. } else if (arg.type === 'int128') {
  608. builder.writeln('%s = reader.readLargeInt(128);', name)
  609. } else if (arg.type === 'int256') {
  610. builder.writeln('%s = reader.readLargeInt(256);', name)
  611. } else if (arg.type === 'double') {
  612. builder.writeln('%s = reader.readDouble();', name)
  613. } else if (arg.type === 'string') {
  614. builder.writeln('%s = reader.tgReadString();', name)
  615. } else if (arg.type === 'Bool') {
  616. builder.writeln('%s = reader.tgReadBool();', name)
  617. } else if (arg.type === 'true') {
  618. builder.writeln('%s = true;', name)
  619. } else if (arg.type === 'bytes') {
  620. builder.writeln('%s = reader.tgReadBytes();', name)
  621. } else if (arg.type === 'date') {
  622. builder.writeln('%s = reader.tgReadDate();', name)
  623. } else {
  624. // Else it may be a custom type
  625. if (!arg.skipConstructorId) {
  626. builder.writeln('%s = reader.tgReadObject();', name)
  627. } else {
  628. // Import the correct type inline to avoid cyclic imports.
  629. // There may be better solutions so that we can just access
  630. // all the types before the files have been parsed, but I
  631. // don't know of any.
  632. const sepIndex = arg.type.indexOf('.')
  633. let ns
  634. let t
  635. if (sepIndex === -1) {
  636. ns = '.'
  637. t = arg.type
  638. } else {
  639. ns = '.' + arg.type.slice(0, sepIndex)
  640. t = arg.type.slice(sepIndex + 1)
  641. }
  642. const className = snakeToCamelCase(t)
  643. // There would be no need to import the type if we're in the
  644. // file with the same namespace, but since it does no harm
  645. // and we don't have information about such thing in the
  646. // method we just ignore that case.
  647. builder.writeln('let %s = require("%s");', className, ns)
  648. builder.writeln('%s = %s.fromReader(reader);', name, className)
  649. }
  650. }
  651. // End vector and flag blocks if required (if we opened them before)
  652. if (arg.isVector) {
  653. builder.writeln('}')
  654. }
  655. if (wasFlag) {
  656. builder.endBlock()
  657. builder.writeln('else {')
  658. builder.writeln('%s = null', name)
  659. builder.endBlock()
  660. // Restore .isFlag;
  661. arg.isFlag = true
  662. }
  663. }
  664. const writePatched = (outDir, namespaceTlobjects) => {
  665. fs.mkdirSync(outDir, { recursive: true })
  666. for (const [ns, tlobjects] of Object.entries(namespaceTlobjects)) {
  667. const file = `${outDir}/${ns === 'null' ? 'index' : ns}.js`
  668. const stream = fs.createWriteStream(file)
  669. const builder = new SourceBuilder(stream)
  670. builder.writeln(AUTO_GEN_NOTICE)
  671. builder.writeln('const struct = require(\'python-struct\');')
  672. builder.writeln(`const { TLObject, types, custom } = require('..');`)
  673. builder.writeln()
  674. for (const t of tlobjects) {
  675. builder.writeln('class %s extends custom.%s {', t.className, PATCHED_TYPES[t.fullname])
  676. builder.writeln(`static CONSTRUCTOR_ID = 0x${t.id.toString(16)}`)
  677. builder.writeln(`static SUBCLASS_OF_ID = 0x${crc32(t.result).toString('16')}`)
  678. builder.writeln()
  679. builder.writeln('constructor() {')
  680. builder.writeln('super();')
  681. builder.writeln(`this.CONSTRUCTOR_ID = 0x${t.id.toString(16)}`)
  682. builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(t.result).toString('16')}`)
  683. builder.endBlock()
  684. // writeToJson(t, builder);
  685. writeToBytes(t, builder)
  686. writeFromReader(t, builder)
  687. builder.writeln()
  688. builder.endBlock()
  689. builder.currentIndent = 0
  690. builder.writeln('types.%s%s = %s', t.namespace ? `${t.namespace}.` : '', t.className, t.className)
  691. builder.writeln()
  692. }
  693. }
  694. }
  695. const writeAllTLObjects = (tlobjects, layer, builder) => {
  696. builder.writeln(AUTO_GEN_NOTICE)
  697. builder.writeln()
  698. builder.writeln('const { types, functions, patched } = require(\'.\');')
  699. builder.writeln()
  700. // Create a constant variable to indicate which layer this is
  701. builder.writeln(`const LAYER = %s;`, layer)
  702. builder.writeln()
  703. // Then create the dictionary containing constructor_id: class
  704. builder.writeln('const tlobjects = {')
  705. // Fill the dictionary (0x1a2b3c4f: tl.full.type.path.Class)
  706. for (const tlobject of tlobjects) {
  707. builder.write('0x0%s: ', tlobject.id.toString(16).padStart(8, '0'))
  708. if (tlobject.fullname in PATCHED_TYPES) {
  709. builder.write('patched')
  710. } else {
  711. builder.write(tlobject.isFunction ? 'functions' : 'types')
  712. }
  713. if (tlobject.namespace) {
  714. builder.write('.%s', tlobject.namespace)
  715. }
  716. builder.writeln('.%s,', tlobject.className)
  717. }
  718. builder.endBlock(true)
  719. builder.writeln('')
  720. builder.writeln('module.exports = {')
  721. builder.writeln('LAYER,')
  722. builder.writeln('tlobjects')
  723. builder.endBlock(true)
  724. }
  725. const generateTLObjects = (tlobjects, layer, importDepth, outputDir) => {
  726. // Group everything by {namespace :[tlobjects]} to generate index.js
  727. const namespaceFunctions = {}
  728. const namespaceTypes = {}
  729. const namespacePatched = {}
  730. // Group {type: [constructors]} to generate the documentation
  731. const typeConstructors = {}
  732. for (const tlobject of tlobjects) {
  733. if (tlobject.isFunction) {
  734. if (!namespaceFunctions[tlobject.namespace]) {
  735. namespaceFunctions[tlobject.namespace] = []
  736. }
  737. namespaceFunctions[tlobject.namespace].push(tlobject)
  738. } else {
  739. if (!namespaceTypes[tlobject.namespace]) {
  740. namespaceTypes[tlobject.namespace] = []
  741. }
  742. if (!typeConstructors[tlobject.result]) {
  743. typeConstructors[tlobject.result] = []
  744. }
  745. namespaceTypes[tlobject.namespace].push(tlobject)
  746. typeConstructors[tlobject.result].push(tlobject)
  747. if (tlobject.fullname in PATCHED_TYPES) {
  748. if (!namespacePatched[tlobject.namespace]) {
  749. namespacePatched[tlobject.namespace] = []
  750. }
  751. namespacePatched[tlobject.namespace].push(tlobject)
  752. }
  753. }
  754. }
  755. writeModules(`${outputDir}/functions`, importDepth, 'TLRequest', namespaceFunctions, typeConstructors)
  756. writeModules(`${outputDir}/types`, importDepth, 'TLObject', namespaceTypes, typeConstructors)
  757. writePatched(`${outputDir}/patched`, namespacePatched)
  758. const filename = `${outputDir}/AllTLObjects.js`
  759. const stream = fs.createWriteStream(filename)
  760. const builder = new SourceBuilder(stream)
  761. writeAllTLObjects(tlobjects, layer, builder)
  762. }
  763. const cleanTLObjects = (outputDir) => {
  764. for (let d of ['functions', 'types', 'patched']) {
  765. d = `${outputDir}/d`
  766. if (fs.statSync(d).isDirectory()) {
  767. fs.rmdirSync(d)
  768. }
  769. }
  770. const tl = `${outputDir}/AllTLObjects.js`
  771. if (fs.statSync(tl).isFile()) {
  772. fs.unlinkSync(tl)
  773. }
  774. }
  775. const writeModuleExports = (tlobjects, builder) => {
  776. builder.writeln('module.exports = {')
  777. for (const t of tlobjects) {
  778. builder.writeln(`${t.className},`)
  779. }
  780. builder.currentIndent--
  781. builder.writeln('};')
  782. }
  783. module.exports = {
  784. generateTLObjects,
  785. cleanTLObjects,
  786. }