tlobject.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. const { crc32 } = require('crc')
  2. const struct = require('python-struct')
  3. const { snakeToCamelCase } = require('../../utils')
  4. // https://github.com/telegramdesktop/tdesktop/blob/4bf66cb6e93f3965b40084771b595e93d0b11bcd/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py#L57-L62
  5. const WHITELISTED_MISMATCHING_IDS = {
  6. // 0 represents any layer
  7. 0: new Set([
  8. 'channel', // Since layer 77, there seems to be no going back...
  9. 'ipPortSecret',
  10. 'accessPointRule',
  11. 'help.configSimple',
  12. ]),
  13. }
  14. /**
  15. * Initializes a new TLObject, given its properties.
  16. *
  17. * :param fullname: The fullname of the TL object (namespace.name)
  18. * The namespace can be omitted.
  19. * :param object_id: The hexadecimal string representing the object ID
  20. * :param args: The arguments, if any, of the TL object
  21. * :param result: The result type of the TL object
  22. * :param is_function: Is the object a function or a type?
  23. * :param usability: The usability for this method.
  24. * :param friendly: A tuple (namespace, friendly method name) if known.
  25. * :param layer: The layer this TLObject belongs to.
  26. */
  27. class TLObject {
  28. constructor(fullname, objectId, args, result, isFunction, usability, friendly, layer) {
  29. // The name can or not have a namespace
  30. this.fullname = fullname
  31. if (fullname.includes('.')) {
  32. [this.namespace, this.name] = fullname.split(/\.(.+)/)
  33. // console.log(fullname.split(/\.(.+)/));
  34. // const [namespace, ...name] = fullname.split('.');
  35. // this.namespace = namespace;
  36. // this.name = name.join('.');
  37. } else {
  38. this.namespace = null
  39. this.name = fullname
  40. }
  41. this.args = args
  42. this.result = result
  43. this.isFunction = isFunction
  44. this.usability = usability
  45. this.friendly = friendly
  46. this.id = null
  47. if (!objectId) {
  48. this.id = this.inferId()
  49. } else {
  50. this.id = parseInt(objectId, 16)
  51. const whitelist = new Set([
  52. ...WHITELISTED_MISMATCHING_IDS[0],
  53. ...(WHITELISTED_MISMATCHING_IDS[layer] || []),
  54. ])
  55. if (!whitelist.has(this.fullname)) {
  56. if (this.id !== this.inferId()) {
  57. throw new Error(`Invalid inferred ID for ${this.repr()}`)
  58. }
  59. }
  60. }
  61. this.className = snakeToCamelCase(this.name, this.isFunction ? 'Request' : '')
  62. this.realArgs = this.sortedArgs().filter((a) => !(a.flagIndicator || a.genericDefinition))
  63. }
  64. get innermostResult() {
  65. const index = this.result.indexOf('<')
  66. return index === -1 ? this.result : this.result.slice(index + 1, -1)
  67. }
  68. /**
  69. * Returns the arguments properly sorted and ready to plug-in
  70. * into a Python's method header (i.e., flags and those which
  71. * can be inferred will go last so they can default =None)
  72. */
  73. sortedArgs() {
  74. return this.args.sort((x) => x.isFlag || x.canBeInferred)
  75. }
  76. repr(ignoreId) {
  77. let hexId
  78. let args
  79. if (this.id === null || ignoreId) {
  80. hexId = ''
  81. } else {
  82. hexId = `#${this.id.toString(16).padStart(8, '0')}`
  83. }
  84. if (this.args.length) {
  85. args = ` ${this.args.map((arg) => arg.toString()).join(' ')}`
  86. } else {
  87. args = ''
  88. }
  89. return `${this.fullname}${hexId}${args} = ${this.result}`
  90. }
  91. inferId() {
  92. const representation = this.repr(true)
  93. .replace(/(:|\?)bytes /g, '$1string ')
  94. .replace(/</g, ' ')
  95. .replace(/>|{|}/g, '')
  96. .replace(/ \w+:flags\.\d+\?true/g, '')
  97. if (this.fullname === 'inputMediaInvoice') {
  98. // eslint-disable-next-line no-empty
  99. if (this.fullname === 'inputMediaInvoice') {
  100. }
  101. }
  102. return crc32(Buffer.from(representation, 'utf8'))
  103. }
  104. toJson() {
  105. return {
  106. id: struct.unpack('i', struct.pack('I', this.id))[0].toString(),
  107. [this.isFunction ? 'method' : 'predicate']: this.fullname,
  108. param: this.args.filter((x) => !x.genericDefinition).map((x) => x.toJson()),
  109. type: this.result,
  110. }
  111. }
  112. isGoodExample() {
  113. return !this.className.endsWith('Empty')
  114. }
  115. asExample(f, indent) {
  116. f.write('<strong>new</strong> ')
  117. f.write(this.isFunction ? 'functions' : 'types')
  118. if (this.namespace) {
  119. f.write('.')
  120. f.write(this.namespace)
  121. }
  122. f.write('.')
  123. f.write(this.className)
  124. f.write('(')
  125. const args = this.realArgs.filter((arg) => !arg.omitExample())
  126. if (!args.length) {
  127. f.write(')')
  128. return
  129. }
  130. f.write('\n')
  131. indent++
  132. let remaining = args.length
  133. for (const arg of args) {
  134. remaining--
  135. f.write(' '.repeat(indent))
  136. f.write(arg.name)
  137. f.write('=')
  138. if (arg.isVector) {
  139. f.write('[')
  140. }
  141. arg.asExample(f, indent || 0)
  142. if (arg.isVector) {
  143. f.write(']')
  144. }
  145. if (remaining) {
  146. f.write(',')
  147. }
  148. f.write('\n')
  149. }
  150. indent--
  151. f.write(' '.repeat(indent))
  152. f.write(')')
  153. }
  154. }
  155. module.exports = {
  156. TLObject,
  157. }