tlobject.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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(
  29. fullname,
  30. objectId,
  31. args,
  32. result,
  33. isFunction,
  34. usability,
  35. friendly,
  36. layer
  37. ) {
  38. // The name can or not have a namespace
  39. this.fullname = fullname;
  40. if (fullname.includes('.')) {
  41. [this.namespace, this.name] = fullname.split(/\.(.+)/);
  42. // console.log(fullname.split(/\.(.+)/));
  43. // const [namespace, ...name] = fullname.split('.');
  44. // this.namespace = namespace;
  45. // this.name = name.join('.');
  46. } else {
  47. this.namespace = null;
  48. this.name = fullname;
  49. }
  50. this.args = args;
  51. this.result = result;
  52. this.isFunction = isFunction;
  53. this.usability = usability;
  54. this.friendly = friendly;
  55. this.id = null;
  56. if (!objectId) {
  57. this.id = this.inferId();
  58. } else {
  59. this.id = parseInt(objectId, 16);
  60. const whitelist = new Set([
  61. ...WHITELISTED_MISMATCHING_IDS[0],
  62. ...(WHITELISTED_MISMATCHING_IDS[layer] || []),
  63. ]);
  64. if (!whitelist.has(this.fullname)) {
  65. if (this.id !== this.inferId()) {
  66. throw new Error(`Invalid inferred ID for ${this.repr()}`);
  67. }
  68. }
  69. }
  70. this.className = snakeToCamelCase(
  71. this.name,
  72. this.isFunction ? 'Request' : ''
  73. );
  74. this.realArgs = this.sortedArgs().filter(
  75. a => !(a.flagIndicator || a.genericDefinition)
  76. );
  77. }
  78. get innermostResult() {
  79. const index = this.result.indexOf('<');
  80. return index === -1 ? this.result : this.result.slice(index + 1, -1);
  81. }
  82. /**
  83. * Returns the arguments properly sorted and ready to plug-in
  84. * into a Python's method header (i.e., flags and those which
  85. * can be inferred will go last so they can default =None)
  86. */
  87. sortedArgs() {
  88. return this.args.sort(x => x.isFlag || x.canBeInferred);
  89. }
  90. repr(ignoreId) {
  91. let hexId, args;
  92. if (this.id === null || ignoreId) {
  93. hexId = '';
  94. } else {
  95. hexId = `#${this.id.toString(16).padStart(8, '0')}`;
  96. }
  97. if (this.args.length) {
  98. args = ` ${this.args.map(arg => arg.toString()).join(' ')}`;
  99. } else {
  100. args = '';
  101. }
  102. return `${this.fullname}${hexId}${args} = ${this.result}`;
  103. }
  104. inferId() {
  105. const representation = this.repr(true)
  106. .replace(/(:|\?)bytes /g, '$1string ')
  107. .replace(/</g, ' ')
  108. .replace(/>|{|}/g, '')
  109. .replace(/ \w+:flags\.\d+\?true/g, '');
  110. if (this.fullname === 'inputMediaInvoice') {
  111. if (this.fullname === 'inputMediaInvoice') {
  112. }
  113. }
  114. return crc32(Buffer.from(representation, 'utf8'));
  115. }
  116. toJson() {
  117. return {
  118. id: struct.unpack('i', struct.pack('I', this.id))[0].toString(),
  119. [this.isFunction ? 'method' : 'predicate']: this.fullname,
  120. param: this.args
  121. .filter(x => !x.genericDefinition)
  122. .map(x => x.toJson()),
  123. type: this.result,
  124. };
  125. }
  126. isGoodExample() {
  127. return !this.className.endsWith('Empty');
  128. }
  129. asExample(f, indent) {
  130. f.write('<strong>new</strong> ');
  131. f.write(this.isFunction ? 'functions' : 'types');
  132. if (this.namespace) {
  133. f.write('.');
  134. f.write(this.namespace);
  135. }
  136. f.write('.');
  137. f.write(this.className);
  138. f.write('(');
  139. const args = this.realArgs.filter(arg => !arg.omitExample());
  140. if (!args.length) {
  141. f.write(')');
  142. return;
  143. }
  144. f.write('\n');
  145. indent++;
  146. let remaining = args.length;
  147. for (const arg of args) {
  148. remaining--;
  149. f.write(' '.repeat(indent));
  150. f.write(arg.name);
  151. f.write('=');
  152. if (arg.isVector) {
  153. f.write('[');
  154. }
  155. arg.asExample(f, indent || 0);
  156. if (arg.isVector) {
  157. f.write(']');
  158. }
  159. if (remaining) {
  160. f.write(',');
  161. }
  162. f.write('\n');
  163. }
  164. indent--;
  165. f.write(' '.repeat(indent));
  166. f.write(')');
  167. }
  168. }
  169. module.exports = {
  170. TLObject,
  171. };