tlarg.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. const fmtStrings = (...objects) => {
  2. for (const object of objects) {
  3. for (const [k, v] of Object.entries(object)) {
  4. if (['null', 'true', 'false'].includes(v)) {
  5. object[k] = `<strong>${v}</strong>`
  6. } else {
  7. object[k] = v.replace(/((['"]).*\2)/, (_, g) => `<em>${g}</em>`)
  8. }
  9. }
  10. }
  11. }
  12. const KNOWN_NAMED_EXAMPLES = {
  13. 'message,string': '\'Hello there!\'',
  14. 'expires_at,date': 'datetime.timedelta(minutes=5)',
  15. 'until_date,date': 'datetime.timedelta(days=14)',
  16. 'view_messages,true': 'None',
  17. 'send_messages,true': 'None',
  18. 'limit,int': '100',
  19. 'hash,int': '0',
  20. 'hash,string': '\'A4LmkR23G0IGxBE71zZfo1\'',
  21. 'min_id,int': '0',
  22. 'max_id,int': '0',
  23. 'min_id,long': '0',
  24. 'max_id,long': '0',
  25. 'add_offset,int': '0',
  26. 'title,string': '\'My awesome title\'',
  27. 'device_model,string': '\'ASUS Laptop\'',
  28. 'system_version,string': '\'Arch Linux\'',
  29. 'app_version,string': '\'1.0\'',
  30. 'system_lang_code,string': '\'en\'',
  31. 'lang_pack,string': '\'\'',
  32. 'lang_code,string': '\'en\'',
  33. 'chat_id,int': '478614198',
  34. 'client_id,long': 'random.randrange(-2**63, 2**63)',
  35. }
  36. const KNOWN_TYPED_EXAMPLES = {
  37. int128: 'int.from_bytes(crypto.randomBytes(16), \'big\')',
  38. bytes: 'b\'arbitrary\\x7f data \\xfa here\'',
  39. long: '-12398745604826',
  40. string: '\'some string here\'',
  41. int: '42',
  42. date: 'datetime.datetime(2018, 6, 25)',
  43. double: '7.13',
  44. Bool: 'False',
  45. true: 'True',
  46. InputChatPhoto: 'client.upload_file(\'/path/to/photo.jpg\')',
  47. InputFile: 'client.upload_file(\'/path/to/file.jpg\')',
  48. InputPeer: '\'username\'',
  49. }
  50. fmtStrings(KNOWN_NAMED_EXAMPLES, KNOWN_TYPED_EXAMPLES)
  51. const SYNONYMS = {
  52. InputUser: 'InputPeer',
  53. InputChannel: 'InputPeer',
  54. InputDialogPeer: 'InputPeer',
  55. InputNotifyPeer: 'InputPeer',
  56. InputMessage: 'int',
  57. }
  58. // These are flags that are cleaner to leave off
  59. const OMITTED_EXAMPLES = [
  60. 'silent',
  61. 'background',
  62. 'clear_draft',
  63. 'reply_to_msg_id',
  64. 'random_id',
  65. 'reply_markup',
  66. 'entities',
  67. 'embed_links',
  68. 'hash',
  69. 'min_id',
  70. 'max_id',
  71. 'add_offset',
  72. 'grouped',
  73. 'broadcast',
  74. 'admins',
  75. 'edit',
  76. 'delete',
  77. ]
  78. /**
  79. * Initializes a new .tl argument
  80. * :param name: The name of the .tl argument
  81. * :param argType: The type of the .tl argument
  82. * :param genericDefinition: Is the argument a generic definition?
  83. * (i.e. {X:Type})
  84. */
  85. class TLArg {
  86. constructor(name, argType, genericDefinition) {
  87. this.name = name === 'self' ? 'is_self' : name
  88. // Default values
  89. this.isVector = false
  90. this.isFlag = false
  91. this.skipConstructorId = false
  92. this.flagIndex = -1
  93. this.cls = null
  94. // Special case: some types can be inferred, which makes it
  95. // less annoying to type. Currently the only type that can
  96. // be inferred is if the name is 'random_id', to which a
  97. // random ID will be assigned if left as None (the default)
  98. this.canBeInferred = name === 'random_id'
  99. // The type can be an indicator that other arguments will be flags
  100. if (argType === '#') {
  101. this.flagIndicator = true
  102. this.type = null
  103. this.isGeneric = false
  104. } else {
  105. this.flagIndicator = false
  106. this.isGeneric = argType.startsWith('!')
  107. // Strip the exclamation mark always to have only the name
  108. this.type = argType.replace(/^!+/, '')
  109. // The type may be a flag (flags.IDX?REAL_TYPE)
  110. // Note that 'flags' is NOT the flags name; this
  111. // is determined by a previous argument
  112. // However, we assume that the argument will always be called 'flags'
  113. const flagMatch = this.type.match(/flags.(\d+)\?([\w<>.]+)/)
  114. if (flagMatch) {
  115. this.isFlag = true
  116. this.flagIndex = Number(flagMatch[1]);
  117. // Update the type to match the exact type, not the "flagged" one
  118. [, , this.type] = flagMatch
  119. }
  120. // Then check if the type is a Vector<REAL_TYPE>
  121. const vectorMatch = this.type.match(/[Vv]ector<([\w\d.]+)>/)
  122. if (vectorMatch) {
  123. this.isVector = true
  124. // If the type's first letter is not uppercase, then
  125. // it is a constructor and we use (read/write) its ID.
  126. this.useVectorId = this.type.charAt(0) === 'V';
  127. // Update the type to match the one inside the vector
  128. [, this.type] = vectorMatch
  129. }
  130. // See use_vector_id. An example of such case is ipPort in
  131. // help.configSpecial
  132. if (
  133. /^[a-z]$/.test(
  134. this.type
  135. .split('.')
  136. .pop()
  137. .charAt(0),
  138. )
  139. ) {
  140. this.skipConstructorId = true
  141. }
  142. // The name may contain "date" in it, if this is the case and
  143. // the type is "int", we can safely assume that this should be
  144. // treated as a "date" object. Note that this is not a valid
  145. // Telegram object, but it's easier to work with
  146. // if (
  147. // this.type === 'int' &&
  148. // (/(\b|_)([dr]ate|until|since)(\b|_)/.test(name) ||
  149. // ['expires', 'expires_at', 'was_online'].includes(name))
  150. // ) {
  151. // this.type = 'date';
  152. // }
  153. }
  154. this.genericDefinition = genericDefinition
  155. }
  156. typeHint() {
  157. let cls = this.type
  158. if (cls.includes('.')) {
  159. [, cls] = cls.split('.')
  160. }
  161. let result = {
  162. int: 'int',
  163. long: 'int',
  164. int128: 'int',
  165. int256: 'int',
  166. double: 'float',
  167. string: 'str',
  168. date: 'Optional[datetime]', // None date = 0 timestamp
  169. bytes: 'bytes',
  170. Bool: 'bool',
  171. true: 'bool',
  172. }
  173. result = result[cls] || `'Type${cls}'`
  174. if (this.isVector) {
  175. result = `List[${result}]`
  176. }
  177. if (this.isFlag && cls !== 'date') {
  178. result = `Optional[${result}]`
  179. }
  180. return result
  181. }
  182. realType() {
  183. // Find the real type representation by updating it as required
  184. let realType = this.flagIndicator ? '#' : this.type
  185. if (this.isVector) {
  186. if (this.useVectorId) {
  187. realType = `Vector<${realType}>`
  188. } else {
  189. realType = `vector<${realType}>`
  190. }
  191. }
  192. if (this.isGeneric) {
  193. realType = `!${realType}`
  194. }
  195. if (this.isFlag) {
  196. realType = `flags.${this.flagIndex}?${realType}`
  197. }
  198. return realType
  199. }
  200. toString() {
  201. if (this.genericDefinition) {
  202. return `{${this.name}:${this.realType()}}`
  203. } else {
  204. return `${this.name}:${this.realType()}`
  205. }
  206. }
  207. toJson() {
  208. return {
  209. name: this.name.replace('is_self', 'self'),
  210. type: this.realType().replace(/\bdate$/, 'int'),
  211. }
  212. }
  213. asExample(f, indent) {
  214. if (this.isGeneric) {
  215. f.write('other_request')
  216. return
  217. }
  218. const known =
  219. KNOWN_NAMED_EXAMPLES[`${this.name},${this.type}`] ||
  220. KNOWN_TYPED_EXAMPLES[this.type] ||
  221. KNOWN_TYPED_EXAMPLES[SYNONYMS[this.type]]
  222. if (known) {
  223. f.write(known)
  224. return
  225. }
  226. // assert self.omit_example() or self.cls, 'TODO handle ' + str(self)
  227. // Pick an interesting example if any
  228. for (const cls of this.cls) {
  229. if (cls.isGoodExample()) {
  230. cls.asExample(f, indent || 0)
  231. return
  232. }
  233. }
  234. // If no example is good, just pick the first
  235. this.cls[0].asExample(f, indent || 0)
  236. }
  237. omitExample() {
  238. return this.isFlag || (this.canBeInferred && OMITTED_EXAMPLES.includes(this.name))
  239. }
  240. }
  241. module.exports = {
  242. KNOWN_NAMED_EXAMPLES,
  243. KNOWN_TYPED_EXAMPLES,
  244. SYNONYMS,
  245. OMITTED_EXAMPLES,
  246. TLArg,
  247. }