1
0
Эх сурвалжийг харах

Merge test repo into main

painor 4 жил өмнө
parent
commit
8727367186
70 өөрчлөгдсөн 17764 нэмэгдсэн , 2244 устгасан
  1. 16 3
      .bablerc
  2. 1 3
      .gitignore
  3. 5 3
      README.md
  4. 1 0
      empty.txt
  5. 2 2
      examples/simpleLogin.html
  6. 1 3
      examples/simpleLogin.js
  7. 12 0
      gramjs/.editorconfig
  8. 152 124
      gramjs/Helpers.js
  9. 76 62
      gramjs/Password.js
  10. 171 637
      gramjs/Utils.js
  11. 22 0
      gramjs/client/TelegramClient.d.ts
  12. 423 372
      gramjs/client/TelegramClient.js
  13. 332 0
      gramjs/client/auth.ts
  14. 192 0
      gramjs/client/downloadFile.ts
  15. 129 0
      gramjs/client/uploadFile.ts
  16. 33 15
      gramjs/crypto/AuthKey.js
  17. 17 0
      gramjs/crypto/CTR.js
  18. 59 71
      gramjs/crypto/Factorizator.js
  19. 37 0
      gramjs/crypto/IGE.js
  20. 44 76
      gramjs/crypto/RSA.js
  21. 55 0
      gramjs/crypto/converters.ts
  22. 123 0
      gramjs/crypto/crypto.js
  23. 51 0
      gramjs/crypto/words.ts
  24. 13 17
      gramjs/errors/Common.js
  25. 8 4
      gramjs/errors/RPCBaseErrors.js
  26. 87 0
      gramjs/errors/RPCErrorList.js
  27. 4 7
      gramjs/errors/index.js
  28. 31 73
      gramjs/events/NewMessage.js
  29. 10 12
      gramjs/events/Raw.js
  30. 5 5
      gramjs/extensions/BinaryReader.js
  31. 16 17
      gramjs/extensions/Logger.js
  32. 14 11
      gramjs/extensions/MessagePacker.js
  33. 31 8
      gramjs/extensions/PromisedNetSockets.js
  34. 44 21
      gramjs/extensions/PromisedWebSockets.js
  35. 0 15
      gramjs/extensions/index.js
  36. 10 0
      gramjs/index.d.ts
  37. 6 6
      gramjs/index.js
  38. 43 60
      gramjs/network/Authenticator.js
  39. 12 22
      gramjs/network/MTProtoPlainSender.js
  40. 129 52
      gramjs/network/MTProtoSender.js
  41. 38 28
      gramjs/network/MTProtoState.js
  42. 18 12
      gramjs/network/connection/Connection.js
  43. 9 7
      gramjs/network/connection/TCPAbridged.js
  44. 45 44
      gramjs/network/connection/TCPFull.js
  45. 7 6
      gramjs/network/connection/TCPObfuscated.js
  46. 14 0
      gramjs/network/index.js
  47. 25 3
      gramjs/sessions/Abstract.js
  48. 126 0
      gramjs/sessions/CacheApiSession.js
  49. 20 19
      gramjs/sessions/Memory.js
  50. 39 20
      gramjs/sessions/StringSession.js
  51. 3 2
      gramjs/sessions/index.js
  52. 19 0
      gramjs/tl/AllTLObjects.js
  53. 10113 0
      gramjs/tl/api.d.ts
  54. 391 0
      gramjs/tl/api.js
  55. 24 12
      gramjs/tl/core/GZIPPacked.js
  56. 4 4
      gramjs/tl/core/MessageContainer.js
  57. 4 4
      gramjs/tl/core/RPCResult.js
  58. 3 3
      gramjs/tl/core/TLMessage.js
  59. 326 0
      gramjs/tl/generationHelpers.js
  60. 8 9
      gramjs/tl/index.js
  61. 1500 0
      gramjs/tl/static/api.tl
  62. 113 0
      gramjs/tl/static/schema.tl
  63. 57 0
      gramjs/tl/types-generator/generate.js
  64. 219 0
      gramjs/tl/types-generator/template.js
  65. 489 338
      package-lock.json
  66. 41 32
      package.json
  67. 1500 0
      static/api.tl
  68. 113 0
      static/schema.tl
  69. 28 0
      tsconfig.json
  70. 51 0
      webpack.config.js

+ 16 - 3
.bablerc

@@ -1,5 +1,18 @@
- {
- "presets": [
+{
+  "presets": [
+    [
+      "@babel/typescript"
+    ],
+    [
       "@babel/preset-env"
+    ],
+    [
+      "@babel/preset-react"
     ]
- }
+  ],
+  "plugins": [
+    [
+      "@babel/plugin-proposal-class-properties"
+    ]
+  ]
+}

+ 1 - 3
.gitignore

@@ -6,8 +6,6 @@
 /gramjs/tl/functions/
 /gramjs/tl/types/
 /gramjs/tl/patched/
-/gramjs/tl/AllTLObjects.js
-/gramjs/errors/RPCErrorList.js
 /dist/
 /coverage/
 
@@ -23,4 +21,4 @@ example.js
 
 settings
 
-/browser/
+/browser/

+ 5 - 3
README.md

@@ -12,9 +12,11 @@ can be changed later as long as I'm aware.
 4. Click on `Create application` at the end. Now that you have the `API ID` and `Hash`
 
 ## Running GramJS
-First of all, you need to run the `index.js` by issuing `node index.js gen`. This will generate all the
-TLObjects from the given `scheme.tl` file.
-Then check the `examples` folder to check how to use the library
+If you want to run in it in a browser just use webpack (a configuration file is already present). 
+The output will be in `browser` folder.
+In the browser gramjs will use localstorage to not regenerate api methods each run.
+check the `examples` folder for more info.
+Docs coming soon
 
 ## Using raw api
 Currently you can use any raw api function using `await client.invoke(new RequestClass(args))` .

+ 1 - 0
empty.txt

@@ -0,0 +1 @@
+.

+ 2 - 2
examples/simpleLogin.html

@@ -37,11 +37,11 @@
 
 </form>
 </body>
-<script src="https://painor.dev/gramjs.js?version=0.0.2"></script>
+<script src="../browser/gramjs.js"></script>
 <!--Loading the library-->
 <script src="betterLogging.js"></script>
 <!--beautifies the ouput (this rewrites console.log)-->
 
 <script src="simpleLogin.js"></script>
 
-</html>
+</html>

+ 1 - 3
examples/simpleLogin.js

@@ -64,9 +64,7 @@ const client = new TelegramClient(new StringSession(''), apiId, apiHash) // you
 // client.session.setDC(2, '149.154.167.40', 80)
 
 client.start({
-    phone: phoneCallback,
-    password: passwordCallback,
-    code: codeCallback,
+    botAuthToken: phoneCallback,
 }).then(() => {
     console.log('%c you should now be connected', 'color:#B54128')
     console.log('%c your string session is ' + client.session.save(), 'color:#B54128')

+ 12 - 0
gramjs/.editorconfig

@@ -0,0 +1,12 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+charset = utf-8
+indent_style = space
+indent_size = 4

+ 152 - 124
gramjs/Helpers.js

@@ -1,29 +1,15 @@
-const crypto = require('crypto')
-const fs = require('fs')
+const { isBrowser, isNode } = require("browser-or-node" );
 
-/**
- * use this instead of ** because of webpack
- * @param a {bigint}
- * @param b {bigint}
- * @returns {bigint}
- */
-function bigIntPower(a, b) {
-    let i
-    let pow = BigInt(1)
-
-    for (i = BigInt(0); i < b; i++) {
-        pow = pow * a
-    }
-
-    return pow
-}
+const BigInt = require('big-integer')
+const IS_NODE = isNode
+const crypto = require(IS_NODE ? 'crypto' : './crypto/crypto')
 
 /**
  * converts a buffer to big int
  * @param buffer
  * @param little
  * @param signed
- * @returns {bigint}
+ * @returns {bigInt.BigInteger}
  */
 function readBigIntFromBuffer(buffer, little = true, signed = false) {
     let randBuffer = Buffer.from(buffer)
@@ -31,38 +17,57 @@ function readBigIntFromBuffer(buffer, little = true, signed = false) {
     if (little) {
         randBuffer = randBuffer.reverse()
     }
-    let bigInt = BigInt('0x' + randBuffer.toString('hex'))
+    let bigInt = BigInt(randBuffer.toString('hex'), 16)
     if (signed && Math.floor(bigInt.toString('2').length / 8) >= bytesNumber) {
-        bigInt -= bigIntPower(BigInt(2), BigInt(bytesNumber * 8))
+        bigInt = bigInt.subtract(BigInt(2)
+            .pow(BigInt(bytesNumber * 8)))
     }
     return bigInt
 }
 
+/**
+ * Special case signed little ints
+ * @param big
+ * @param number
+ * @returns {Buffer}
+ */
+function toSignedLittleBuffer(big, number = 8) {
+    const bigNumber = BigInt(big)
+    const byteArray = []
+    for (let i = 0; i < number; i++) {
+        byteArray[i] = bigNumber.shiftRight(8 * i).and(255)
+    }
+    return Buffer.from(byteArray)
+}
+
+
 /**
  * converts a big int to a buffer
- * @param bigInt
+ * @param bigInt {BigInteger}
  * @param bytesNumber
  * @param little
  * @param signed
  * @returns {Buffer}
  */
 function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) {
-    const bitLength = bigInt.toString('2').length
+    bigInt = BigInt(bigInt)
+    const bitLength = bigInt.bitLength()
 
     const bytes = Math.ceil(bitLength / 8)
     if (bytesNumber < bytes) {
         throw new Error('OverflowError: int too big to convert')
     }
-    if (!signed && bigInt < 0) {
+    if (!signed && bigInt.lesser(BigInt(0))) {
         throw new Error('Cannot convert to unsigned')
     }
     let below = false
-    if (bigInt < 0) {
+    if (bigInt.lesser(BigInt(0))) {
         below = true
-        bigInt = -bigInt
+        bigInt = bigInt.abs()
     }
 
-    const hex = bigInt.toString('16').padStart(bytesNumber * 2, '0')
+    const hex = bigInt.toString('16')
+        .padStart(bytesNumber * 2, '0')
     let l = Buffer.from(hex, 'hex')
     if (little) {
         l = l.reverse()
@@ -70,8 +75,19 @@ function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false
 
     if (signed && below) {
         if (little) {
-            l[0] = 256 - l[0]
-            for (let i = 1; i < l.length; i++) {
+            let reminder = false
+            if (l[0] !== 0) {
+                l[0] -= 1
+            }
+            for (let i = 0; i < l.length; i++) {
+                if (l[i] === 0) {
+                    reminder = true
+                    continue
+                }
+                if (reminder) {
+                    l[i] -= 1
+                    reminder = false
+                }
                 l[i] = 255 - l[i]
             }
         } else {
@@ -86,7 +102,7 @@ function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false
 
 /**
  * Generates a random long integer (8 bytes), which is optionally signed
- * @returns {BigInt}
+ * @returns {BigInteger}
  */
 function generateRandomLong(signed = true) {
     return readBigIntFromBuffer(generateRandomBytes(8), true, signed)
@@ -102,16 +118,25 @@ function mod(n, m) {
     return ((n % m) + m) % m
 }
 
+/**
+ * returns a positive bigInt
+ * @param n {BigInt}
+ * @param m {BigInt}
+ * @returns {BigInt}
+ */
+function bigIntMod(n, m) {
+    return ((n.remainder(m)).add(m)).remainder(m)
+}
+
 /**
  * Generates a random bytes array
  * @param count
  * @returns {Buffer}
  */
 function generateRandomBytes(count) {
-    return crypto.randomBytes(count)
+    return Buffer.from(crypto.randomBytes(count))
 }
 
-
 /**
  * Calculate the key based on Telegram guidelines, specifying whether it's the client or not
  * @param sharedKey
@@ -119,28 +144,25 @@ function generateRandomBytes(count) {
  * @param client
  * @returns {{iv: Buffer, key: Buffer}}
  */
-
-function calcKey(sharedKey, msgKey, client) {
+/*CONTEST
+this is mtproto 1 (mostly used for secret chats)
+async function calcKey(sharedKey, msgKey, client) {
     const x = client === true ? 0 : 8
-    const sha1a = sha1(Buffer.concat([msgKey, sharedKey.slice(x, x + 32)]))
-    const sha1b = sha1(
-        Buffer.concat([sharedKey.slice(x + 32, x + 48), msgKey, sharedKey.slice(x + 48, x + 64)]),
-    )
-    const sha1c = sha1(Buffer.concat([sharedKey.slice(x + 64, x + 96), msgKey]))
-    const sha1d = sha1(Buffer.concat([msgKey, sharedKey.slice(x + 96, x + 128)]))
+    const [sha1a, sha1b, sha1c, sha1d] = await Promise.all([
+        sha1(Buffer.concat([msgKey, sharedKey.slice(x, x + 32)])),
+        sha1(Buffer.concat([sharedKey.slice(x + 32, x + 48), msgKey, sharedKey.slice(x + 48, x + 64)])),
+        sha1(Buffer.concat([sharedKey.slice(x + 64, x + 96), msgKey])),
+        sha1(Buffer.concat([msgKey, sharedKey.slice(x + 96, x + 128)]))
+    ])
     const key = Buffer.concat([sha1a.slice(0, 8), sha1b.slice(8, 20), sha1c.slice(4, 16)])
     const iv = Buffer.concat([sha1a.slice(8, 20), sha1b.slice(0, 8), sha1c.slice(16, 20), sha1d.slice(0, 8)])
-    return { key, iv }
+    return {
+        key,
+        iv
+    }
 }
 
-/**
- * Calculates the message key from the given data
- * @param data
- * @returns {Buffer}
  */
-function calcMsgKey(data) {
-    return sha1(data).slice(4, 20)
-}
 
 /**
  * Generates the key data corresponding to the given nonces
@@ -148,29 +170,35 @@ function calcMsgKey(data) {
  * @param newNonce
  * @returns {{key: Buffer, iv: Buffer}}
  */
-function generateKeyDataFromNonce(serverNonce, newNonce) {
-    serverNonce = readBufferFromBigInt(serverNonce, 16, true, true)
-    newNonce = readBufferFromBigInt(newNonce, 32, true, true)
-    const hash1 = sha1(Buffer.concat([newNonce, serverNonce]))
-    const hash2 = sha1(Buffer.concat([serverNonce, newNonce]))
-    const hash3 = sha1(Buffer.concat([newNonce, newNonce]))
+async function generateKeyDataFromNonce(serverNonce, newNonce) {
+    serverNonce = toSignedLittleBuffer(serverNonce, 16)
+    newNonce = toSignedLittleBuffer(newNonce, 32)
+    const [hash1, hash2, hash3] = await Promise.all([
+        sha1(Buffer.concat([newNonce, serverNonce])),
+        sha1(Buffer.concat([serverNonce, newNonce])),
+        sha1(Buffer.concat([newNonce, newNonce]))
+    ])
     const keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)])
     const ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)])
-    return { key: keyBuffer, iv: ivBuffer }
+    return {
+        key: keyBuffer,
+        iv: ivBuffer
+    }
 }
 
-/**
- * ensures that the parent directory exists
- * @param filePath
- */
-function ensureParentDirExists(filePath) {
-    fs.mkdirSync(filePath, { recursive: true })
+function convertToLittle(buf) {
+    const correct = Buffer.alloc(buf.length * 4);
+
+    for (let i = 0; i < buf.length; i++) {
+        correct.writeUInt32BE(buf[i], i * 4)
+    }
+    return correct;
 }
 
 /**
  * Calculates the SHA1 digest for the given data
  * @param data
- * @returns {Buffer}
+ * @returns {Promise}
  */
 function sha1(data) {
     const shaSum = crypto.createHash('sha1')
@@ -178,10 +206,11 @@ function sha1(data) {
     return shaSum.digest()
 }
 
+
 /**
  * Calculates the SHA256 digest for the given data
  * @param data
- * @returns {Buffer}
+ * @returns {Promise}
  */
 function sha256(data) {
     const shaSum = crypto.createHash('sha256')
@@ -194,25 +223,38 @@ function sha256(data) {
  * @param a
  * @param b
  * @param n
- * @returns {bigint}
+ * @returns {bigInt.BigInteger}
  */
 function modExp(a, b, n) {
-    a = a % n
-    let result = BigInt(1)
+    a = a.remainder(n)
+    let result = BigInt.one
     let x = a
-    while (b > BigInt(0)) {
-        const leastSignificantBit = b % BigInt(2)
-        b = b / BigInt(2)
-        if (leastSignificantBit === BigInt(1)) {
-            result = result * x
-            result = result % n
+    while (b.greater(BigInt.zero)) {
+        const leastSignificantBit = b.remainder(BigInt(2))
+        b = b.divide(BigInt(2))
+        if (leastSignificantBit.eq(BigInt.one)) {
+            result = result.multiply(x)
+            result = result.remainder(n)
         }
-        x = x * x
-        x = x % n
+        x = x.multiply(x)
+        x = x.remainder(n)
     }
     return result
 }
 
+
+/**
+ * Gets the arbitrary-length byte array corresponding to the given integer
+ * @param integer {number,BigInteger}
+ * @param signed {boolean}
+ * @returns {Buffer}
+ */
+function getByteArray(integer, signed = false) {
+    const bits = integer.toString(2).length
+    const byteLength = Math.floor((bits + 8 - 1) / 8)
+    return readBufferFromBigInt(BigInt(integer), byteLength, false, signed)
+}
+
 /**
  * returns a random int from min (inclusive) and max (inclusive)
  * @param min
@@ -237,6 +279,9 @@ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
  * @param obj
  * @returns {boolean}
  */
+/*
+CONTEST
+we do'nt support array requests anyway
 function isArrayLike(obj) {
     if (!obj) return false
     const l = obj.length
@@ -250,56 +295,37 @@ function isArrayLike(obj) {
     }
     return true
 }
-
-/**
- * Strips whitespace from the given text modifying the provided entities.
- * This assumes that there are no overlapping entities, that their length
- * is greater or equal to one, and that their length is not out of bounds.
- */
-function stripText(text, entities) {
-    if (!entities || entities.length === 0) return text.trim()
-
-    entities = Array.isArray(entities) ? entities : [entities]
-    while (text && text.slice(-1).match(/\s/)) {
-        const e = entities.slice(-1)
-        if (e.offset + e.length === text.length) {
-            if (e.length === 1) {
-                delete entities[entities.length - 1]
-                if (!entities) return text.trim()
-            } else {
-                e.length -= 1
-            }
+*/
+// Taken from https://stackoverflow.com/questions/18638900/javascript-crc32/18639999#18639999
+function makeCRCTable() {
+    let c
+    const crcTable = []
+    for (let n = 0; n < 256; n++) {
+        c = n
+        for (let k = 0; k < 8; k++) {
+            c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1))
         }
-
-        text = text.slice(0, -1)
+        crcTable[n] = c
     }
+    return crcTable
+}
 
-    while (text && text[0].match(/\s/)) {
-        for (let i = entities.size; i > 0; i--) {
-            const e = entities[i]
-            if (e.offset !== 0) {
-                e.offset -= 1
-                continue
-            }
+let crcTable = null
 
-            if (e.length === 1) {
-                delete entities[0]
-                if (entities.size === 0) {
-                    return text.trim()
-                }
-            } else {
-                e.length -= 1
-            }
-        }
-
-        text = text(1, text.length)
+function crc32(buf) {
+    if (!crcTable) {
+        crcTable = makeCRCTable()
     }
+    if (!Buffer.isBuffer(buf)) {
+        buf = Buffer.from(buf)
+    }
+    let crc = -1
 
-    return text
-}
-
-function regExpEscape(str) {
-    return str.replace(/[-[\]{}()*+!<=:?./\\^$|#\s,]/g, '\\$&')
+    for (let index = 0; index < buf.length; index++) {
+        const byte = buf[index]
+        crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8)
+    }
+    return (crc ^ (-1)) >>> 0
 }
 
 module.exports = {
@@ -307,17 +333,19 @@ module.exports = {
     readBufferFromBigInt,
     generateRandomLong,
     mod,
+    crc32,
     generateRandomBytes,
-    calcKey,
-    calcMsgKey,
+    //calcKey,
     generateKeyDataFromNonce,
     sha1,
     sha256,
+    bigIntMod,
     modExp,
     getRandomInt,
     sleep,
-    isArrayLike,
-    ensureParentDirExists,
-    stripText,
-    regExpEscape,
+    getByteArray,
+    //isArrayLike,
+    toSignedLittleBuffer,
+    convertToLittle,
+    IS_NODE
 }

+ 76 - 62
gramjs/Password.js

@@ -1,56 +1,62 @@
+const BigInt = require('big-integer')
 const Factorizator = require('./crypto/Factorizator')
-const { types } = require('./tl')
-const { readBigIntFromBuffer, readBufferFromBigInt, sha256, modExp, generateRandomBytes } = require('./Helpers')
-const crypto = require('crypto')
+const { constructors } = require('./tl')
+const { readBigIntFromBuffer, readBufferFromBigInt, sha256, bigIntMod, modExp,
+    generateRandomBytes } = require('./Helpers')
+const crypto = require('./crypto/crypto')
 const SIZE_FOR_HASH = 256
 
 /**
  *
  *
- * @param prime{BigInt}
- * @param g{BigInt}
+ * @param prime{BigInteger}
+ * @param g{BigInteger}
  */
+/*
+We don't support changing passwords yet
 function checkPrimeAndGoodCheck(prime, g) {
+    console.error('Unsupported function `checkPrimeAndGoodCheck` call. Arguments:', prime, g)
+
     const goodPrimeBitsCount = 2048
-    if (prime < 0 || prime.toString('2').length !== goodPrimeBitsCount) {
-        throw new Error(`bad prime count ${prime.toString('2').length},expected ${goodPrimeBitsCount}`)
+    if (prime < 0 || prime.bitLength() !== goodPrimeBitsCount) {
+        throw new Error(`bad prime count ${prime.bitLength()},expected ${goodPrimeBitsCount}`)
     }
     // TODO this is kinda slow
     if (Factorizator.factorize(prime)[0] !== 1) {
         throw new Error('give "prime" is not prime')
     }
-    if (g === BigInt(2)) {
-        if (prime % BigInt(8) !== BigInt(7)) {
+    if (g.eq(BigInt(2))) {
+        if ((prime.remainder(BigInt(8))).neq(BigInt(7))) {
             throw new Error(`bad g ${g}, mod8 ${prime % 8}`)
         }
-    } else if (g === BigInt(3)) {
-        if (prime % BigInt(3) !== BigInt(2)) {
+    } else if (g.eq(BigInt(3))) {
+        if ((prime.remainder(BigInt(3))).neq(BigInt(2))) {
             throw new Error(`bad g ${g}, mod3 ${prime % 3}`)
         }
         // eslint-disable-next-line no-empty
-    } else if (g === BigInt(4)) {
+    } else if (g.eq(BigInt(4))) {
 
-    } else if (g === BigInt(5)) {
-        if (!([BigInt(1), BigInt(4)].includes(prime % BigInt(5)))) {
+    } else if (g.eq(BigInt(5))) {
+        if (!([ BigInt(1), BigInt(4) ].includes(prime.remainder(BigInt(5))))) {
             throw new Error(`bad g ${g}, mod8 ${prime % 5}`)
         }
-    } else if (g === BigInt(6)) {
-        if (!([BigInt(19), BigInt(23)].includes(prime % BigInt(24)))) {
+    } else if (g.eq(BigInt(6))) {
+        if (!([ BigInt(19), BigInt(23) ].includes(prime.remainder(BigInt(24))))) {
             throw new Error(`bad g ${g}, mod8 ${prime % 24}`)
         }
-    } else if (g === BigInt(7)) {
-        if (!([BigInt(3), BigInt(5), BigInt(6)].includes(prime % BigInt(7)))) {
+    } else if (g.eq(BigInt(7))) {
+        if (!([ BigInt(3), BigInt(5), BigInt(6) ].includes(prime.remainder(BigInt(7))))) {
             throw new Error(`bad g ${g}, mod8 ${prime % 7}`)
         }
     } else {
         throw new Error(`bad g ${g}`)
     }
-    const primeSub1Div2 = (prime - BigInt(1)) / BigInt(2)
+    const primeSub1Div2 = (prime.subtract(BigInt(1))).divide(BigInt(2))
     if (Factorizator.factorize(primeSub1Div2)[0] !== 1) {
         throw new Error('(prime - 1) // 2 is not prime')
     }
 }
-
+*/
 /**
  *
  * @param primeBytes{Buffer}
@@ -76,21 +82,22 @@ function checkPrimeAndGood(primeBytes, g) {
         0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B,
     ])
     if (goodPrime.equals(primeBytes)) {
-        if ([3, 4, 5, 7].includes(g)) {
+        if ([ 3, 4, 5, 7 ].includes(g)) {
             return // It's good
         }
     }
-    checkPrimeAndGoodCheck(readBigIntFromBuffer(primeBytes, false), g)
+    throw new Error("Changing passwords unsupported")
+    //checkPrimeAndGoodCheck(readBigIntFromBuffer(primeBytes, false), g)
 }
 
 /**
  *
- * @param number{BigInt}
- * @param p{BigInt}
+ * @param number{BigInteger}
+ * @param p{BigInteger}
  * @returns {boolean}
  */
 function isGoodLarge(number, p) {
-    return (number > BigInt(0) && (p - number) > BigInt(0))
+    return (number.greater(BigInt(0)) && (p.subtract(number).greater(BigInt(0))))
 }
 
 /**
@@ -99,7 +106,7 @@ function isGoodLarge(number, p) {
  * @returns {Buffer}
  */
 function numBytesForHash(number) {
-    return Buffer.concat([Buffer.alloc(SIZE_FOR_HASH - number.length), number])
+    return Buffer.concat([ Buffer.alloc(SIZE_FOR_HASH - number.length), number ])
 }
 
 /**
@@ -113,19 +120,19 @@ function bigNumForHash(g) {
 
 /**
  *
- * @param modexp
- * @param prime
+ * @param modexp {BigInteger}
+ * @param prime {BigInteger}
  * @returns {Boolean}
  */
 function isGoodModExpFirst(modexp, prime) {
-    const diff = prime - modexp
+    const diff = prime.subtract(modexp)
 
     const minDiffBitsCount = 2048 - 64
     const maxModExpSize = 256
 
-    return !(diff < 0 || diff.toString('2').length < minDiffBitsCount ||
-        modexp.toString('2').length < minDiffBitsCount ||
-        Math.floor((modexp.toString('2').length + 7) / 8) > maxModExpSize)
+    return !(diff.lesser(BigInt(0)) || diff.bitLength() < minDiffBitsCount ||
+        modexp.bitLength() < minDiffBitsCount ||
+        Math.floor((modexp.bitLength() + 7) / 8) > maxModExpSize)
 }
 
 function xor(a, b) {
@@ -145,29 +152,30 @@ function xor(a, b) {
  * @param iterations{number}
  * @returns {*}
  */
+
 function pbkdf2sha512(password, salt, iterations) {
-    return crypto.pbkdf2Sync(password, salt, iterations, 64, 'sha512')
+    return crypto.pbkdf2(password, salt, iterations, 64, 'sha512')
 }
 
 /**
  *
- * @param algo {types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow}
+ * @param algo {constructors.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow}
  * @param password
  * @returns {Buffer|*}
  */
-function computeHash(algo, password) {
-    const hash1 = sha256(Buffer.concat([algo.salt1, Buffer.from(password, 'utf-8'), algo.salt1]))
-    const hash2 = sha256(Buffer.concat([algo.salt2, hash1, algo.salt2]))
-    const hash3 = pbkdf2sha512(hash2, algo.salt1, 100000)
-    return sha256(Buffer.concat([algo.salt2, hash3, algo.salt2]))
+async function computeHash(algo, password) {
+    const hash1 = await sha256(Buffer.concat([ algo.salt1, Buffer.from(password, 'utf-8'), algo.salt1 ]))
+    const hash2 = await sha256(Buffer.concat([ algo.salt2, hash1, algo.salt2 ]))
+    const hash3 = await pbkdf2sha512(hash2, algo.salt1, 100000)
+    return sha256(Buffer.concat([ algo.salt2, hash3, algo.salt2 ]))
 }
 
 /**
  *
- * @param algo {types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow}
+ * @param algo {constructors.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow}
  * @param password
  */
-function computeDigest(algo, password) {
+async function computeDigest(algo, password) {
     try {
         checkPrimeAndGood(algo.p, algo.g)
     } catch (e) {
@@ -175,23 +183,23 @@ function computeDigest(algo, password) {
     }
 
     const value = modExp(BigInt(algo.g),
-        readBigIntFromBuffer(computeHash(algo, password), false),
+        readBigIntFromBuffer(await computeHash(algo, password), false),
         readBigIntFromBuffer(algo.p, false))
     return bigNumForHash(value)
 }
 
 /**
  *
- * @param request {types.account.Password}
+ * @param request {constructors.account.Password}
  * @param password {string}
  */
-function computeCheck(request, password) {
+async function computeCheck(request, password) {
     const algo = request.currentAlgo
-    if (!(algo instanceof types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)) {
-        throw new Error(`Unsupported password algorithm ${algo.constructor.name}`)
+    if (!(algo instanceof constructors.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)) {
+        throw new Error(`Unsupported password algorithm ${algo.className}`)
     }
 
-    const pwHash = computeHash(algo, password)
+    const pwHash = await computeHash(algo, password)
     const p = readBigIntFromBuffer(algo.p, false)
     const g = algo.g
     const B = readBigIntFromBuffer(request.srp_B, false)
@@ -208,9 +216,9 @@ function computeCheck(request, password) {
     const gForHash = bigNumForHash(g)
     const bForHash = numBytesForHash(request.srp_B)
     const gX = modExp(BigInt(g), x, p)
-    const k = readBigIntFromBuffer(sha256(Buffer.concat([pForHash, gForHash])), false)
-    const kgX = (k * gX) % p
-    const generateAndCheckRandom = () => {
+    const k = readBigIntFromBuffer(await sha256(Buffer.concat([ pForHash, gForHash ])), false)
+    const kgX = bigIntMod(k.multiply(gX),p)
+    const generateAndCheckRandom =async () => {
         const randomSize = 256
         // eslint-disable-next-line no-constant-condition
         while (true) {
@@ -219,34 +227,40 @@ function computeCheck(request, password) {
             const A = modExp(BigInt(g), a, p)
             if (isGoodModExpFirst(A, p)) {
                 const aForHash = bigNumForHash(A)
-                const u = readBigIntFromBuffer(sha256(Buffer.concat([aForHash, bForHash])), false)
-                if (u > BigInt(0)) {
-                    return [a, aForHash, u]
+                const u = readBigIntFromBuffer(await sha256(Buffer.concat([ aForHash, bForHash ])), false)
+                if (u.greater(BigInt(0))) {
+                    return [ a, aForHash, u ]
                 }
             }
         }
     }
-    const [a, aForHash, u] = generateAndCheckRandom()
-    const gB = (B - kgX) % p
+    const [ a, aForHash, u ] =await  generateAndCheckRandom()
+    const gB = bigIntMod(B.subtract(kgX),p)
     if (!isGoodModExpFirst(gB, p)) {
         throw new Error('bad gB')
     }
 
-    const ux = u * x
-    const aUx = a + ux
+    const ux = u.multiply(x)
+    const aUx = a.add(ux)
     const S = modExp(gB, aUx, p)
-    const K = sha256(bigNumForHash(S))
-    const M1 = sha256(Buffer.concat([
-        xor(sha256(pForHash), sha256(gForHash)),
+    const [K, pSha ,gSha, salt1Sha, salt2Sha] = await Promise.all([
+        sha256(bigNumForHash(S)),
+        sha256(pForHash),
+        sha256(gForHash),
         sha256(algo.salt1),
-        sha256(algo.salt2),
+        sha256(algo.salt2)
+    ])
+    const M1 = await sha256(Buffer.concat([
+        xor(pSha,gSha),
+        salt1Sha,
+        salt2Sha,
         aForHash,
         bForHash,
         K,
     ]))
 
 
-    return new types.InputCheckPasswordSRP({
+    return new constructors.InputCheckPasswordSRP({
         srpId: request.srpId,
         A: Buffer.from(aForHash),
         M1: M1,

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 171 - 637
gramjs/Utils.js


+ 22 - 0
gramjs/client/TelegramClient.d.ts

@@ -0,0 +1,22 @@
+import { Api } from '..';
+
+import { BotAuthParams, UserAuthParams } from './auth';
+import { uploadFile, UploadFileParams } from './uploadFile';
+import { downloadFile, DownloadFileParams } from './downloadFile';
+
+declare class TelegramClient {
+    constructor(...args: any)
+
+    async start(authParams: UserAuthParams | BotAuthParams);
+
+    async invoke<R extends Api.AnyRequest>(request: R): Promise<R['__response']>;
+
+    async uploadFile(uploadParams: UploadFileParams): ReturnType<typeof uploadFile>;
+
+    async downloadFile(uploadParams: DownloadFileParams): ReturnType<typeof downloadFile>;
+
+    // Untyped methods.
+    [prop: string]: any;
+}
+
+export default TelegramClient;

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 423 - 372
gramjs/client/TelegramClient.js


+ 332 - 0
gramjs/client/auth.ts

@@ -0,0 +1,332 @@
+import  { default as Api }  from '../tl/api';
+import TelegramClient from './TelegramClient';
+// @ts-ignore
+import * as utils from '../Utils';
+// @ts-ignore
+import { sleep } from '../Helpers';
+// @ts-ignore
+import { computeCheck as computePasswordSrpCheck } from '../Password';
+
+export interface UserAuthParams {
+    phoneNumber: string | (() => Promise<string>);
+    phoneCode: (isCodeViaApp?: boolean) => Promise<string>;
+    password: (hint?: string) => Promise<string>;
+    firstAndLastNames: () => Promise<[string, string?]>;
+    qrCode: (qrCode: { token: Buffer, expires: number }) => Promise<void>;
+    onError: (err: Error) => void;
+    forceSMS?: boolean;
+}
+interface ReturnString {
+    (): string
+}
+
+export interface BotAuthParams {
+    botAuthToken: string | ReturnString;
+}
+
+interface ApiCredentials {
+    apiId: number,
+    apiHash: string,
+}
+
+const QR_CODE_TIMEOUT = 30000;
+
+export async function authFlow(
+    client: TelegramClient,
+    apiCredentials: ApiCredentials,
+    authParams: UserAuthParams | BotAuthParams,
+) {
+    const me = 'phoneNumber' in authParams
+        ? await signInUser(client, apiCredentials, authParams)
+        : await signInBot(client, apiCredentials, authParams);
+
+    // TODO @logger
+    client._log.info('Signed in successfully as', utils.getDisplayName(me));
+}
+
+
+export async function checkAuthorization(client: TelegramClient) {
+    try {
+        await client.invoke(new Api.updates.GetState());
+        return true;
+    } catch (e) {
+        return false;
+    }
+}
+
+async function signInUser(
+    client: TelegramClient, apiCredentials: ApiCredentials, authParams: UserAuthParams,
+): Promise<Api.TypeUser> {
+    let phoneNumber;
+    let phoneCodeHash;
+    let isCodeViaApp = false;
+
+    while (1) {
+        try {
+            if (typeof authParams.phoneNumber === 'function') {
+                try {
+                    phoneNumber = await authParams.phoneNumber();
+                } catch (err) {
+                    if (err.message === 'RESTART_AUTH_WITH_QR') {
+                        return signInUserWithQrCode(client, apiCredentials, authParams);
+                    }
+
+                    throw err;
+                }
+            } else {
+                phoneNumber = authParams.phoneNumber;
+            }
+            const sendCodeResult = await sendCode(client, apiCredentials, phoneNumber, authParams.forceSMS);
+            phoneCodeHash = sendCodeResult.phoneCodeHash;
+            isCodeViaApp = sendCodeResult.isCodeViaApp;
+
+            if (typeof phoneCodeHash !== 'string') {
+                throw new Error('Failed to retrieve phone code hash');
+            }
+
+            break;
+        } catch (err) {
+            if (typeof authParams.phoneNumber !== 'function') {
+                throw err;
+            }
+
+            authParams.onError(err);
+        }
+    }
+
+    let phoneCode;
+    let isRegistrationRequired = false;
+    let termsOfService;
+
+    while (1) {
+        try {
+            try {
+                phoneCode = await authParams.phoneCode(isCodeViaApp);
+            } catch (err) {
+                // This is the support for changing phone number from the phone code screen.
+                if (err.message === 'RESTART_AUTH') {
+                    return signInUser(client, apiCredentials, authParams);
+                }
+            }
+
+            if (!phoneCode) {
+                throw new Error('Code is empty');
+            }
+
+            // May raise PhoneCodeEmptyError, PhoneCodeExpiredError,
+            // PhoneCodeHashEmptyError or PhoneCodeInvalidError.
+            const result = await client.invoke(new Api.auth.SignIn({
+                phoneNumber,
+                phoneCodeHash,
+                phoneCode,
+            }));
+
+            if (result instanceof Api.auth.AuthorizationSignUpRequired) {
+                isRegistrationRequired = true;
+                termsOfService = result.termsOfService;
+                break;
+            }
+
+            return result.user;
+        } catch (err) {
+            if (err.message === 'SESSION_PASSWORD_NEEDED') {
+                return signInWithPassword(client, apiCredentials, authParams);
+            } else {
+                authParams.onError(err);
+            }
+        }
+    }
+
+    if (isRegistrationRequired) {
+        while (1) {
+            try {
+                const [firstName, lastName] = await authParams.firstAndLastNames();
+                if (!firstName) {
+                    throw new Error('First name is required');
+                }
+
+                const { user } = await client.invoke(new Api.auth.SignUp({
+                    phoneNumber,
+                    phoneCodeHash,
+                    firstName,
+                    lastName,
+                })) as Api.auth.Authorization;
+
+                if (termsOfService) {
+                    // This is a violation of Telegram rules: the user should be presented with and accept TOS.
+                    await client.invoke(new Api.help.AcceptTermsOfService({ id: termsOfService.id }));
+                }
+
+                return user;
+            } catch (err) {
+                authParams.onError(err);
+            }
+        }
+    }
+
+    authParams.onError(new Error('Auth failed'));
+    return signInUser(client, apiCredentials, authParams);
+}
+
+async function signInUserWithQrCode(
+    client: TelegramClient, apiCredentials: ApiCredentials, authParams: UserAuthParams,
+): Promise<Api.TypeUser> {
+    const inputPromise = (async () => {
+        while (1) {
+            const result = await client.invoke(new Api.auth.ExportLoginToken({
+                apiId: Number(process.env.TELEGRAM_T_API_ID),
+                apiHash: process.env.TELEGRAM_T_API_HASH,
+                exceptIds: [],
+            }));
+
+            if (!(result instanceof Api.auth.LoginToken)) {
+                throw new Error('Unexpected');
+            }
+
+            const { token, expires } = result;
+
+            await Promise.race([
+                authParams.qrCode({ token, expires }),
+                sleep(QR_CODE_TIMEOUT),
+            ]);
+        }
+    })();
+
+    const updatePromise = new Promise((resolve) => {
+        client.addEventHandler((update: Api.TypeUpdate) => {
+            if (update instanceof Api.UpdateLoginToken) {
+                resolve();
+            }
+        }, { build: (update: object) => update });
+    });
+
+    try {
+        await Promise.race([updatePromise, inputPromise]);
+    } catch (err) {
+        if (err.message === 'RESTART_AUTH') {
+            return signInUser(client, apiCredentials, authParams);
+        }
+
+        throw err;
+    }
+
+    try {
+        const result2 = await client.invoke(new Api.auth.ExportLoginToken({
+            apiId: Number(process.env.TELEGRAM_T_API_ID),
+            apiHash: process.env.TELEGRAM_T_API_HASH,
+            exceptIds: [],
+        }));
+
+        if (result2 instanceof Api.auth.LoginTokenSuccess && result2.authorization instanceof Api.auth.Authorization) {
+            return result2.authorization.user;
+        } else if (result2 instanceof Api.auth.LoginTokenMigrateTo) {
+            await client._switchDC(result2.dcId);
+            const migratedResult = await client.invoke(new Api.auth.ImportLoginToken({
+                token: result2.token,
+            }));
+
+            if (migratedResult instanceof Api.auth.LoginTokenSuccess && migratedResult.authorization instanceof Api.auth.Authorization) {
+                return migratedResult.authorization.user;
+            }
+        }
+    } catch (err) {
+        if (err.message === 'SESSION_PASSWORD_NEEDED') {
+            return signInWithPassword(client, apiCredentials, authParams);
+        }
+    }
+
+    authParams.onError(new Error('QR auth failed'));
+    return signInUser(client, apiCredentials, authParams);
+}
+
+async function sendCode(
+    client: TelegramClient, apiCredentials: ApiCredentials, phoneNumber: string, forceSMS = false,
+): Promise<{
+    phoneCodeHash: string;
+    isCodeViaApp: boolean;
+}> {
+    try {
+        const { apiId, apiHash } = apiCredentials;
+        const sendResult = await client.invoke(new Api.auth.SendCode({
+            phoneNumber,
+            apiId,
+            apiHash,
+            settings: new Api.CodeSettings(),
+        }));
+
+        // If we already sent a SMS, do not resend the phoneCode (hash may be empty)
+        if (!forceSMS || (sendResult.type instanceof Api.auth.SentCodeTypeSms)) {
+            return {
+                phoneCodeHash: sendResult.phoneCodeHash,
+                isCodeViaApp: sendResult.type instanceof Api.auth.SentCodeTypeApp,
+            };
+        }
+
+        const resendResult = await client.invoke(new Api.auth.ResendCode({
+            phoneNumber,
+            phoneCodeHash: sendResult.phoneCodeHash,
+        }));
+
+        return {
+            phoneCodeHash: resendResult.phoneCodeHash,
+            isCodeViaApp: resendResult.type instanceof Api.auth.SentCodeTypeApp,
+        };
+    } catch (err) {
+        if (err.message === 'AUTH_RESTART') {
+            return sendCode(client, apiCredentials, phoneNumber, forceSMS);
+        } else {
+            throw err;
+        }
+    }
+}
+
+async function signInWithPassword(
+    client: TelegramClient, apiCredentials: ApiCredentials, authParams: UserAuthParams,
+): Promise<Api.TypeUser> {
+    while (1) {
+        try {
+            const passwordSrpResult = await client.invoke(new Api.account.GetPassword());
+            const password = await authParams.password(passwordSrpResult.hint);
+            if (!password) {
+                throw new Error('Password is empty');
+            }
+
+            const passwordSrpCheck = await computePasswordSrpCheck(passwordSrpResult, password);
+            const { user } = await client.invoke(new Api.auth.CheckPassword({
+                password: passwordSrpCheck,
+            })) as Api.auth.Authorization;
+
+            return user;
+        } catch (err) {
+            authParams.onError(err);
+        }
+    }
+
+    return undefined!; // Never reached (TypeScript fix)
+}
+
+async function signInBot(client: TelegramClient, apiCredentials: ApiCredentials, authParams: BotAuthParams) {
+    const { apiId, apiHash } = apiCredentials;
+    let { botAuthToken } = authParams;
+    if (!botAuthToken){
+        throw new Error('a valid BotToken is required');
+    }
+    if (typeof botAuthToken === "function") {
+        let token;
+        while (true){
+            token = await botAuthToken();
+            if (token){
+                botAuthToken = token;
+                break;
+            }
+        }
+    }
+
+    console.dir(botAuthToken)
+    const { user } = await client.invoke(new Api.auth.ImportBotAuthorization({
+        apiId,
+        apiHash,
+        botAuthToken,
+    })) as Api.auth.Authorization;
+    return user;
+}

+ 192 - 0
gramjs/client/downloadFile.ts

@@ -0,0 +1,192 @@
+import { default as Api } from '../tl/api';
+import TelegramClient from './TelegramClient';
+// @ts-ignore
+import { getAppropriatedPartSize } from '../Utils';
+// @ts-ignore
+import { sleep } from '../Helpers';
+
+export interface progressCallback {
+    (
+        progress: number, // Float between 0 and 1.
+        ...args: any[]
+    ): void;
+
+    isCanceled?: boolean;
+    acceptsBuffer?: boolean;
+}
+
+export interface DownloadFileParams {
+    dcId: number;
+    fileSize: number;
+    workers?: number;
+    partSizeKb?: number;
+    start?: number;
+    end?: number;
+    progressCallback?: progressCallback;
+}
+
+interface Deferred {
+    promise: Promise<any>;
+    resolve: (value?: any) => void;
+}
+
+// Chunk sizes for `upload.getFile` must be multiple of the smallest size
+const MIN_CHUNK_SIZE = 4096;
+const DEFAULT_CHUNK_SIZE = 64; // kb
+const ONE_MB = 1024 * 1024;
+const REQUEST_TIMEOUT = 15000;
+
+export async function downloadFile(
+    client: TelegramClient,
+    inputLocation: Api.InputFileLocation,
+    fileParams: DownloadFileParams,
+) {
+    let { partSizeKb, fileSize, workers = 1, end } = fileParams;
+    const { dcId, progressCallback, start = 0 } = fileParams;
+
+    end = end && end < fileSize ? end : fileSize - 1;
+
+    if (!partSizeKb) {
+        partSizeKb = fileSize ? getAppropriatedPartSize(fileSize) : DEFAULT_CHUNK_SIZE;
+    }
+
+    // @ts-ignore
+    const partSize = partSizeKb * 1024;
+    const partsCount = end ? Math.ceil((end - start) / partSize) : 1;
+
+    if (partSize % MIN_CHUNK_SIZE !== 0) {
+        throw new Error(`The part size must be evenly divisible by ${MIN_CHUNK_SIZE}`);
+    }
+
+    let sender: any;
+    if (dcId) {
+        try {
+            sender = await client._borrowExportedSender(dcId);
+        } catch (e) {
+            // This should never raise
+            client._log.error(e);
+            if (e.message === 'DC_ID_INVALID') {
+                // Can't export a sender for the ID we are currently in
+                sender = client._sender;
+            } else {
+                throw e;
+            }
+        }
+    } else {
+        sender = client._sender;
+    }
+
+    client._log.info(`Downloading file in chunks of ${partSize} bytes`);
+
+    const foreman = new Foreman(workers);
+    const promises: Promise<any>[] = [];
+    let offset = start;
+    // Used for files with unknown size and for manual cancellations
+    let hasEnded = false;
+
+    let progress = 0;
+    if (progressCallback) {
+        progressCallback(progress);
+    }
+
+    while (true) {
+        let limit = partSize;
+        let isPrecise = false;
+
+        if (Math.floor(offset / ONE_MB) !== Math.floor((offset + limit - 1) / ONE_MB)) {
+            limit = ONE_MB - offset % ONE_MB;
+            isPrecise = true;
+        }
+
+        await foreman.requestWorker();
+
+        if (hasEnded) {
+            await foreman.releaseWorker();
+            break;
+        }
+
+        promises.push((async () => {
+            try {
+                const result = await Promise.race([
+                    await sender.send(new Api.upload.GetFile({
+                        location: inputLocation,
+                        offset,
+                        limit,
+                        precise: isPrecise || undefined,
+                    })),
+                    sleep(REQUEST_TIMEOUT).then(() => Promise.reject(new Error('REQUEST_TIMEOUT'))),
+                ]);
+
+                if (progressCallback) {
+                    if (progressCallback.isCanceled) {
+                        throw new Error('USER_CANCELED');
+                    }
+
+                    progress += (1 / partsCount);
+                    progressCallback(progress);
+                }
+
+                if (!end && (result.bytes.length < limit)) {
+                    hasEnded = true;
+                }
+
+                return result.bytes;
+            } catch (err) {
+                hasEnded = true;
+                throw err;
+            } finally {
+                foreman.releaseWorker();
+            }
+        })());
+
+        offset += limit;
+
+        if (end && (offset > end)) {
+            break;
+        }
+    }
+
+    const results = await Promise.all(promises);
+    const buffers = results.filter(Boolean);
+    const totalLength = end ? (end + 1) - start : undefined;
+    return Buffer.concat(buffers, totalLength);
+}
+
+class Foreman {
+    private deferred: Deferred | undefined;
+    private activeWorkers = 0;
+
+    constructor(private maxWorkers: number) {
+    }
+
+    requestWorker() {
+        this.activeWorkers++;
+
+        if (this.activeWorkers > this.maxWorkers) {
+            this.deferred = createDeferred();
+            return this.deferred.promise;
+        }
+
+        return Promise.resolve();
+    }
+
+    releaseWorker() {
+        this.activeWorkers--;
+
+        if (this.deferred && (this.activeWorkers <= this.maxWorkers)) {
+            this.deferred.resolve();
+        }
+    }
+}
+
+function createDeferred(): Deferred {
+    let resolve: Deferred['resolve'];
+    const promise = new Promise((_resolve) => {
+        resolve = _resolve;
+    });
+
+    return {
+        promise,
+        resolve: resolve!,
+    };
+}

+ 129 - 0
gramjs/client/uploadFile.ts

@@ -0,0 +1,129 @@
+import { default as Api } from '../tl/api';
+
+import TelegramClient from './TelegramClient';
+// @ts-ignore
+import { generateRandomBytes, readBigIntFromBuffer, sleep } from '../Helpers';
+// @ts-ignore
+import { getAppropriatedPartSize } from '../Utils';
+
+interface OnProgress {
+    // Float between 0 and 1.
+    (progress: number): void;
+
+    isCanceled?: boolean;
+}
+
+export interface UploadFileParams {
+    file: File;
+    workers: number;
+    onProgress?: OnProgress;
+}
+
+const KB_TO_BYTES = 1024;
+const LARGE_FILE_THRESHOLD = 10 * 1024 * 1024;
+const UPLOAD_TIMEOUT = 15 * 1000;
+
+export async function uploadFile(
+    client: TelegramClient,
+    fileParams: UploadFileParams,
+): Promise<Api.InputFile | Api.InputFileBig> {
+    const { file, onProgress } = fileParams;
+    let { workers } = fileParams;
+
+    const { name, size } = file;
+    const fileId = readBigIntFromBuffer(generateRandomBytes(8), true, true);
+    const isLarge = size > LARGE_FILE_THRESHOLD;
+
+    const partSize = getAppropriatedPartSize(size) * KB_TO_BYTES;
+    const partCount = Math.floor((size + partSize - 1) / partSize);
+    const buffer = Buffer.from(await fileToBuffer(file));
+
+    // We always upload from the DC we are in.
+    const sender = await client._borrowExportedSender(client.session.dcId);
+
+    if (!workers || !size) {
+        workers = 1;
+    }
+    if (workers >= partCount) {
+        workers = partCount;
+    }
+
+    let progress = 0;
+    if (onProgress) {
+        onProgress(progress);
+    }
+
+    for (let i = 0; i < partCount; i += workers) {
+        let sendingParts = [];
+        let end = i + workers;
+        if (end > partCount) {
+            end = partCount;
+        }
+
+        for (let j = i; j < end; j++) {
+            const bytes = buffer.slice(j * partSize, (j + 1) * partSize);
+
+            sendingParts.push((async () => {
+                await sender.send(
+                    isLarge
+                        ? new Api.upload.SaveBigFilePart({
+                            fileId,
+                            filePart: j,
+                            fileTotalParts: partCount,
+                            bytes,
+                        })
+                        : new Api.upload.SaveFilePart({
+                            fileId,
+                            filePart: j,
+                            bytes,
+                        }),
+                );
+
+                if (onProgress) {
+                    if (onProgress.isCanceled) {
+                        throw new Error('USER_CANCELED');
+                    }
+
+                    progress += (1 / partCount);
+                    onProgress(progress);
+                }
+            })());
+
+        }
+        try {
+            await Promise.race([
+                await Promise.all(sendingParts),
+                sleep(UPLOAD_TIMEOUT * workers).then(() => Promise.reject(new Error('TIMEOUT'))),
+            ]);
+        } catch (err) {
+            if (err.message === 'TIMEOUT') {
+                console.warn('Upload timeout. Retrying...');
+                i -= workers;
+                continue;
+            }
+
+            throw err;
+        }
+    }
+
+    return isLarge
+        ? new Api.InputFileBig({
+            id: fileId,
+            parts: partCount,
+            name,
+        })
+        : new Api.InputFile({
+            id: fileId,
+            parts: partCount,
+            name,
+            md5Checksum: '', // This is not a "flag", so not sure if we can make it optional.
+        });
+}
+
+function generateRandomBigInt() {
+    return readBigIntFromBuffer(generateRandomBytes(8), false);
+}
+
+function fileToBuffer(file: File) {
+    return new Response(file).arrayBuffer();
+}

+ 33 - 15
gramjs/crypto/AuthKey.js

@@ -1,33 +1,48 @@
-const { sha1, readBufferFromBigInt, readBigIntFromBuffer } = require('../Helpers')
+const { sha1, toSignedLittleBuffer,readBufferFromBigInt, readBigIntFromBuffer } = require('../Helpers')
 const BinaryReader = require('../extensions/BinaryReader')
-const struct = require('python-struct')
+const { sleep } = require('../Helpers')
 
 class AuthKey {
-    constructor(data) {
-        this.key = data
-    }
 
-    set key(value) {
+    constructor(value, hash) {
+        if (!hash || !value) {
+            return
+        }
+        this._key = value
+        this._hash = hash
+        const reader = new BinaryReader(hash)
+        this.auxHash = reader.readLong(false)
+        reader.read(4)
+        this.keyId = reader.readLong(false)
+    }
 
+    async setKey(value) {
         if (!value) {
-            this._key = this.auxHash = this.keyId = null
+            this._key = this.auxHash = this.keyId = this._hash = null
             return
         }
         if (value instanceof AuthKey) {
             this._key = value._key
             this.auxHash = value.auxHash
             this.keyId = value.keyId
+            this._hash = value._hash
             return
         }
-
         this._key = value
-        const reader = new BinaryReader(sha1(this._key))
+        this._hash = await sha1(this._key)
+        const reader = new BinaryReader(this._hash)
         this.auxHash = reader.readLong(false)
         reader.read(4)
         this.keyId = reader.readLong(false)
     }
 
-    get key() {
+    async waitForKey() {
+        while (!this.keyId) {
+            await sleep(20)
+        }
+    }
+
+    getKey() {
         return this._key
     }
 
@@ -39,17 +54,20 @@ class AuthKey {
      * @param number
      * @returns {bigint}
      */
-    calcNewNonceHash(newNonce, number) {
-        newNonce = readBufferFromBigInt(newNonce, 32, true, true)
-        const data = Buffer.concat([newNonce, struct.pack('<BQ', number.toString(), this.auxHash.toString())])
+    async calcNewNonceHash(newNonce, number) {
+        newNonce = toSignedLittleBuffer(newNonce, 32)
+        const n = Buffer.alloc(1)
+        n.writeUInt8(number, 0)
+        const data = Buffer.concat([newNonce,
+            Buffer.concat([n, readBufferFromBigInt(this.auxHash, 8, true)])])
 
         // Calculates the message key from the given data
-        const shaData = sha1(data).slice(4, 20)
+        const shaData = (await sha1(data)).slice(4, 20)
         return readBigIntFromBuffer(shaData, true, true)
     }
 
     equals(other) {
-        return other instanceof this.constructor && other.key === this._key
+        return other instanceof this.constructor && this._key && other.getKey() && other.getKey().equals(this._key)
     }
 }
 

+ 17 - 0
gramjs/crypto/CTR.js

@@ -0,0 +1,17 @@
+const crypto = require('./crypto')
+
+class CTR {
+    constructor(key, iv) {
+        if (!Buffer.isBuffer(key) || !Buffer.isBuffer(iv) || iv.length !== 16) {
+            throw new Error('Key and iv need to be a buffer')
+        }
+
+        this.cipher = crypto.createCipheriv('AES-256-CTR', key, iv)
+    }
+
+    encrypt(data) {
+        return Buffer.from(this.cipher.update(data))
+    }
+}
+
+module.exports = CTR

+ 59 - 71
gramjs/crypto/Factorizator.js

@@ -1,90 +1,78 @@
-const { getRandomInt } = require('../Helpers')
+const BigInt = require('big-integer')
+const { modExp } = require('../Helpers')
 
 class Factorizator {
     /**
-     * Finds the small multiplier by using Lopatin's method
-     * @param what {BigInt}
-     * @return {BigInt}
+     * Calculates the greatest common divisor
+     * @param a {BigInteger}
+     * @param b {BigInteger}
+     * @returns {BigInteger}
      */
-    static findSmallMultiplierLopatin(what) {
-        let g = BigInt(0)
-        for (let i = BigInt(0); i < BigInt(3); i++) {
-            const q = BigInt(30) || BigInt((getRandomInt(0, 127) & 15) + 17)
-            let x = BigInt(40) || BigInt(getRandomInt(0, 1000000000) + 1)
+    static gcd(a, b) {
+        while (b.neq(BigInt.zero)) {
+            let temp = b
+            b = a.remainder(b)
+            a = temp
+        }
+        return a
+    }
 
-            let y = x
-            const lim = BigInt(1) << (i + BigInt(18))
-            for (let j = BigInt(1); j < lim; j++) {
-                let a = x
-                let b = x
+    /**
+     * Factorizes the given number and returns both the divisor and the number divided by the divisor
+     * @param pq {BigInteger}
+     * @returns {{p: *, q: *}}
+     */
+    static factorize(pq) {
+        if (pq.remainder(2).equals(BigInt.zero)) {
+            return { p: BigInt(2), q: pq.divide(BigInt(2)) }
+        }
+        let y = BigInt.randBetween(BigInt(1),pq.minus(1))
+        const c = BigInt.randBetween(BigInt(1),pq.minus(1))
+        const m = BigInt.randBetween(BigInt(1),pq.minus(1))
 
-                let c = q
-                while (b !== BigInt(0)) {
-                    if (BigInt(b & BigInt(1)) !== BigInt(0)) {
-                        c += a
-                        if (c >= what) {
-                            c -= what
-                        }
-                    }
-                    a += a
-                    if (a >= what) {
-                        a -= what
-                    }
-                    b >>= BigInt(1)
-                }
+        let g = BigInt.one
+        let r = BigInt.one
+        let q = BigInt.one
+        let x = BigInt.zero
+        let ys = BigInt.zero
+        let k
 
-                x = c
-                const z = BigInt(x < y ? y - x : x - y)
-                g = this.gcd(z, what)
+        while (g.eq(BigInt.one)) {
+            x = y
+            for (let i = 0; BigInt(i).lesser(r); i++) {
+                y = (modExp(y, BigInt(2), pq)).add(c).remainder(pq)
+            }
+            k = BigInt.zero
 
-                if (g !== BigInt(1)) {
-                    break
-                }
+            while (k.lesser(r) && g.eq(BigInt.one)) {
 
-                if ((j & (j - BigInt(1))) === BigInt(0)) {
-                    y = x
+                ys = y
+                let condition = BigInt.min(m, r.minus(k))
+                for (let i = 0; BigInt(i).lesser(condition); i++) {
+                    y = (modExp(y, BigInt(2), pq)).add(c).remainder(pq)
+                    q = q.multiply(x.minus(y).abs()).remainder(pq)
                 }
+                g = Factorizator.gcd(q, pq)
+                k = k.add(m)
             }
-            if (g > 1) {
-                break
-            }
+
+            r = r.multiply(2)
         }
-        const p = what / g
 
-        return p < g ? p : g
-    }
 
-    /**
-     * Calculates the greatest common divisor
-     * @param a {BigInt}
-     * @param b {BigInt}
-     * @returns {BigInt}
-     */
-    static gcd(a, b) {
-        while (a !== BigInt(0) && b !== BigInt(0)) {
-            while ((b & BigInt(1)) === BigInt(0)) {
-                b >>= BigInt(1)
-            }
-            while ((a & BigInt(1)) === BigInt(0)) {
-                a >>= BigInt(1)
-            }
-            if (a > b) {
-                a -= b
-            } else {
-                b -= a
+        if (g.eq(pq)) {
+            while (true) {
+                ys = (modExp(ys, BigInt(2), pq)).add(c).remainder(pq)
+                g = Factorizator.gcd(x.minus(ys).abs(), pq)
+
+                if (g.greater(1)) {
+                    break
+                }
             }
         }
-        return b === BigInt(0) ? a : b
-    }
-
-    /**
-     * Factorizes the given number and returns both the divisor and the number divided by the divisor
-     * @param pq {BigInt}
-     * @returns {{p: BigInt, q: BigInt}}
-     */
-    static factorize(pq) {
-        const divisor = this.findSmallMultiplierLopatin(pq)
-        return { p: divisor, q: pq / divisor }
+        const p = g
+        q = pq.divide(g)
+        return p < q ? { p: p, q: q } : { p: q, q: p }
     }
 }
 

+ 37 - 0
gramjs/crypto/IGE.js

@@ -0,0 +1,37 @@
+const Helpers = require("../Helpers");
+
+const {IGE:aes_ige} = require('@cryptography/aes');
+
+class IGENEW {
+    constructor(key, iv) {
+        this.ige = new aes_ige(key,iv);
+    }
+
+    /**
+     * Decrypts the given text in 16-bytes blocks by using the given key and 32-bytes initialization vector
+     * @param cipherText {Buffer}
+     * @returns {Buffer}
+     */
+    decryptIge(cipherText) {
+        return Helpers.convertToLittle(this.ige.decrypt(cipherText));
+    }
+
+    /**
+     * Encrypts the given text in 16-bytes blocks by using the given key and 32-bytes initialization vector
+     * @param plainText {Buffer}
+     * @returns {Buffer}
+     */
+    encryptIge(plainText) {
+        const padding = plainText.length % 16
+        if (padding) {
+            plainText = Buffer.concat([plainText, Helpers.generateRandomBytes(16 - padding)])
+        }
+
+        return Helpers.convertToLittle(this.ige.encrypt(plainText));
+
+
+    }
+
+}
+
+module.exports = IGENEW

+ 44 - 76
gramjs/crypto/RSA.js

@@ -1,89 +1,57 @@
-const NodeRSA = require('node-rsa')
-const { TLObject } = require('../tl/tlobject')
-const { readBigIntFromBuffer, sha1, modExp, readBufferFromBigInt, generateRandomBytes } = require('../Helpers')
-const _serverKeys = {}
-
-/**
- * Gets the arbitrary-length byte array corresponding to the given integer
- * @param integer {number,BigInt}
- * @param signed {boolean}
- * @returns {Buffer}
- */
-function getByteArray(integer, signed = false) {
-    const { length: bits } = integer.toString(2)
-    const byteLength = Math.floor((bits + 8 - 1) / 8)
-    return readBufferFromBigInt(BigInt(integer), byteLength, false, signed)
-}
+const BigInt = require('big-integer')
+const { readBigIntFromBuffer, readBufferFromBigInt, getByteArray, sha1, generateRandomBytes, modExp } = require('../Helpers')
+
+const PUBLIC_KEYS = [{
+    'fingerprint': [40, 85, 94, 156, 117, 240, 61, 22, 65, 244, 169, 2, 33, 107, 232, 108, 2, 43, 180, 195],
+    'n': BigInt('24403446649145068056824081744112065346446136066297307473868293895086332508101251964919587745984311372853053253457835208829824428441874946556659953519213382748319518214765985662663680818277989736779506318868003755216402538945900388706898101286548187286716959100102939636333452457308619454821845196109544157601096359148241435922125602449263164512290854366930013825808102403072317738266383237191313714482187326643144603633877219028262697593882410403273959074350849923041765639673335775605842311578109726403165298875058941765362622936097839775380070572921007586266115476975819175319995527916042178582540628652481530373407'),
+    'e': 65537
+}, {
+    'fingerprint': [140, 171, 9, 34, 146, 246, 166, 50, 10, 170, 229, 247, 155, 114, 28, 177, 29, 106, 153, 154],
+    'n': BigInt('25081407810410225030931722734886059247598515157516470397242545867550116598436968553551465554653745201634977779380884774534457386795922003815072071558370597290368737862981871277312823942822144802509055492512145589734772907225259038113414940384446493111736999668652848440655603157665903721517224934142301456312994547591626081517162758808439979745328030376796953660042629868902013177751703385501412640560275067171555763725421377065095231095517201241069856888933358280729674273422117201596511978645878544308102076746465468955910659145532699238576978901011112475698963666091510778777356966351191806495199073754705289253783'),
+    'e': 65537
+}, {
+    'fingerprint': [243, 218, 109, 239, 16, 202, 176, 78, 167, 8, 255, 209, 120, 234, 205, 112, 111, 42, 91, 176],
+    'n': BigInt('22347337644621997830323797217583448833849627595286505527328214795712874535417149457567295215523199212899872122674023936713124024124676488204889357563104452250187725437815819680799441376434162907889288526863223004380906766451781702435861040049293189979755757428366240570457372226323943522935844086838355728767565415115131238950994049041950699006558441163206523696546297006014416576123345545601004508537089192869558480948139679182328810531942418921113328804749485349441503927570568778905918696883174575510385552845625481490900659718413892216221539684717773483326240872061786759868040623935592404144262688161923519030977'),
+    'e': 65537
+}, {
+    'fingerprint': [128, 80, 214, 72, 77, 244, 98, 7, 201, 250, 37, 244, 227, 51, 96, 199, 182, 37, 224, 113],
+    'n': BigInt('24573455207957565047870011785254215390918912369814947541785386299516827003508659346069416840622922416779652050319196701077275060353178142796963682024347858398319926119639265555410256455471016400261630917813337515247954638555325280392998950756512879748873422896798579889820248358636937659872379948616822902110696986481638776226860777480684653756042166610633513404129518040549077551227082262066602286208338952016035637334787564972991208252928951876463555456715923743181359826124083963758009484867346318483872552977652588089928761806897223231500970500186019991032176060579816348322451864584743414550721639495547636008351'),
+    'e': 65537
+}]
 
-function _computeFingerprint(key) {
-    const buf = readBigIntFromBuffer(key.keyPair.n.toBuffer(), false)
-    const nArray = getByteArray(buf)
+const _serverKeys = {}
 
-    const n = TLObject.serializeBytes(nArray)
-    const e = TLObject.serializeBytes(getByteArray(key.keyPair.e))
-    // Telegram uses the last 8 bytes as the fingerprint
-    const sh = sha1(Buffer.concat([n, e]))
-    return readBigIntFromBuffer(sh.slice(-8), true, true)
-}
+PUBLIC_KEYS.forEach(({ fingerprint, ...keyInfo }) => {
+    _serverKeys[readBigIntFromBuffer(fingerprint.slice(-8), true, true)] = keyInfo
+})
 
-function addKey(pub) {
-    const key = new NodeRSA(pub)
-    _serverKeys[_computeFingerprint(key)] = key
-}
+/**
+ * Encrypts the given data known the fingerprint to be used
+ * in the way Telegram requires us to do so (sha1(data) + data + padding)
 
-function encrypt(fingerprint, data) {
+ * @param fingerprint the fingerprint of the RSA key.
+ * @param data the data to be encrypted.
+ * @returns {Buffer|*|undefined} the cipher text, or None if no key matching this fingerprint is found.
+ */
+async function encrypt(fingerprint, data) {
     const key = _serverKeys[fingerprint]
     if (!key) {
         return undefined
     }
-    const buf = readBigIntFromBuffer(key.keyPair.n.toBuffer(), false)
-    const rand = generateRandomBytes(235 - data.length)
-    const toEncrypt = Buffer.concat([sha1(data), data, rand])
-    const payload = readBigIntFromBuffer(toEncrypt, false)
-    const encrypted = modExp(payload, BigInt(key.keyPair.e), buf)
-    const block = readBufferFromBigInt(encrypted, 256, false)
-    return block
-}
 
-const publicKeys = [
-    `-----BEGIN RSA PUBLIC KEY-----
-MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
-lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
-an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
-Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
-8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
-Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
------END RSA PUBLIC KEY-----`,
-
-    `-----BEGIN RSA PUBLIC KEY-----
-MIIBCgKCAQEAxq7aeLAqJR20tkQQMfRn+ocfrtMlJsQ2Uksfs7Xcoo77jAid0bRt
-ksiVmT2HEIJUlRxfABoPBV8wY9zRTUMaMA654pUX41mhyVN+XoerGxFvrs9dF1Ru
-vCHbI02dM2ppPvyytvvMoefRoL5BTcpAihFgm5xCaakgsJ/tH5oVl74CdhQw8J5L
-xI/K++KJBUyZ26Uba1632cOiq05JBUW0Z2vWIOk4BLysk7+U9z+SxynKiZR3/xdi
-XvFKk01R3BHV+GUKM2RYazpS/P8v7eyKhAbKxOdRcFpHLlVwfjyM1VlDQrEZxsMp
-NTLYXb6Sce1Uov0YtNx5wEowlREH1WOTlwIDAQAB
------END RSA PUBLIC KEY-----`,
+    // len(sha1.digest) is always 20, so we're left with 255 - 20 - x padding
+    const rand = generateRandomBytes(235 - data.length)
 
-    `-----BEGIN RSA PUBLIC KEY-----
-MIIBCgKCAQEAsQZnSWVZNfClk29RcDTJQ76n8zZaiTGuUsi8sUhW8AS4PSbPKDm+
-DyJgdHDWdIF3HBzl7DHeFrILuqTs0vfS7Pa2NW8nUBwiaYQmPtwEa4n7bTmBVGsB
-1700/tz8wQWOLUlL2nMv+BPlDhxq4kmJCyJfgrIrHlX8sGPcPA4Y6Rwo0MSqYn3s
-g1Pu5gOKlaT9HKmE6wn5Sut6IiBjWozrRQ6n5h2RXNtO7O2qCDqjgB2vBxhV7B+z
-hRbLbCmW0tYMDsvPpX5M8fsO05svN+lKtCAuz1leFns8piZpptpSCFn7bWxiA9/f
-x5x17D7pfah3Sy2pA+NDXyzSlGcKdaUmwQIDAQAB
------END RSA PUBLIC KEY-----`,
+    const toEncrypt = Buffer.concat([await sha1(data), data, rand])
 
-    `-----BEGIN RSA PUBLIC KEY-----
-MIIBCgKCAQEAwqjFW0pi4reKGbkc9pK83Eunwj/k0G8ZTioMMPbZmW99GivMibwa
-xDM9RDWabEMyUtGoQC2ZcDeLWRK3W8jMP6dnEKAlvLkDLfC4fXYHzFO5KHEqF06i
-qAqBdmI1iBGdQv/OQCBcbXIWCGDY2AsiqLhlGQfPOI7/vvKc188rTriocgUtoTUc
-/n/sIUzkgwTqRyvWYynWARWzQg0I9olLBBC2q5RQJJlnYXZwyTL3y9tdb7zOHkks
-WV9IMQmZmyZh/N7sMbGWQpt4NMchGpPGeJ2e5gHBjDnlIf2p1yZOYeUYrdbwcS0t
-UiggS4UeE8TzIuXFQxw7fzEIlmhIaq3FnwIDAQAB
------END RSA PUBLIC KEY-----`,
-]
-for (const pub of publicKeys) {
-    addKey(pub)
+    // rsa module rsa.encrypt adds 11 bits for padding which we don't want
+    // rsa module uses rsa.transform.bytes2int(to_encrypt), easier way:
+    const payload = readBigIntFromBuffer(toEncrypt, false)
+    const encrypted = modExp(payload, BigInt(key.e), key.n)
+    // rsa module uses transform.int2bytes(encrypted, keylength), easier:
+    return readBufferFromBigInt(encrypted, 256, false)
 }
 
-module.exports = { encrypt, addKey }
+module.exports = {
+    encrypt,
+}

+ 55 - 0
gramjs/crypto/converters.ts

@@ -0,0 +1,55 @@
+/**
+ * Uint32Array -> ArrayBuffer (low-endian os)
+ */
+export function i2abLow(buf: Uint32Array): ArrayBuffer {
+    const uint8 = new Uint8Array(buf.length * 4);
+    let i = 0;
+
+    for (let j = 0; j < buf.length; j++) {
+        const int = buf[j];
+
+        uint8[i++] = int >>> 24;
+        uint8[i++] = (int >> 16) & 0xFF;
+        uint8[i++] = (int >> 8) & 0xFF;
+        uint8[i++] = int & 0xFF;
+    }
+
+    return uint8.buffer;
+}
+
+/**
+ * Uint32Array -> ArrayBuffer (big-endian os)
+ */
+export function i2abBig(buf: Uint32Array): ArrayBuffer {
+    return buf.buffer;
+}
+
+/**
+ * ArrayBuffer -> Uint32Array (low-endian os)
+ */
+export function ab2iLow(ab: ArrayBuffer | SharedArrayBuffer | Uint8Array): Uint32Array {
+    const uint8 = new Uint8Array(ab);
+    const buf = new Uint32Array(uint8.length / 4);
+
+    for (let i = 0; i < uint8.length; i += 4) {
+        buf[i / 4] = (
+            uint8[i] << 24
+            ^ uint8[i + 1] << 16
+            ^ uint8[i + 2] << 8
+            ^ uint8[i + 3]
+        );
+    }
+
+    return buf;
+}
+
+/**
+ * ArrayBuffer -> Uint32Array (big-endian os)
+ */
+export function ab2iBig(ab: ArrayBuffer | SharedArrayBuffer | Uint8Array): Uint32Array {
+    return new Uint32Array(ab);
+}
+
+export const isBigEndian = new Uint8Array(new Uint32Array([0x01020304]))[0] === 0x01;
+export const i2ab = isBigEndian ? i2abBig : i2abLow;
+export const ab2i = isBigEndian ? ab2iBig : ab2iLow;

+ 123 - 0
gramjs/crypto/crypto.js

@@ -0,0 +1,123 @@
+const AES = require('@cryptography/aes').default;
+const { i2ab, ab2i } = require('./converters');
+const { getWords } = require('./words');
+class Counter {
+    constructor(initialValue) {
+        this.setBytes(initialValue)
+    }
+
+    setBytes(bytes) {
+        bytes = Buffer.from(bytes)
+        this._counter = bytes
+    }
+
+    increment() {
+        for (let i = 15; i >= 0; i--) {
+            if (this._counter[i] === 255) {
+                this._counter[i] = 0
+            } else {
+                this._counter[i]++
+                break
+            }
+        }
+    }
+}
+
+class CTR {
+    constructor(key, counter) {
+
+        if (!(counter instanceof Counter)) {
+            counter = new Counter(counter)
+        }
+
+        this._counter = counter
+
+        this._remainingCounter = null
+        this._remainingCounterIndex = 16
+
+        this._aes = new AES(getWords(key))
+    }
+
+    update(plainText) {
+        return this.encrypt(plainText)
+    }
+
+    encrypt(plainText) {
+        const encrypted = Buffer.from(plainText)
+
+        for (let i = 0; i < encrypted.length; i++) {
+            if (this._remainingCounterIndex === 16) {
+                this._remainingCounter = Buffer.from(i2ab(this._aes.encrypt(ab2i(this._counter._counter))))
+                this._remainingCounterIndex = 0
+                this._counter.increment()
+            }
+            encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++]
+        }
+
+        return encrypted
+    }
+}
+
+// endregion
+function createDecipheriv(algorithm, key, iv) {
+    if (algorithm.includes('ECB')) {
+        throw new Error('Not supported');
+    } else {
+        return new CTR(key, iv)
+    }
+}
+
+function createCipheriv(algorithm, key, iv) {
+    if (algorithm.includes('ECB')) {
+        throw new Error('Not supported');
+    } else {
+        return new CTR(key, iv)
+    }
+}
+
+function randomBytes(count) {
+    const bytes = new Uint8Array(count)
+    crypto.getRandomValues(bytes)
+    return bytes
+}
+
+class Hash {
+    constructor(algorithm) {
+        this.algorithm = algorithm
+    }
+
+    update(data) {
+        //We shouldn't be needing new Uint8Array but it doesn't
+        //work without it
+        this.data = new Uint8Array(data)
+    }
+
+    async digest() {
+        if (this.algorithm === 'sha1') {
+            return Buffer.from(await self.crypto.subtle.digest('SHA-1', this.data))
+        } else if (this.algorithm === 'sha256') {
+            return Buffer.from(await self.crypto.subtle.digest('SHA-256', this.data))
+        }
+    }
+}
+
+async function pbkdf2(password, salt, iterations) {
+    const passwordKey = await crypto.subtle.importKey('raw', password,
+        {name: 'PBKDF2'}, false, ['deriveBits'])
+    return Buffer.from(await crypto.subtle.deriveBits({
+        name: 'PBKDF2',
+        hash: 'SHA-512', salt, iterations
+    }, passwordKey, 512));
+}
+
+function createHash(algorithm) {
+    return new Hash(algorithm)
+}
+
+module.exports = {
+    createCipheriv,
+    createDecipheriv,
+    randomBytes,
+    createHash,
+    pbkdf2
+}

+ 51 - 0
gramjs/crypto/words.ts

@@ -0,0 +1,51 @@
+/*
+ * Imported from https://github.com/spalt08/cryptography/blob/master/packages/aes/src/utils/words.ts
+ */
+
+export function s2i(str: string, pos: number) {
+    return (
+        str.charCodeAt(pos) << 24
+        ^ str.charCodeAt(pos + 1) << 16
+        ^ str.charCodeAt(pos + 2) << 8
+        ^ str.charCodeAt(pos + 3)
+    );
+}
+
+/**
+ * Helper function for transforming string key to Uint32Array
+ */
+export function getWords(key: string | Uint8Array | Uint32Array) {
+    if (key instanceof Uint32Array) {
+        return key;
+    }
+
+    if (typeof key === 'string') {
+        if (key.length % 4 !== 0) for (let i = key.length % 4; i <= 4; i++) key += '\0x00';
+
+        const buf = new Uint32Array(key.length / 4);
+        for (let i = 0; i < key.length; i += 4) buf[i / 4] = s2i(key, i);
+
+        return buf;
+    }
+
+    if (key instanceof Uint8Array) {
+        const buf = new Uint32Array(key.length / 4);
+
+        for (let i = 0; i < key.length; i += 4) {
+            buf[i / 4] = (
+                key[i] << 24
+                ^ key[i + 1] << 16
+                ^ key[i + 2] << 8
+                ^ key[i + 3]
+            );
+        }
+
+        return buf;
+    }
+
+    throw new Error('Unable to create 32-bit words');
+}
+
+export function xor(left: Uint32Array, right: Uint32Array, to = left) {
+    for (let i = 0; i < left.length; i++) to[i] = left[i] ^ right[i];
+}

+ 13 - 17
gramjs/errors/Common.js

@@ -2,7 +2,6 @@
  * Errors not related to the Telegram API itself
  */
 
-const struct = require('python-struct')
 
 /**
  * Occurs when a read operation was cancelled.
@@ -21,7 +20,10 @@ class TypeNotFoundError extends Error {
     constructor(invalidConstructorId, remaining) {
         super(`Could not find a matching Constructor ID for the TLObject that was supposed to be
         read with ID ${invalidConstructorId}. Most likely, a TLObject was trying to be read when
-         it should not be read. Remaining bytes: ${remaining}`)
+         it should not be read. Remaining bytes: ${remaining.length}`)
+        if (typeof alert !== 'undefined') {
+            alert(`Missing MTProto Entity: Please, make sure to add TL definition for ID ${invalidConstructorId}`)
+        }
         this.invalidConstructorId = invalidConstructorId
         this.remaining = remaining
     }
@@ -45,14 +47,14 @@ class InvalidChecksumError extends Error {
  */
 class InvalidBufferError extends Error {
     constructor(payload) {
+        let code = null
         if (payload.length === 4) {
-            const code = -(struct.unpack('<i', payload)[0])
+            code = -payload.readInt32LE(0)
             super(`Invalid response buffer (HTTP code ${code})`)
-            this.code = code
         } else {
             super(`Invalid response buffer (too short ${payload})`)
-            this.code = null
         }
+        this.code = code
         this.payload = payload
     }
 }
@@ -79,15 +81,6 @@ class CdnFileTamperedError extends SecurityError {
     }
 }
 
-/**
- * Occurs when another exclusive conversation is opened in the same chat.
- */
-class AlreadyInConversationError extends Error {
-    constructor() {
-        super('Cannot open exclusive conversation in a chat that already has one open conversation')
-    }
-}
-
 /**
  * Occurs when handling a badMessageNotification
  */
@@ -132,8 +125,12 @@ class BadMessageError extends Error {
         64: 'Invalid container.',
     }
 
-    constructor(code) {
-        super(BadMessageError.ErrorMessages[code] || `Unknown error code (this should not happen): ${code}.`)
+    constructor(request,code) {
+        let errorMessage = BadMessageError.ErrorMessages[code] ||
+            `Unknown error code (this should not happen): ${code}.`
+        errorMessage+= `  Caused by ${request.className}`
+        super(errorMessage)
+        this.message = errorMessage
         this.code = code
     }
 }
@@ -147,6 +144,5 @@ module.exports = {
     InvalidBufferError,
     SecurityError,
     CdnFileTamperedError,
-    AlreadyInConversationError,
     BadMessageError,
 }

+ 8 - 4
gramjs/errors/RPCBaseErrors.js

@@ -2,12 +2,12 @@
  * Base class for all Remote Procedure Call errors.
  */
 class RPCError extends Error {
-    constructor(request, message, code = null) {
+    constructor(message, request, code = null) {
         super(
             'RPCError {0}: {1}{2}'
                 .replace('{0}', code)
                 .replace('{1}', message)
-                .replace('{2}', RPCError._fmtRequest(request)),
+                .replace('{2}', RPCError._fmtRequest(request))
         )
         this.code = code
         this.message = message
@@ -15,7 +15,11 @@ class RPCError extends Error {
 
     static _fmtRequest(request) {
         // TODO fix this
-        return ` (caused by ${request.constructor.name})`
+        if (request) {
+            return ` (caused by ${request.className})`
+        } else {
+            return ''
+        }
     }
 }
 
@@ -24,7 +28,7 @@ class RPCError extends Error {
  */
 class InvalidDCError extends RPCError {
     constructor(request, message, code) {
-        super(request, message, code)
+        super(message, request, code)
         this.code = code || 303
         this.message = message || 'ERROR_SEE_OTHER'
     }

+ 87 - 0
gramjs/errors/RPCErrorList.js

@@ -0,0 +1,87 @@
+const { RPCError, InvalidDCError, FloodError } = require('./RPCBaseErrors')
+
+
+class UserMigrateError extends InvalidDCError {
+    constructor(args) {
+        const newDc = Number(args.capture || 0)
+        super(`The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request))
+        this.message = `The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)
+        this.newDc = newDc
+    }
+}
+
+
+class PhoneMigrateError extends InvalidDCError {
+    constructor(args) {
+        const newDc = Number(args.capture || 0)
+        super(`The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request))
+        this.message = `The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)
+        this.newDc = newDc
+    }
+}
+
+class SlowModeWaitError extends FloodError {
+    constructor(args) {
+        const seconds = Number(args.capture || 0)
+        super(`A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request))
+        this.message = `A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request)
+        this.seconds = seconds
+    }
+}
+
+class FloodWaitError extends FloodError {
+    constructor(args) {
+        const seconds = Number(args.capture || 0)
+        super(`A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request))
+        this.message = `A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request)
+        this.seconds = seconds
+    }
+}
+
+class FloodTestPhoneWaitError extends FloodError {
+    constructor(args) {
+        const seconds = Number(args.capture || 0)
+        super(`A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request))
+        this.message = `A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request)
+        this.seconds = seconds
+    }
+}
+
+class FileMigrateError extends InvalidDCError {
+    constructor(args) {
+        const newDc = Number(args.capture || 0)
+        super(`The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request))
+        this.message = `The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request)
+        this.newDc = newDc
+    }
+}
+
+class NetworkMigrateError extends InvalidDCError {
+    constructor(args) {
+        const newDc = Number(args.capture || 0)
+        super(`The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request))
+        this.message = `The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)
+        this.newDc = newDc
+    }
+}
+
+const rpcErrorRe = [
+    [/FILE_MIGRATE_(\d+)/, FileMigrateError],
+    [/FLOOD_TEST_PHONE_WAIT_(\d+)/, FloodTestPhoneWaitError],
+    [/FLOOD_WAIT_(\d+)/, FloodWaitError],
+    [/PHONE_MIGRATE_(\d+)/, PhoneMigrateError],
+    [/SLOWMODE_WAIT_(\d+)/, SlowModeWaitError],
+    [/USER_MIGRATE_(\d+)/, UserMigrateError],
+    [/NETWORK_MIGRATE_(\d+)/, NetworkMigrateError],
+
+]
+module.exports = {
+    rpcErrorRe,
+    FileMigrateError,
+    FloodTestPhoneWaitError,
+    FloodWaitError,
+    PhoneMigrateError,
+    SlowModeWaitError,
+    UserMigrateError,
+    NetworkMigrateError
+}

+ 4 - 7
gramjs/errors/index.js

@@ -4,15 +4,10 @@
  * @param request the request that caused this error
  * @constructor the RPCError as a Python exception that represents this error
  */
-const { rpcErrorsObject, rpcErrorRe } = require('./RPCErrorList')
+const { RPCError } = require("./RPCBaseErrors")
+const { rpcErrorRe } = require('./RPCErrorList')
 
 function RPCMessageToError(rpcError, request) {
-    // Try to get the error by direct look-up, otherwise regex
-    const cls = rpcErrorsObject[rpcError.errorMessage]
-    if (cls) {
-        // eslint-disable-next-line new-cap
-        return new cls({ request: request })
-    }
     for (const [msgRegex, Cls] of rpcErrorRe) {
         const m = rpcError.errorMessage.match(msgRegex)
         if (m) {
@@ -20,6 +15,8 @@ function RPCMessageToError(rpcError, request) {
             return new Cls({ request: request, capture: capture })
         }
     }
+
+    return new RPCError(rpcError.errorMessage, request)
 }
 
 module.exports = {

+ 31 - 73
gramjs/events/NewMessage.js

@@ -1,29 +1,17 @@
+/*CONTEST
 const { EventBuilder, EventCommon } = require('./common')
-const { types } = require('../tl')
+const { constructors } = require('../tl')
 
-/**
- * This event occurs whenever a new text message, or a message
- * with media is received.
- */
 class NewMessage extends EventBuilder {
-    constructor({
-        outgoing = true,
-        incoming = false,
-        fromUsers = [],
-        forwards = true,
-        pattern = undefined,
-    } = {}) {
-        super()
-
-        this.outgoing = outgoing
-        this.incoming = incoming
-        this.fromUsers = fromUsers
-        this.forwards = forwards
-        this.pattern = pattern
-
-        if (!this.outgoing && !this.incoming) {
-            throw new Error('one of incoming or outgoing must be true')
-        }
+    constructor(args = {
+        chats: null,
+        func: null,
+    }) {
+        super(args)
+
+        this.chats = args.chats
+        this.func = args.func
+        this._noCheck = true
     }
 
     async _resolve(client) {
@@ -33,44 +21,44 @@ class NewMessage extends EventBuilder {
 
     build(update, others = null, thisId = null) {
         let event
-
-        if (!this.filter(update)) return
-
-        if (update instanceof types.UpdateNewMessage || update instanceof types.UpdateNewChannelMessage) {
+        if (update instanceof constructors.UpdateNewMessage || update instanceof constructors.UpdateNewChannelMessage) {
+            if (!(update.message instanceof constructors.Message)) {
+                return
+            }
             event = new Event(update.message)
-        } else if (update instanceof types.UpdateShortMessage) {
-            event = new Event(new types.Message({
-                out: update.out || false,
+        } else if (update instanceof constructors.UpdateShortMessage) {
+            event = new Event(new constructors.Message({
+                out: update.out,
                 mentioned: update.mentioned,
                 mediaUnread: update.mediaUnread,
                 silent: update.silent,
                 id: update.id,
                 // Note that to_id/from_id complement each other in private
                 // messages, depending on whether the message was outgoing.
-                toId: new types.PeerUser(update.out ? update.userId : thisId),
+                toId: new constructors.PeerUser(update.out ? update.userId : thisId),
                 fromId: update.out ? thisId : update.userId,
                 message: update.message,
                 date: update.date,
                 fwdFrom: update.fwdFrom,
                 viaBotId: update.viaBotId,
                 replyToMsgId: update.replyToMsgId,
-                entities: update.entities || [],
+                entities: update.entities,
             }))
-        } else if (update instanceof types.UpdateShortChatMessage) {
-            event = new this.Event(new types.Message({
-                out: update.out || false,
+        } else if (update instanceof constructors.UpdateShortChatMessage) {
+            event = new this.Event(new constructors.Message({
+                out: update.out,
                 mentioned: update.mentioned,
                 mediaUnread: update.mediaUnread,
                 silent: update.silent,
                 id: update.id,
-                toId: new types.PeerChat(update.chatId),
+                toId: new constructors.PeerChat(update.chatId),
                 fromId: update.fromId,
                 message: update.message,
                 date: update.date,
                 fwdFrom: update.fwdFrom,
                 viaBotId: update.viaBotId,
                 replyToMsgId: update.replyToMsgId,
-                entities: update.entities || [],
+                entities: update.entities,
             }))
         } else {
             return
@@ -79,7 +67,7 @@ class NewMessage extends EventBuilder {
         // Make messages sent to ourselves outgoing unless they're forwarded.
         // This makes it consistent with official client's appearance.
         const ori = event.message
-        if (ori.toId instanceof types.PeerUser) {
+        if (ori.toId instanceof constructors.PeerUser) {
             if (ori.fromId === ori.toId.userId && !ori.fwdFrom) {
                 event.message.out = true
             }
@@ -87,42 +75,11 @@ class NewMessage extends EventBuilder {
         return event
     }
 
-    filter(update) {
-        const message = update.message
-
-        // Make sure this is a message object in the first place
-        if (!(message instanceof types.Message)) {
-            return false
+    filter(event) {
+        if (this._noCheck) {
+            return event
         }
-
-        // Check if the message is incoming or outgoing, and if
-        // we want to accept whichever one it is
-        if (message.out) {
-            if (!this.outgoing) return false
-        } else {
-            if (!this.incoming) return false
-        }
-
-        // See if the message was sent by one of the `fromUsers`
-        if (this.fromUsers.length > 0) {
-            const valid = this.fromUsers.map((user) => {
-                const id = 'id' in user ? user.id : user
-                if (message.fromId === id) return true
-                else return false
-            })
-
-            if (!valid.includes(true)) return false
-        }
-
-        // Check if the message was forwarded
-        if (message.fwdFrom && !this.forwards) return false
-
-        // Finally check the message text against a pattern
-        if (this.pattern) {
-            if (!message.message.match(this.pattern)) return false
-        }
-
-        return true
+        return event
     }
 }
 
@@ -134,3 +91,4 @@ class Event extends EventCommon {
 }
 
 module.exports = NewMessage
+*/

+ 10 - 12
gramjs/events/Raw.js

@@ -1,22 +1,20 @@
 const { EventBuilder } = require('./common')
 
 class Raw extends EventBuilder {
-    constructor({
-        types = [],
-    } = {}) {
+    constructor(args = {
+        types: null,
+        func: null,
+    }) {
         super()
-        
-        this.types = Array.isArray(types) ? types : [types]
+        if (!args.types) {
+            this.types = true
+        } else {
+            this.types = args.types
+        }
     }
 
     build(update, others = null) {
-        if (this.types.length < 1) return update
-        
-        for (const _type of this.types) {
-            if (update instanceof _type) {
-                return update
-            }
-        }
+        return update
     }
 }
 

+ 5 - 5
gramjs/extensions/BinaryReader.js

@@ -1,4 +1,3 @@
-const { unpack } = require('python-struct')
 const { TypeNotFoundError } = require('../errors/Common')
 const { coreObjects } = require('../tl/core')
 const { tlobjects } = require('../tl/AllTLObjects')
@@ -44,7 +43,7 @@ class BinaryReader {
     /**
      * Reads a long integer (8 bytes or 64 bits) value.
      * @param signed
-     * @returns {bigint}
+     * @returns {BigInteger}
      */
     readLong(signed = true) {
         return this.readLargeInt(64, signed)
@@ -55,15 +54,16 @@ class BinaryReader {
      * @returns {number}
      */
     readFloat() {
-        return unpack('<f', this.read(4))[0]
+        return this.read(4).readFloatLE(0)
     }
 
     /**
      * Reads a real floating point (8 bytes) value.
-     * @returns {BigInt}
+     * @returns {BigInteger}
      */
     readDouble() {
-        return unpack('<f', this.read(8))[0]
+        // was this a bug ? it should have been <d
+        return this.read(8).readDoubleLE(0)
     }
 
     /**

+ 16 - 17
gramjs/extensions/Logger.js

@@ -1,10 +1,13 @@
-let logger = null
+let _level = null
 
 class Logger {
-    static levels = ['debug', 'info', 'warn', 'error']
+    static levels = ['error', 'warn', 'info', 'debug']
 
     constructor(level) {
-        this.level = level
+        if (!_level) {
+            _level = level || 'debug'
+        }
+
         this.isBrowser = typeof process === 'undefined' ||
             process.type === 'renderer' ||
             process.browser === true ||
@@ -37,34 +40,34 @@ class Logger {
      * @returns {boolean}
      */
     canSend(level) {
-        return (Logger.levels.indexOf(this.level) <= Logger.levels.indexOf(level))
+        return (Logger.levels.indexOf(_level) >= Logger.levels.indexOf(level))
     }
 
     /**
      * @param message {string}
      */
-    warn(...message) {
+    warn(message) {
         this._log('warn', message, this.colors.warn)
     }
 
     /**
      * @param message {string}
      */
-    info(...message) {
+    info(message) {
         this._log('info', message, this.colors.info)
     }
 
     /**
      * @param message {string}
      */
-    debug(...message) {
+    debug(message) {
         this._log('debug', message, this.colors.debug)
     }
 
     /**
      * @param message {string}
      */
-    error(...message) {
+    error(message) {
         this._log('error', message, this.colors.error)
     }
 
@@ -74,11 +77,8 @@ class Logger {
             .replace('%m', message)
     }
 
-    static getLogger() {
-        if (!logger) {
-            logger = new Logger('debug')
-        }
-        return logger
+    static setLevel(level) {
+        _level = level;
     }
 
     /**
@@ -87,10 +87,9 @@ class Logger {
      * @param color {string}
      */
     _log(level, message, color) {
-        if (Array.isArray(message)) {
-            message = message.join(' ')
+        if (!_level){
+            return
         }
-
         if (this.canSend(level)) {
             if (!this.isBrowser) {
                 console.log(color + this.format(message, level) + this.colors.end)
@@ -98,7 +97,7 @@ class Logger {
                 console.log(this.colors.start + this.format(message, level), color)
             }
         } else {
-            // console.log('can\'t send')
+
         }
     }
 }

+ 14 - 11
gramjs/extensions/MessagePacker.js

@@ -1,8 +1,6 @@
 const MessageContainer = require('../tl/core/MessageContainer')
 const TLMessage = require('../tl/core/TLMessage')
-const { TLRequest } = require('../tl/tlobject')
 const BinaryWriter = require('../extensions/BinaryWriter')
-const struct = require('python-struct')
 
 class MessagePacker {
     constructor(state, logger) {
@@ -31,14 +29,19 @@ class MessagePacker {
     }
 
     async get() {
+
         if (!this._queue.length) {
             this._ready = new Promise(((resolve) => {
                 this.setReady = resolve
             }))
             await this._ready
         }
+        if (!this._queue[this._queue.length - 1]) {
+            this._queue = []
+            return
+        }
         let data
-        const buffer = new BinaryWriter(Buffer.alloc(0))
+        let buffer = new BinaryWriter(Buffer.alloc(0))
 
         const batch = []
         let size = 0
@@ -52,11 +55,10 @@ class MessagePacker {
                     afterId = state.after.msgId
                 }
                 state.msgId = await this._state.writeDataAsMessage(
-                    buffer, state.data, state.request instanceof TLRequest,
+                    buffer, state.data, state.request.classType === 'request',
                     afterId,
                 )
-
-                this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.constructor.name}`)
+                this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.className || state.request.constructor.name}`)
                 batch.push(state)
                 continue
             }
@@ -64,7 +66,7 @@ class MessagePacker {
                 this._queue.unshift(state)
                 break
             }
-            this._log.warn(`Message payload for ${state.request.constructor.name} is too long ${state.data.length} and cannot be sent`)
+            this._log.warn(`Message payload for ${state.request.className || state.request.constructor.name} is too long ${state.data.length} and cannot be sent`)
             state.promise.reject('Request Payload is too big')
             size = 0
             continue
@@ -73,10 +75,11 @@ class MessagePacker {
             return null
         }
         if (batch.length > 1) {
-            data = Buffer.concat([struct.pack(
-                '<Ii', MessageContainer.CONSTRUCTOR_ID, batch.length,
-            ), buffer.getValue()])
-
+            const b = Buffer.alloc(8)
+            b.writeUInt32LE(MessageContainer.CONSTRUCTOR_ID, 0)
+            b.writeInt32LE(batch.length, 4)
+            data = Buffer.concat([b, buffer.getValue()])
+            buffer = new BinaryWriter(Buffer.alloc(0))
             const containerId = await this._state.writeDataAsMessage(
                 buffer, data, false,
             )

+ 31 - 8
gramjs/extensions/PromisedNetSockets.js

@@ -1,22 +1,35 @@
 const Socket = require('net').Socket
+const Mutex = require('async-mutex').Mutex
+const mutex = new Mutex()
 
 const closeError = new Error('NetSocket was closed')
 
 class PromisedNetSockets {
     constructor() {
         this.client = null
-
-
         this.closed = true
     }
 
+    async readExactly(number) {
+        let readData = Buffer.alloc(0)
+        while (true) {
+            const thisTime = await this.read(number)
+            readData = Buffer.concat([readData, thisTime])
+            number = number - thisTime.length
+            if (!number) {
+                return readData
+            }
+        }
+    }
+
     async read(number) {
         if (this.closed) {
-            console.log('couldn\'t read')
             throw closeError
         }
-        const canWe = await this.canRead
-
+        await this.canRead
+        if (this.closed) {
+            throw closeError
+        }
         const toReturn = this.stream.slice(0, number)
         this.stream = this.stream.slice(number)
         if (this.stream.length === 0) {
@@ -76,6 +89,8 @@ class PromisedNetSockets {
         this.client.write(data)
     }
 
+
+
     async close() {
         await this.client.destroy()
         this.client.unref()
@@ -84,11 +99,19 @@ class PromisedNetSockets {
 
     async receive() {
         this.client.on('data', async (message) => {
-            const data = Buffer.from(message)
-            this.stream = Buffer.concat([this.stream, data])
-            this.resolveRead(true)
+
+            const release = await mutex.acquire()
+            try {
+                let data
+                //CONTEST BROWSER
+                this.stream = Buffer.concat([this.stream, message])
+                this.resolveRead(true)
+            } finally {
+                release()
+            }
         })
     }
 }
 
 module.exports = PromisedNetSockets
+

+ 44 - 21
gramjs/extensions/PromisedWebSockets.js

@@ -1,23 +1,43 @@
+const Mutex = require('async-mutex').Mutex
+const mutex = new Mutex()
+
 const WebSocketClient = require('websocket').w3cwebsocket
 
 const closeError = new Error('WebSocket was closed')
 
 class PromisedWebSockets {
     constructor() {
+        /*CONTEST
         this.isBrowser = typeof process === 'undefined' ||
             process.type === 'renderer' ||
             process.browser === true ||
             process.__nwjs
+
+         */
+        this.client = null
         this.closed = true
     }
 
+    async readExactly(number) {
+        let readData = Buffer.alloc(0)
+        while (true) {
+            const thisTime = await this.read(number)
+            readData = Buffer.concat([readData, thisTime])
+            number = number - thisTime.length
+            if (!number) {
+                return readData
+            }
+        }
+    }
+
     async read(number) {
         if (this.closed) {
-            console.log('couldn\'t read')
             throw closeError
         }
-        const canWe = await this.canRead
-
+        await this.canRead
+        if (this.closed) {
+            throw closeError
+        }
         const toReturn = this.stream.slice(0, number)
         this.stream = this.stream.slice(number)
         if (this.stream.length === 0) {
@@ -43,22 +63,17 @@ class PromisedWebSockets {
 
     getWebSocketLink(ip, port) {
         if (port === 443) {
-            return 'ws://' + ip + '/apiws'
+            return `wss://${ip}:${port}/apiws`
         } else {
-            return 'ws://' + ip + '/apiws'
+            return `ws://${ip}:${port}/apiws`
         }
     }
 
     async connect(port, ip) {
-        console.log('trying to connect')
-
         this.stream = Buffer.alloc(0)
-        this.client = null
-
         this.canRead = new Promise((resolve) => {
             this.resolveRead = resolve
         })
-
         this.closed = false
         this.website = this.getWebSocketLink(ip, port)
         this.client = new WebSocketClient(this.website, 'binary')
@@ -67,12 +82,19 @@ class PromisedWebSockets {
                 this.receive()
                 resolve(this)
             }
-            this.client.onerror = reject
+            this.client.onerror = (error) => {
+                reject(error)
+            }
             this.client.onclose = () => {
-                if (this.client.closed) {
                     this.resolveRead(false)
                     this.closed = true
-                }
+            }
+            //CONTEST
+            if (typeof window !== 'undefined'){
+                window.addEventListener('offline', async () => {
+                    await this.close()
+                    this.resolveRead(false)
+                });
             }
         })
     }
@@ -85,21 +107,22 @@ class PromisedWebSockets {
     }
 
     async close() {
-        console.log('something happened. closing')
         await this.client.close()
         this.closed = true
     }
 
     async receive() {
         this.client.onmessage = async (message) => {
-            let data
-            if (this.isBrowser) {
-                data = Buffer.from(await new Response(message.data).arrayBuffer())
-            } else {
-                data = Buffer.from(message.data)
+            const release = await mutex.acquire()
+            try {
+                let data
+                //CONTEST BROWSER
+                    data = Buffer.from(await new Response(message.data).arrayBuffer())
+                this.stream = Buffer.concat([this.stream, data])
+                this.resolveRead(true)
+            } finally {
+                release()
             }
-            this.stream = Buffer.concat([this.stream, data])
-            this.resolveRead(true)
         }
     }
 }

+ 0 - 15
gramjs/extensions/index.js

@@ -4,11 +4,6 @@ const BinaryReader = require('./BinaryReader')
 const PromisedWebSockets = require('./PromisedWebSockets')
 const MessagePacker = require('./MessagePacker')
 const AsyncQueue = require('./AsyncQueue')
-const PromisedNetSocket = require('./PromisedNetSockets')
-const Scanner = require('./Scanner')
-const markdown = require('./Markdown')
-const html = require('./HTML')
-
 module.exports = {
     BinaryWriter,
     BinaryReader,
@@ -16,14 +11,4 @@ module.exports = {
     AsyncQueue,
     Logger,
     PromisedWebSockets,
-    PromisedNetSocket,
-    Scanner,
-    markdown: {
-        parse: markdown.parse,
-        unparse: markdown.unparse,
-    },
-    html: {
-        parse: html.parse,
-        unparse: html.unparse,
-    }
 }

+ 10 - 0
gramjs/index.d.ts

@@ -0,0 +1,10 @@
+export { default as Api } from './tl/api';
+export { default as TelegramClient } from './client/TelegramClient';
+export { default as connection } from './network';
+export { default as tl } from './tl';
+export { default as version } from './Version';
+export { default as events } from './events';
+export { default as utils } from './Utils';
+export { default as errors } from './errors';
+export { default as sessions } from './sessions';
+export { default as helpers } from './Helpers';

+ 6 - 6
gramjs/index.js

@@ -1,6 +1,4 @@
-require('regenerator-runtime/runtime')
-require('regenerator-runtime')
-
+const Api = require('./tl/api')
 const TelegramClient = require('./client/TelegramClient')
 const connection = require('./network')
 const tl = require('./tl')
@@ -8,9 +6,11 @@ const version = require('./Version')
 const events = require('./events')
 const utils = require('./Utils')
 const errors = require('./errors')
-const session = require('./sessions')
+const sessions = require('./sessions')
+const extensions = require('./extensions')
+const helpers = require('./Helpers')
 
 module.exports = {
-    TelegramClient, session, connection,
-    tl, version, events, utils, errors,
+    Api, TelegramClient, sessions, connection, extensions,
+    tl, version, events, utils, errors, helpers,
 }

+ 43 - 60
gramjs/network/Authenticator.js

@@ -1,22 +1,12 @@
-const AES = require('../crypto/AES')
+const BigInt = require('big-integer')
+const IGE = require('../crypto/IGE')
 const AuthKey = require('../crypto/AuthKey')
 const Factorizator = require('../crypto/Factorizator')
 const RSA = require('../crypto/RSA')
 const Helpers = require('../Helpers')
-const { ServerDHParamsFail } = require('../tl/types')
-const { ServerDHParamsOk } = require('../tl/types')
-const { ReqDHParamsRequest } = require('../tl/functions')
-const { SecurityError } = require('../errors/Common')
-const { PQInnerData } = require('../tl/types')
+const { constructors, requests } = require('../tl')
 const BinaryReader = require('../extensions/BinaryReader')
-const { ClientDHInnerData } = require('../tl/types')
-const { DhGenFail } = require('../tl/types')
-const { DhGenRetry } = require('../tl/types')
-const { DhGenOk } = require('../tl/types')
-const { SetClientDHParamsRequest } = require('../tl/functions')
-const { ServerDHInnerData } = require('../tl/types')
-const { ResPQ } = require('../tl/types')
-const { ReqPqMultiRequest } = require('../tl/functions')
+const { SecurityError } = require("../errors/Common")
 
 /**
  * Executes the authentication process with the Telegram servers.
@@ -29,12 +19,13 @@ async function doAuthentication(sender, log) {
     let bytes = Helpers.generateRandomBytes(16)
 
     const nonce = Helpers.readBigIntFromBuffer(bytes, false, true)
-    const resPQ = await sender.send(new ReqPqMultiRequest({ nonce: nonce }))
+    const resPQ = await sender.send(new requests.ReqPqMulti({ nonce: nonce }))
     log.debug('Starting authKey generation step 1')
-    if (!(resPQ instanceof ResPQ)) {
+
+    if (!(resPQ instanceof constructors.ResPQ)) {
         throw new Error(`Step 1 answer was ${resPQ}`)
     }
-    if (resPQ.nonce !== nonce) {
+    if (resPQ.nonce.neq(nonce)) {
         throw new SecurityError('Step 1 invalid nonce from server')
     }
     const pq = Helpers.readBigIntFromBuffer(resPQ.pq, false, true)
@@ -42,15 +33,15 @@ async function doAuthentication(sender, log) {
     log.debug('Starting authKey generation step 2')
     // Step 2 sending: DH Exchange
     let { p, q } = Factorizator.factorize(pq)
-    p = getByteArray(p)
 
-    q = getByteArray(q)
+    // TODO Bring back after `Factorizator` fix.
+     p = Helpers.getByteArray(p)
+     q = Helpers.getByteArray(q)
 
     bytes = Helpers.generateRandomBytes(32)
     const newNonce = Helpers.readBigIntFromBuffer(bytes, true, true)
-
-    const pqInnerData = new PQInnerData({
-        pq: getByteArray(pq), // unsigned
+    const pqInnerData = new constructors.PQInnerData({
+        pq: Helpers.getByteArray(pq), // unsigned
         p: p,
         q: q,
         nonce: resPQ.nonce,
@@ -62,7 +53,7 @@ async function doAuthentication(sender, log) {
     let cipherText = null
     let targetFingerprint = null
     for (const fingerprint of resPQ.serverPublicKeyFingerprints) {
-        cipherText = RSA.encrypt(fingerprint.toString(), pqInnerData.getBytes())
+        cipherText = await RSA.encrypt(fingerprint.toString(), pqInnerData.getBytes())
         if (cipherText !== null && cipherText !== undefined) {
             targetFingerprint = fingerprint
             break
@@ -73,7 +64,7 @@ async function doAuthentication(sender, log) {
     }
 
     const serverDhParams = await sender.send(
-        new ReqDHParamsRequest({
+        new requests.ReqDHParams({
             nonce: resPQ.nonce,
             serverNonce: resPQ.serverNonce,
             p: p,
@@ -82,98 +73,101 @@ async function doAuthentication(sender, log) {
             encryptedData: cipherText,
         }),
     )
-    if (!(serverDhParams instanceof ServerDHParamsOk || serverDhParams instanceof ServerDHParamsFail)) {
+    if (!(serverDhParams instanceof constructors.ServerDHParamsOk || serverDhParams instanceof constructors.ServerDHParamsFail)) {
         throw new Error(`Step 2.1 answer was ${serverDhParams}`)
     }
-    if (serverDhParams.nonce !== resPQ.nonce) {
+    if (serverDhParams.nonce.neq(resPQ.nonce)) {
         throw new SecurityError('Step 2 invalid nonce from server')
     }
 
-    if (serverDhParams.serverNonce !== resPQ.serverNonce) {
+    if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) {
         throw new SecurityError('Step 2 invalid server nonce from server')
     }
 
-    if (serverDhParams instanceof ServerDHParamsFail) {
-        const sh = Helpers.sha1(Helpers.readBufferFromBigInt(newNonce, 32, true, true).slice(4, 20))
+    if (serverDhParams instanceof constructors.ServerDHParamsFail) {
+        const sh = await Helpers.sha1(Helpers.toSignedLittleBuffer(newNonce, 32).slice(4, 20))
         const nnh = Helpers.readBigIntFromBuffer(sh, true, true)
-        if (serverDhParams.newNonceHash !== nnh) {
+        if (serverDhParams.newNonceHash.neq(nnh)) {
             throw new SecurityError('Step 2 invalid DH fail nonce from server')
         }
     }
-    if (!(serverDhParams instanceof ServerDHParamsOk)) {
+    if (!(serverDhParams instanceof constructors.ServerDHParamsOk)) {
         throw new Error(`Step 2.2 answer was ${serverDhParams}`)
     }
     log.debug('Finished authKey generation step 2')
     log.debug('Starting authKey generation step 3')
 
     // Step 3 sending: Complete DH Exchange
-    const { key, iv } = Helpers.generateKeyDataFromNonce(resPQ.serverNonce, newNonce)
+    const { key, iv } = await Helpers.generateKeyDataFromNonce(resPQ.serverNonce, newNonce)
     if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
         // See PR#453
         throw new SecurityError('Step 3 AES block size mismatch')
     }
-    const plainTextAnswer = AES.decryptIge(serverDhParams.encryptedAnswer, key, iv)
+    const ige = new IGE(key,iv)
+    const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer)
     const reader = new BinaryReader(plainTextAnswer)
     reader.read(20) // hash sum
     const serverDhInner = reader.tgReadObject()
-    if (!(serverDhInner instanceof ServerDHInnerData)) {
+    if (!(serverDhInner instanceof constructors.ServerDHInnerData)) {
         throw new Error(`Step 3 answer was ${serverDhInner}`)
     }
 
-    if (serverDhInner.nonce !== resPQ.nonce) {
+    if (serverDhInner.nonce.neq(resPQ.nonce)) {
         throw new SecurityError('Step 3 Invalid nonce in encrypted answer')
     }
-    if (serverDhInner.serverNonce !== resPQ.serverNonce) {
+    if (serverDhInner.serverNonce.neq(resPQ.serverNonce)) {
         throw new SecurityError('Step 3 Invalid server nonce in encrypted answer')
     }
     const dhPrime = Helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false)
     const ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false)
     const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000)
-
     const b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false)
     const gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime)
     const gab = Helpers.modExp(ga, b, dhPrime)
 
     // Prepare client DH Inner Data
-    const clientDhInner = new ClientDHInnerData({
+    const clientDhInner = new constructors.ClientDHInnerData({
         nonce: resPQ.nonce,
         serverNonce: resPQ.serverNonce,
         retryId: 0, // TODO Actual retry ID
-        gB: getByteArray(gb, false),
+        gB: Helpers.getByteArray(gb, false),
     }).getBytes()
 
-    const clientDdhInnerHashed = Buffer.concat([Helpers.sha1(clientDhInner), clientDhInner])
+    const clientDdhInnerHashed = Buffer.concat([await Helpers.sha1(clientDhInner), clientDhInner])
     // Encryption
-    const clientDhEncrypted = AES.encryptIge(clientDdhInnerHashed, key, iv)
+
+    const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed)
     const dhGen = await sender.send(
-        new SetClientDHParamsRequest({
+        new requests.SetClientDHParams({
             nonce: resPQ.nonce,
             serverNonce: resPQ.serverNonce,
             encryptedData: clientDhEncrypted,
         }),
     )
-    const nonceTypes = [DhGenOk, DhGenRetry, DhGenFail]
+    const nonceTypes = [constructors.DhGenOk, constructors.DhGenRetry, constructors.DhGenFail]
     if (!(dhGen instanceof nonceTypes[0] || dhGen instanceof nonceTypes[1] || dhGen instanceof nonceTypes[2])) {
         throw new Error(`Step 3.1 answer was ${dhGen}`)
     }
     const { name } = dhGen.constructor
-    if (dhGen.nonce !== resPQ.nonce) {
+    if (dhGen.nonce.neq(resPQ.nonce)) {
         throw new SecurityError(`Step 3 invalid ${name} nonce from server`)
     }
-    if (dhGen.serverNonce !== resPQ.serverNonce) {
+    if (dhGen.serverNonce.neq(resPQ.serverNonce)) {
         throw new SecurityError(`Step 3 invalid ${name} server nonce from server`)
     }
-    const authKey = new AuthKey(getByteArray(gab))
+    const authKey = new AuthKey()
+    await authKey.setKey(Helpers.getByteArray(gab))
+
     const nonceNumber = 1 + nonceTypes.indexOf(dhGen.constructor)
 
-    const newNonceHash = authKey.calcNewNonceHash(newNonce, nonceNumber)
+    const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber)
     const dhHash = dhGen[`newNonceHash${nonceNumber}`]
 
-    if (dhHash !== newNonceHash) {
+    if (dhHash.neq(newNonceHash)) {
         throw new SecurityError('Step 3 invalid new nonce hash')
     }
 
-    if (!(dhGen instanceof DhGenOk)) {
+    if (!(dhGen instanceof constructors.DhGenOk)) {
         throw new Error(`Step 3.2 answer was ${dhGen}`)
     }
     log.debug('Finished authKey generation step 3')
@@ -181,16 +175,5 @@ async function doAuthentication(sender, log) {
     return { authKey, timeOffset }
 }
 
-/**
- * Gets the arbitrary-length byte array corresponding to the given integer
- * @param integer {number,BigInt}
- * @param signed {boolean}
- * @returns {Buffer}
- */
-function getByteArray(integer, signed = false) {
-    const bits = integer.toString(2).length
-    const byteLength = Math.floor((bits + 8 - 1) / 8)
-    return Helpers.readBufferFromBigInt(BigInt(integer), byteLength, false, signed)
-}
 
 module.exports = doAuthentication

+ 12 - 22
gramjs/network/MTProtoPlainSender.js

@@ -4,9 +4,10 @@
  */
 const Helpers = require('../Helpers')
 const MTProtoState = require('./MTProtoState')
-const struct = require('python-struct')
 const BinaryReader = require('../extensions/BinaryReader')
 const { InvalidBufferError } = require('../errors/Common')
+const BigInt = require('big-integer')
+const { toSignedLittleBuffer } = require("../Helpers")
 
 /**
  * MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)
@@ -28,22 +29,29 @@ class MTProtoPlainSender {
      * @param request
      */
     async send(request) {
+
         let body = request.getBytes()
+
         let msgId = this._state._getNewMsgId()
-        const res = Buffer.concat([struct.pack('<qqi', [0, msgId.toString(), body.length]), body])
+        const m = toSignedLittleBuffer(msgId, 8)
+        const b = Buffer.alloc(4)
+        b.writeInt32LE(body.length, 0)
+
+        const res = Buffer.concat([Buffer.concat([Buffer.alloc(8), m, b]), body])
 
         await this._connection.send(res)
         body = await this._connection.recv()
+
         if (body.length < 8) {
             throw new InvalidBufferError(body)
         }
         const reader = new BinaryReader(body)
         const authKeyId = reader.readLong()
-        if (authKeyId !== BigInt(0)) {
+        if (authKeyId.neq(BigInt(0))) {
             throw new Error('Bad authKeyId')
         }
         msgId = reader.readLong()
-        if (msgId === BigInt(0)) {
+        if (msgId.eq(BigInt(0))) {
             throw new Error('Bad msgId')
         }
         /** ^ We should make sure that the read ``msg_id`` is greater
@@ -64,24 +72,6 @@ class MTProtoPlainSender {
         return reader.tgReadObject()
     }
 
-    /**
-     * Generates a new message ID based on the current time (in ms) since epoch
-     * @returns {BigInt}
-     */
-    getNewMsgId() {
-        // See https://core.telegram.org/mtproto/description#message-identifier-msg-id
-        const msTime = Date.now()
-        let newMsgId =
-            (BigInt(Math.floor(msTime / 1000)) << BigInt(32)) | // "must approximately equal unixtime*2^32"
-            (BigInt(msTime % 1000) << BigInt(32)) | // "approximate moment in time the message was created"
-            (BigInt(Helpers.getRandomInt(0, 524288)) << BigInt(2)) // "message identifiers are divisible by 4"
-        // Ensure that we always return a message ID which is higher than the previous one
-        if (this._lastMsgId >= newMsgId) {
-            newMsgId = this._lastMsgId + BigInt(4)
-        }
-        this._lastMsgId = newMsgId
-        return BigInt(newMsgId)
-    }
 }
 
 module.exports = MTProtoPlainSender

+ 129 - 52
gramjs/network/MTProtoSender.js

@@ -7,10 +7,11 @@ const RPCResult = require('../tl/core/RPCResult')
 const MessageContainer = require('../tl/core/MessageContainer')
 const GZIPPacked = require('../tl/core/GZIPPacked')
 const RequestState = require('./RequestState')
-const format = require('string-format')
-const { MsgsAck, File, MsgsStateInfo, Pong } = require('../tl/types')
+const { MsgsAck, upload, MsgsStateInfo, Pong } = require('../tl').constructors
 const MessagePacker = require('../extensions/MessagePacker')
 const BinaryReader = require('../extensions/BinaryReader')
+const { UpdateConnectionState } = require("./index");
+const { BadMessageError } = require("../errors/Common")
 const {
     BadServerSalt,
     BadMsgNotification,
@@ -21,15 +22,13 @@ const {
     MsgsStateReq,
     MsgResendReq,
     MsgsAllInfo,
-} = require('../tl/types')
+} = require('../tl').constructors
 const { SecurityError } = require('../errors/Common')
 const { InvalidBufferError } = require('../errors/Common')
-const { LogOutRequest } = require('../tl/functions/auth')
+const { LogOut } = require('../tl').requests.auth
 const { RPCMessageToError } = require('../errors')
 const { TypeNotFoundError } = require('../errors/Common')
 
-// const { tlobjects } = require("../gramjs/tl/alltlobjects");
-format.extend(String.prototype, {})
 
 /**
  * MTProto Mobile Protocol sender
@@ -47,13 +46,15 @@ format.extend(String.prototype, {})
 class MTProtoSender {
     static DEFAULT_OPTIONS = {
         logger: null,
-        retries: 5,
-        delay: 1,
+        retries: Infinity,
+        delay: 2000,
         autoReconnect: true,
         connectTimeout: null,
         authKeyCallback: null,
         updateCallback: null,
         autoReconnectCallback: null,
+        isMainSender: null,
+        senderCallback: null,
     }
 
     /**
@@ -64,6 +65,7 @@ class MTProtoSender {
         const args = { ...MTProtoSender.DEFAULT_OPTIONS, ...opts }
         this._connection = null
         this._log = args.logger
+        this._dcId = args.dcId
         this._retries = args.retries
         this._delay = args.delay
         this._autoReconnect = args.autoReconnect
@@ -71,6 +73,8 @@ class MTProtoSender {
         this._authKeyCallback = args.authKeyCallback
         this._updateCallback = args.updateCallback
         this._autoReconnectCallback = args.autoReconnectCallback
+        this._isMainSender = args.isMainSender;
+        this._senderCallback = args.senderCallback;
 
         /**
          * Whether the user has explicitly connected or disconnected.
@@ -93,7 +97,7 @@ class MTProtoSender {
         /**
          * Preserving the references of the AuthKey and state is important
          */
-        this.authKey = authKey || new AuthKey(null)
+        this.authKey = authKey || new AuthKey()
         this._state = new MTProtoState(this.authKey, this._log)
 
         /**
@@ -146,15 +150,32 @@ class MTProtoSender {
     /**
      * Connects to the specified given connection using the given auth key.
      * @param connection
+     * @param eventDispatch {function}
      * @returns {Promise<boolean>}
      */
-    async connect(connection) {
+    async connect(connection, eventDispatch=null) {
         if (this._user_connected) {
             this._log.info('User is already connected!')
             return false
         }
         this._connection = connection
-        await this._connect()
+
+        const retries = this._retries
+
+        for (let attempt = 0; attempt < retries; attempt++) {
+            try {
+                await this._connect()
+                break
+            } catch (e) {
+                if (attempt===0 && eventDispatch!==null){
+                    eventDispatch({ update: new UpdateConnectionState(-1) })
+                }
+                console.dir(e);
+
+                this._log.error("WebSocket connection failed attempt : "+(attempt+1))
+                await Helpers.sleep(this._delay)
+            }
+        }
         return true
     }
 
@@ -167,6 +188,7 @@ class MTProtoSender {
      * all pending requests, and closes the send and receive loops.
      */
     async disconnect() {
+
         await this._disconnect()
     }
 
@@ -199,14 +221,18 @@ class MTProtoSender {
         if (!this._user_connected) {
             throw new Error('Cannot send requests while disconnected')
         }
-
+        //CONTEST
+        const state = new RequestState(request)
+        this._send_queue.append(state)
+        return state.promise
+        /*
         if (!Helpers.isArrayLike(request)) {
             const state = new RequestState(request)
             this._send_queue.append(state)
             return state.promise
         } else {
             throw new Error('not supported')
-        }
+        }*/
     }
 
     /**
@@ -218,15 +244,16 @@ class MTProtoSender {
      */
     async _connect() {
         this._log.info('Connecting to {0}...'.replace('{0}', this._connection))
-
         await this._connection.connect()
         this._log.debug('Connection success!')
-        if (!this.authKey._key) {
+        //process.exit(0)
+        if (!this.authKey.getKey()) {
             const plain = new MtProtoPlainSender(this._connection, this._log)
             this._log.debug('New auth_key attempt ...')
             const res = await doAuthentication(plain, this._log)
             this._log.debug('Generated new auth_key successfully')
-            this.authKey.key = res.authKey
+            await this.authKey.setKey(res.authKey)
+
             this._state.time_offset = res.timeOffset
 
             /**
@@ -236,12 +263,13 @@ class MTProtoSender {
              * switch to different data centers.
              */
             if (this._authKeyCallback) {
-                this._authKeyCallback(this.authKey)
+                await this._authKeyCallback(this.authKey, this._dcId)
             }
         } else {
             this._log.debug('Already have an auth key ...')
         }
         this._user_connected = true
+        this._reconnecting = false
 
         this._log.debug('Starting send loop')
         this._send_loop_handle = this._sendLoop()
@@ -261,6 +289,9 @@ class MTProtoSender {
             this._log.info('Not disconnecting (already have no connection)')
             return
         }
+        if (this._updateCallback){
+            this._updateCallback(-1)
+        }
         this._log.info('Disconnecting from %s...'.replace('%s', this._connection.toString()))
         this._user_connected = false
         this._log.debug('Closing current connection...')
@@ -275,6 +306,8 @@ class MTProtoSender {
      * @private
      */
     async _sendLoop() {
+        this._send_queue = new MessagePacker(this._state, this._log)
+
         while (this._user_connected && !this._reconnecting) {
             if (this._pending_ack.size) {
                 const ack = new RequestState(new MsgsAck({ msgIds: Array(...this._pending_ack) }))
@@ -282,12 +315,16 @@ class MTProtoSender {
                 this._last_acks.push(ack)
                 this._pending_ack.clear()
             }
-            // this._log.debug('Waiting for messages to send...');
-            this._log.debug('Waiting for messages to send...')
+            this._log.debug('Waiting for messages to send...'+this._reconnecting)
             // TODO Wait for the connection send queue to be empty?
             // This means that while it's not empty we can wait for
             // more messages to be added to the send queue.
             const res = await this._send_queue.get()
+
+            if (this._reconnecting) {
+                return
+            }
+
             if (!res) {
                 continue
             }
@@ -295,17 +332,27 @@ class MTProtoSender {
             const batch = res.batch
             this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`)
 
-            data = this._state.encryptMessageData(data)
+            data = await this._state.encryptMessageData(data)
 
             try {
                 await this._connection.send(data)
             } catch (e) {
-                console.log(e)
+                this._log.error(e)
                 this._log.info('Connection closed while sending data')
                 return
             }
             for (const state of batch) {
-                this._pending_state[state.msgId] = state
+                if (!Array.isArray(state)) {
+                    if (state.request.classType === 'request') {
+                        this._pending_state[state.msgId] = state
+                    }
+                } else {
+                    for (const s of state) {
+                        if (s.request.classType === 'request') {
+                            this._pending_state[s.msgId] = s
+                        }
+                    }
+                }
             }
             this._log.debug('Encrypted messages put in a queue to be sent')
         }
@@ -323,13 +370,12 @@ class MTProtoSender {
             } catch (e) {
                 // this._log.info('Connection closed while receiving data');
                 this._log.warn('Connection closed while receiving data')
+                this._startReconnect()
                 return
             }
             try {
                 message = await this._state.decryptMessageData(body)
             } catch (e) {
-                console.log(e)
-
                 if (e instanceof TypeNotFoundError) {
                     // Received object which we don't know how to deserialize
                     this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`)
@@ -341,23 +387,38 @@ class MTProtoSender {
                     continue
                 } else if (e instanceof InvalidBufferError) {
                     this._log.info('Broken authorization key; resetting')
-                    this.authKey.key = null
-                    if (this._authKeyCallback) {
-                        await this._authKeyCallback(null)
+                    if (this._updateCallback && this._isMainSender){
+                        // 0 == broken
+                        this._updateCallback(0)
+                    } else if (this._senderCallback && !this._isMainSender){
+                        // Deletes the current sender from the object
+                        this._senderCallback(this._dcId)
                     }
-                    this._startReconnect(e)
 
+                    // We don't really need to do this if we're going to sign in again
+                    /*await this.authKey.setKey(null)
+
+                    if (this._authKeyCallback) {
+                        await this._authKeyCallback(null)
+                    }*/
+                    // We can disconnect at sign in
+                    /* await this.disconnect()
+                    */
                     return
                 } else {
                     this._log.error('Unhandled error while receiving data')
+                    this._log.error(e)
+                    console.log(e)
+                    this._startReconnect()
                     return
                 }
             }
             try {
                 await this._processMessage(message)
             } catch (e) {
-                console.log(e)
                 this._log.error('Unhandled error while receiving data')
+                console.log(e)
+                this._log.error(e)
             }
         }
     }
@@ -400,8 +461,8 @@ class MTProtoSender {
 
         const toPop = []
 
-        for (state in this._pending_state) {
-            if (state.containerId === msgId) {
+        for (state of Object.values(this._pending_state)) {
+            if (state.containerId && state.containerId.equals(msgId)) {
                 toPop.push(state.msgId)
             }
         }
@@ -432,7 +493,7 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleRPCResult(message) {
+    _handleRPCResult(message) {
         const RPCResult = message.obj
         const state = this._pending_state[RPCResult.reqMsgId]
         if (state) {
@@ -447,10 +508,11 @@ class MTProtoSender {
             // which contain the real response right after.
             try {
                 const reader = new BinaryReader(RPCResult.body)
-                if (!(reader.tgReadObject() instanceof File)) {
+                if (!(reader.tgReadObject() instanceof upload.File)) {
                     throw new TypeNotFoundError('Not an upload.File')
                 }
             } catch (e) {
+                this._log.error(e)
                 if (e instanceof TypeNotFoundError) {
                     this._log.info(`Received response without parent request: ${RPCResult.body}`)
                     return
@@ -466,7 +528,7 @@ class MTProtoSender {
             state.reject(error)
         } else {
             const reader = new BinaryReader(RPCResult.body)
-            const read = await state.request.readResult(reader)
+            const read = state.request.readResult(reader)
             state.resolve(read)
         }
     }
@@ -502,10 +564,10 @@ class MTProtoSender {
     async _handleUpdate(message) {
         if (message.obj.SUBCLASS_OF_ID !== 0x8af52aac) {
             // crc32(b'Updates')
-            this._log.warn(`Note: ${message.obj.constructor.name} is not an update, not dispatching it`)
+            this._log.warn(`Note: ${message.obj.className} is not an update, not dispatching it`)
             return
         }
-        this._log.debug('Handling update ' + message.obj.constructor.name)
+        this._log.debug('Handling update ' + message.obj.className)
         if (this._updateCallback) {
             this._updateCallback(message.obj)
         }
@@ -561,8 +623,8 @@ class MTProtoSender {
     async _handleBadNotification(message) {
         const badMsg = message.obj
         const states = this._popStates(badMsg.badMsgId)
-        this._log.debug(`Handling bad msg ${badMsg}`)
-        if ([16, 17].contains(badMsg.errorCode)) {
+        this._log.debug(`Handling bad msg ${JSON.stringify(badMsg)}`)
+        if ([16, 17].includes(badMsg.errorCode)) {
             // Sent msg_id too low or too high (respectively).
             // Use the current msg_id to determine the right time offset.
             const to = this._state.updateTimeOffset(message.msgId)
@@ -575,11 +637,10 @@ class MTProtoSender {
             // msg_seqno too high never seems to happen but just in case
             this._state._sequence -= 16
         } else {
-            // for (const state of states) {
-            // TODO set errors;
-            /* state.future.set_exception(
-            BadMessageError(state.request, bad_msg.error_code))*/
-            // }
+
+            for (const state of states) {
+                state.reject(new BadMessageError(state.request, badMsg.errorCode))
+            }
 
             return
         }
@@ -655,7 +716,7 @@ class MTProtoSender {
         this._log.debug(`Handling acknowledge for ${ack.msgIds}`)
         for (const msgId of ack.msgIds) {
             const state = this._pending_state[msgId]
-            if (state && state.request instanceof LogOutRequest) {
+            if (state && state.request instanceof LogOut) {
                 delete this._pending_state[msgId]
                 state.resolve(true)
             }
@@ -705,30 +766,46 @@ class MTProtoSender {
     async _handleMsgAll(message) {
     }
 
-    _startReconnect(e) {
+    async _startReconnect() {
         if (this._user_connected && !this._reconnecting) {
             this._reconnecting = true
-            this._reconnect(e)
+            // TODO Should we set this?
+            // this._user_connected = false
+            this._log.info("Started reconnecting")
+            this._reconnect()
         }
     }
 
-    async _reconnect(e) {
+    async _reconnect() {
         this._log.debug('Closing current connection...')
-        await this._connection.disconnect()
-        this._reconnecting = false
+        try {
+            await this.disconnect()
+        } catch (err) {
+            this._log.warn(err)
+        }
+        this._send_queue.append(null)
+
         this._state.reset()
         const retries = this._retries
+
+
         for (let attempt = 0; attempt < retries; attempt++) {
             try {
                 await this._connect()
-                this._send_queue.extend(Object.values(this._pending_state))
+                // uncomment this if you want to resend
+                //this._send_queue.extend(Object.values(this._pending_state))
                 this._pending_state = {}
                 if (this._autoReconnectCallback) {
-                    this._autoReconnectCallback()
+                    await this._autoReconnectCallback()
+                }
+                if (this._updateCallback){
+                    this._updateCallback(1)
                 }
+
                 break
             } catch (e) {
-                this._log.error(e)
+                this._log.error("WebSocket connection failed attempt : "+(attempt+1))
+                console.log(e)
                 await Helpers.sleep(this._delay)
             }
         }

+ 38 - 28
gramjs/network/MTProtoState.js

@@ -1,11 +1,13 @@
-const struct = require('python-struct')
 const Helpers = require('../Helpers')
-const AES = require('../crypto/AES')
+const IGE = require('../crypto/IGE')
 const BinaryReader = require('../extensions/BinaryReader')
 const GZIPPacked = require('../tl/core/GZIPPacked')
 const { TLMessage } = require('../tl/core')
 const { SecurityError, InvalidBufferError } = require('../errors/Common')
-const { InvokeAfterMsgRequest } = require('../tl/functions')
+const { InvokeAfterMsg } = require('../tl').requests
+const BigInt = require('big-integer')
+const { toSignedLittleBuffer,readBufferFromBigInt } = require("../Helpers")
+const { readBigIntFromBuffer } = require("../Helpers")
 
 class MTProtoState {
     /**
@@ -50,7 +52,7 @@ class MTProtoState {
         // Session IDs can be random on every connection
         this.id = Helpers.generateRandomLong(true)
         this._sequence = 0
-        this._lastMsgId = 0
+        this._lastMsgId = BigInt(0)
     }
 
     /**
@@ -69,11 +71,12 @@ class MTProtoState {
      * @param client
      * @returns {{iv: Buffer, key: Buffer}}
      */
-    _calcKey(authKey, msgKey, client) {
+    async _calcKey(authKey, msgKey, client) {
         const x = client === true ? 0 : 8
-        const sha256a = Helpers.sha256(Buffer.concat([msgKey, authKey.slice(x, x + 36)]))
-        const sha256b = Helpers.sha256(Buffer.concat([authKey.slice(x + 40, x + 76), msgKey]))
-
+        const [sha256a , sha256b] = await Promise.all([
+            Helpers.sha256(Buffer.concat([msgKey, authKey.slice(x, x + 36)])),
+            Helpers.sha256(Buffer.concat([authKey.slice(x + 40, x + 76), msgKey]))
+        ])
         const key = Buffer.concat([sha256a.slice(0, 8), sha256b.slice(8, 24), sha256a.slice(24, 32)])
         const iv = Buffer.concat([sha256b.slice(0, 8), sha256a.slice(8, 24), sha256b.slice(24, 32)])
         return { key, iv }
@@ -94,9 +97,14 @@ class MTProtoState {
         if (!afterId) {
             body = await GZIPPacked.gzipIfSmaller(contentRelated, data)
         } else {
-            body = await GZIPPacked.gzipIfSmaller(contentRelated, new InvokeAfterMsgRequest(afterId, data).toBuffer())
+            body = await GZIPPacked.gzipIfSmaller(contentRelated, new InvokeAfterMsg(afterId, data).getBytes())
         }
-        buffer.write(struct.pack('<qii', msgId.toString(), seqNo, body.length))
+        const s = Buffer.alloc(4)
+        s.writeInt32LE(seqNo, 0)
+        const b = Buffer.alloc(4)
+        b.writeInt32LE(body.length, 0)
+        const m = toSignedLittleBuffer(msgId, 8)
+        buffer.write(Buffer.concat([m, s, b]))
         buffer.write(body)
         return msgId
     }
@@ -106,19 +114,22 @@ class MTProtoState {
      * following MTProto 2.0 guidelines core.telegram.org/mtproto/description.
      * @param data
      */
-    encryptMessageData(data) {
-        data = Buffer.concat([struct.pack('<qq', this.salt.toString(), this.id.toString()), data])
+    async encryptMessageData(data) {
+        await this.authKey.waitForKey()
+        const s = toSignedLittleBuffer(this.salt,8)
+        const i = toSignedLittleBuffer(this.id,8)
+        data = Buffer.concat([Buffer.concat([s,i]), data])
         const padding = Helpers.generateRandomBytes(Helpers.mod(-(data.length + 12), 16) + 12)
         // Being substr(what, offset, length); x = 0 for client
         // "msg_key_large = SHA256(substr(auth_key, 88+x, 32) + pt + padding)"
-        const msgKeyLarge = Helpers.sha256(Buffer.concat([this.authKey.key.slice(88, 88 + 32), data, padding]))
+        const msgKeyLarge = await Helpers.sha256(Buffer.concat([this.authKey.getKey().slice(88, 88 + 32), data, padding]))
         // "msg_key = substr (msg_key_large, 8, 16)"
         const msgKey = msgKeyLarge.slice(8, 24)
 
-        const { iv, key } = this._calcKey(this.authKey.key, msgKey, true)
+        const { iv, key } = await this._calcKey(this.authKey.getKey(), msgKey, true)
 
         const keyId = Helpers.readBufferFromBigInt(this.authKey.keyId, 8)
-        return Buffer.concat([keyId, msgKey, AES.encryptIge(Buffer.concat([data, padding]), key, iv)])
+        return Buffer.concat([keyId, msgKey, new IGE(key,iv).encryptIge(Buffer.concat([data, padding]))])
     }
 
     /**
@@ -132,19 +143,18 @@ class MTProtoState {
 
         // TODO Check salt,sessionId, and sequenceNumber
         const keyId = Helpers.readBigIntFromBuffer(body.slice(0, 8))
-
-        if (keyId !== this.authKey.keyId) {
+        if (keyId.neq(this.authKey.keyId)) {
             throw new SecurityError('Server replied with an invalid auth key')
         }
 
         const msgKey = body.slice(8, 24)
-        const { iv, key } = this._calcKey(this.authKey.key, msgKey, false)
-        body = AES.decryptIge(body.slice(24), key, iv)
+        const { iv, key } = await this._calcKey(this.authKey.getKey(), msgKey, false)
+        body = new IGE(key,iv).decryptIge(body.slice(24))
 
         // https://core.telegram.org/mtproto/security_guidelines
         // Sections "checking sha256 hash" and "message length"
 
-        const ourKey = Helpers.sha256(Buffer.concat([this.authKey.key.slice(96, 96 + 32), body]))
+        const ourKey = await Helpers.sha256(Buffer.concat([this.authKey.getKey().slice(96, 96 + 32), body]))
 
         if (!msgKey.equals(ourKey.slice(8, 24))) {
             throw new SecurityError('Received msg_key doesn\'t match with expected one')
@@ -154,7 +164,7 @@ class MTProtoState {
         reader.readLong() // removeSalt
         const serverId = reader.readLong()
         if (serverId !== this.id) {
-            throw new SecurityError('Server replied with a wrong session ID')
+            // throw new SecurityError('Server replied with a wrong session ID');
         }
 
         const remoteMsgId = reader.readLong()
@@ -164,7 +174,7 @@ class MTProtoState {
         // We could read msg_len bytes and use those in a new reader to read
         // the next TLObject without including the padding, but since the
         // reader isn't used for anything else after this, it's unnecessary.
-        const obj = await reader.tgReadObject()
+        const obj = reader.tgReadObject()
 
         return new TLMessage(remoteMsgId, remoteSequence, obj)
     }
@@ -177,9 +187,9 @@ class MTProtoState {
     _getNewMsgId() {
         const now = new Date().getTime() / 1000 + this.timeOffset
         const nanoseconds = Math.floor((now - Math.floor(now)) * 1e9)
-        let newMsgId = (BigInt(Math.floor(now)) << BigInt(32)) | (BigInt(nanoseconds) << BigInt(2))
-        if (this._lastMsgId >= newMsgId) {
-            newMsgId = this._lastMsgId + BigInt(4)
+        let newMsgId = (BigInt(Math.floor(now)).shiftLeft(BigInt(32))).or(BigInt(nanoseconds).shiftLeft(BigInt(2)))
+        if (this._lastMsgId.greaterOrEquals(newMsgId)) {
+            newMsgId = this._lastMsgId.add(BigInt(4))
         }
         this._lastMsgId = newMsgId
         return newMsgId
@@ -188,17 +198,17 @@ class MTProtoState {
     /**
      * Updates the time offset to the correct
      * one given a known valid message ID.
-     * @param correctMsgId
+     * @param correctMsgId {BigInteger}
      */
     updateTimeOffset(correctMsgId) {
         const bad = this._getNewMsgId()
         const old = this.timeOffset
         const now = Math.floor(new Date().getTime() / 1000)
-        const correct = correctMsgId >> BigInt(32)
+        const correct = correctMsgId.shiftRight(BigInt(32))
         this.timeOffset = correct - now
 
         if (this.timeOffset !== old) {
-            this._lastMsgId = 0
+            this._lastMsgId = BigInt(0)
             this._log.debug(
                 `Updated time offset (old offset ${old}, bad ${bad}, good ${correctMsgId}, new ${this.timeOffset})`,
             )

+ 18 - 12
gramjs/network/connection/Connection.js

@@ -1,7 +1,7 @@
 const PromisedWebSockets = require('../../extensions/PromisedWebSockets')
 const PromisedNetSockets = require('../../extensions/PromisedNetSockets')
-
 const AsyncQueue = require('../../extensions/AsyncQueue')
+const {IS_NODE} = require("../../Helpers");
 
 /**
  * The `Connection` class is a wrapper around ``asyncio.open_connection``.
@@ -17,7 +17,7 @@ const AsyncQueue = require('../../extensions/AsyncQueue')
 class Connection {
     PacketCodecClass = null
 
-    constructor(ip, port, dcId, loggers, browser = false) {
+    constructor(ip, port, dcId, loggers) {
         this._ip = ip
         this._port = port
         this._dcId = dcId
@@ -29,25 +29,24 @@ class Connection {
         this._obfuscation = null // TcpObfuscated and MTProxy
         this._sendArray = new AsyncQueue()
         this._recvArray = new AsyncQueue()
-        if (browser) {
-            this.socket = new PromisedWebSockets()
-        } else {
-            this.socket = new PromisedNetSockets()
-        }
+        this.socket = IS_NODE ? new PromisedNetSockets() : new PromisedWebSockets()
+
+        //this.socket = new PromisedWebSockets()
     }
 
     async _connect() {
         this._log.debug('Connecting')
-        await this.socket.connect(this._port, this._ip)
+        this._codec = new this.PacketCodecClass(this)
+        await this.socket.connect(this._port, this._ip, this)
         this._log.debug('Finished connecting')
         // await this.socket.connect({host: this._ip, port: this._port});
-        this._codec = new this.PacketCodecClass(this)
         await this._initConn()
     }
 
     async connect() {
         await this._connect()
         this._connected = true
+
         if (!this._sendTask) {
             this._sendTask = this._sendLoop()
         }
@@ -56,6 +55,7 @@ class Connection {
 
     async disconnect() {
         this._connected = false
+        await this._recvArray.push(null)
         await this.socket.close()
     }
 
@@ -69,6 +69,7 @@ class Connection {
     async recv() {
         while (this._connected) {
             const result = await this._recvArray.pop()
+
             // null = sentinel value = keep trying
             if (result) {
                 return result
@@ -82,10 +83,13 @@ class Connection {
         try {
             while (this._connected) {
                 const data = await this._sendArray.pop()
+                if (!data) {
+                    this._sendTask = null
+                    return
+                }
                 await this._send(data)
             }
         } catch (e) {
-            console.log(e)
             this._log.info('The server closed the connection while sending')
         }
     }
@@ -96,11 +100,13 @@ class Connection {
             try {
                 data = await this._recv()
                 if (!data) {
-                    return
+                    throw new Error("no data received")
                 }
             } catch (e) {
+                this._log.info('connection closed')
+                //await this._recvArray.push()
                 console.log(e)
-                this._log.info('an error occured')
+                this.disconnect()
                 return
             }
             await this._recvArray.push(data)

+ 9 - 7
gramjs/network/connection/TCPAbridged.js

@@ -1,6 +1,6 @@
-const struct = require('python-struct')
 const { readBufferFromBigInt } = require('../../Helpers')
 const { Connection, PacketCodec } = require('./Connection')
+const BigInt = require('big-integer')
 
 class AbridgedPacketCodec extends PacketCodec {
     static tag = Buffer.from('ef', 'hex')
@@ -15,19 +15,21 @@ class AbridgedPacketCodec extends PacketCodec {
     encodePacket(data) {
         let length = data.length >> 2
         if (length < 127) {
-            length = struct.pack('B', length)
+            const b = Buffer.alloc(1)
+            b.writeUInt8(length, 0)
+            length = b
         } else {
-            length = Buffer.from('7f', 'hex') + readBufferFromBigInt(BigInt(length), 3)
+            length = Buffer.concat([Buffer.from('7f', 'hex'), readBufferFromBigInt(BigInt(length), 3)])
         }
         return Buffer.concat([length, data])
     }
 
     async readPacket(reader) {
         const readData = await reader.read(1)
-        let length = struct.unpack('<B', readData)[0]
+        let length = readData[0]
         if (length >= 127) {
-            length = struct.unpack(
-                '<i', Buffer.concat([await reader.read(3), Buffer.alloc(1)]))[0]
+            length = Buffer.concat([await reader.read(3), Buffer.alloc(1)])
+                .readInt32LE(0)
         }
 
         return await reader.read(length << 2)
@@ -40,7 +42,7 @@ class AbridgedPacketCodec extends PacketCodec {
  * 508 bytes (127 << 2, which is very common).
  */
 class ConnectionTCPAbridged extends Connection {
-    packetCode = AbridgedPacketCodec
+    PacketCodecClass = AbridgedPacketCodec
 }
 
 module.exports = {

+ 45 - 44
gramjs/network/connection/TCPFull.js

@@ -1,55 +1,56 @@
 const { Connection, PacketCodec } = require('./Connection')
-const struct = require('python-struct')
-const { crc32 } = require('crc')
-const { InvalidChecksumError } = require('../../errors/Common')
+ const { crc32 } = require('../../Helpers')
+ const { InvalidChecksumError } = require('../../errors/Common')
 
-class FullPacketCodec extends PacketCodec {
-    constructor(connection) {
-        super(connection)
-        this._sendCounter = 0 // Telegram will ignore us otherwise
-    }
+ class FullPacketCodec extends PacketCodec {
+     constructor(connection) {
+         super(connection)
+         this._sendCounter = 0 // Telegram will ignore us otherwise
+     }
 
-    encodePacket(data) {
-        // https://core.telegram.org/mtproto#tcp-transport
-        // total length, sequence number, packet and checksum (CRC32)
-        const length = data.length + 12
-        data = Buffer.concat([struct.pack('<ii', length, this._sendCounter), data])
-        const crc = struct.pack('<I', crc32(data))
-        this._sendCounter += 1
-        return Buffer.concat([data, crc])
-    }
+     encodePacket(data) {
+         // https://core.telegram.org/mtproto#tcp-transport
+         // total length, sequence number, packet and checksum (CRC32)
+         const length = data.length + 12
+         const e = Buffer.alloc(8)
+         e.writeInt32LE(length,0)
+         e.writeInt32LE(this._sendCounter,4)
+         data = Buffer.concat([e, data])
+         const crc =  Buffer.alloc(4)
+         crc.writeUInt32LE(crc32(data),0)
+         this._sendCounter += 1
+         return Buffer.concat([data, crc])
+     }
 
-    /**
-     *
-     * @param reader {PromisedWebSockets}
-     * @returns {Promise<*>}
-     */
-    async readPacket(reader) {
-        const packetLenSeq = await reader.read(8) // 4 and 4
-        // process.exit(0);
-        if (packetLenSeq === undefined) {
-            return false
-        }
+     /**
+      *
+      * @param reader {PromisedWebSockets}
+      * @returns {Promise<*>}
+      */
+     async readPacket(reader) {
+         const packetLenSeq = await reader.read(8) // 4 and 4
+         // process.exit(0);
+         if (packetLenSeq === undefined) {
+             return false
+         }
+         const packetLen = packetLenSeq.readInt32LE(0)
+         let body = await reader.read(packetLen - 8)
+         const checksum = body.slice(-4).readUInt32LE(0)
+         body = body.slice(0, -4)
 
-        const res = struct.unpack('<ii', packetLenSeq)
-        const [packetLen] = res
-        let body = await reader.read(packetLen - 8)
-        const [checksum] = struct.unpack('<I', body.slice(-4))
-        body = body.slice(0, -4)
-
-        const validChecksum = crc32(Buffer.concat([packetLenSeq, body]))
-        if (!(validChecksum === checksum)) {
-            throw new InvalidChecksumError(checksum, validChecksum)
-        }
-        return body
-    }
-}
+         const validChecksum = crc32(Buffer.concat([packetLenSeq, body]))
+         if (!(validChecksum === checksum)) {
+             throw new InvalidChecksumError(checksum, validChecksum)
+         }
+         return body
+     }
+ }
 
 class ConnectionTCPFull extends Connection {
-    PacketCodecClass = FullPacketCodec;
+     PacketCodecClass = FullPacketCodec;
 }
 
 module.exports = {
-    FullPacketCodec,
-    ConnectionTCPFull,
+     FullPacketCodec,
+     ConnectionTCPFull,
 }

+ 7 - 6
gramjs/network/connection/TCPObfuscated.js

@@ -1,7 +1,7 @@
 const { generateRandomBytes } = require('../../Helpers')
 const { ObfuscatedConnection } = require('./Connection')
 const { AbridgedPacketCodec } = require('./TCPAbridged')
-const AESModeCTR = require('../../crypto/AESCTR')
+const CTR = require('../../crypto/CTR')
 
 class ObfuscatedIO {
     header = null
@@ -23,7 +23,7 @@ class ObfuscatedIO {
 
         // eslint-disable-next-line no-constant-condition
         while (true) {
-            random = Buffer.from('dbf538959e7eed8a5b432e6b6c446424a126f29fcfea79ccefd803923b7d70c2118f86ecfc922e5e7e1938df06c956dab0b51ded5110ec598dc7fefcacd0b514', 'hex')// generateRandomBytes(64)
+            random = generateRandomBytes(64)
             if (random[0] !== 0xef && !(random.slice(4, 8).equals(Buffer.alloc(4)))) {
                 let ok = true
                 for (const key of keywords) {
@@ -45,20 +45,20 @@ class ObfuscatedIO {
         const encryptIv = Buffer.from(random.slice(40, 56))
         const decryptKey = Buffer.from(randomReversed.slice(0, 32))
         const decryptIv = Buffer.from(randomReversed.slice(32, 48))
-        const encryptor = new AESModeCTR(encryptKey, encryptIv)
-        const decryptor = new AESModeCTR(decryptKey, decryptIv)
+        const encryptor = new CTR(encryptKey, encryptIv)
+        const decryptor = new CTR(decryptKey, decryptIv)
 
         random = Buffer.concat([
             Buffer.from(random.slice(0, 56)), packetCodec.obfuscateTag, Buffer.from(random.slice(60)),
         ])
         random = Buffer.concat([
-            random.slice(0, 56), encryptor.encrypt(random).slice(56, 64), random.slice(64),
+            Buffer.from(random.slice(0, 56)), Buffer.from(encryptor.encrypt(random).slice(56, 64)),Buffer.from(random.slice(64)) ,
         ])
         return { random, encryptor, decryptor }
     }
 
     async read(n) {
-        const data = await this.connection.read(n)
+        const data = await this.connection.readExactly(n)
         return this._decrypt.encrypt(data)
     }
 
@@ -75,3 +75,4 @@ class ConnectionTCPObfuscated extends ObfuscatedConnection {
 module.exports = {
     ConnectionTCPObfuscated,
 }
+

+ 14 - 0
gramjs/network/index.js

@@ -1,6 +1,19 @@
 const MTProtoPlainSender = require('./MTProtoPlainSender')
 const doAuthentication = require('./Authenticator')
 const MTProtoSender = require('./MTProtoSender')
+
+class UpdateConnectionState {
+    static states = {
+        disconnected: -1,
+        connected: 1,
+        broken: 0,
+    }
+
+    constructor(state) {
+        this.state = state
+    }
+}
+
 const {
     Connection,
     ConnectionTCPFull,
@@ -15,4 +28,5 @@ module.exports = {
     MTProtoPlainSender,
     doAuthentication,
     MTProtoSender,
+    UpdateConnectionState,
 }

+ 25 - 3
gramjs/sessions/Abstract.js

@@ -8,9 +8,10 @@ class Session {
      * @param toInstance {Session|null}
      * @returns {Session}
      */
+    /* CONTEST
     clone(toInstance = null) {
         return toInstance || new this.constructor()
-    }
+    }*/
 
     /**
      * Sets the information of the data center address and port that
@@ -65,18 +66,20 @@ class Session {
      * Returns an ID of the takeout process initialized for this session,
      * or `None` if there's no were any unfinished takeout requests.
      */
+    /*CONTEST
     get takeoutId() {
         throw new Error('Not Implemented')
     }
-
+    */
     /**
      * Sets the ID of the unfinished takeout process for this session.
      * @param value
      */
+    /*CONTEST
     set takeoutId(value) {
         throw new Error('Not Implemented')
     }
-
+    */
     /**
      * Returns the ``UpdateState`` associated with the given `entity_id`.
      * If the `entity_id` is 0, it should return the ``UpdateState`` for
@@ -84,10 +87,13 @@ class Session {
      * it should ``return None``.
      * @param entityId
      */
+    /*CONTEST
     getUpdateState(entityId) {
         throw new Error('Not Implemented')
     }
 
+     */
+
     /**
      * Sets the given ``UpdateState`` for the specified `entity_id`, which
      * should be 0 if the ``UpdateState`` is the "general" state (and not
@@ -95,18 +101,24 @@ class Session {
      * @param entityId
      * @param state
      */
+    /*CONTEST
     setUpdateState(entityId, state) {
         throw new Error('Not Implemented')
     }
 
+     */
+
     /**
      * Called on client disconnection. Should be used to
      * free any used resources. Can be left empty if none.
      */
+    /*CONTEST
     close() {
 
     }
 
+     */
+
     /**
      * called whenever important properties change. It should
      * make persist the relevant session information to disk.
@@ -119,6 +131,7 @@ class Session {
      * Called upon client.log_out(). Should delete the stored
      * information from disk since it's not valid anymore.
      */
+
     delete() {
         throw new Error('Not Implemented')
     }
@@ -126,28 +139,37 @@ class Session {
     /**
      * Lists available sessions. Not used by the library itself.
      */
+    /*CONTEST
     listSessions() {
         throw new Error('Not Implemented')
     }
 
+     */
+
     /**
      * Processes the input ``TLObject`` or ``list`` and saves
      * whatever information is relevant (e.g., ID or access hash).
      * @param tlo
      */
+    /*CONTEST
     processEntities(tlo) {
         throw new Error('Not Implemented')
     }
 
+     */
+
     /**
      * Turns the given key into an ``InputPeer`` (e.g. ``InputPeerUser``).
      * The library uses this method whenever an ``InputPeer`` is needed
      * to suit several purposes (e.g. user only provided its ID or wishes
      * to use a cached username to avoid extra RPC).
      */
+    /*CONTEST
     getInputEntity(key) {
         throw new Error('Not Implemented')
     }
+
+     */
 }
 
 module.exports = Session

+ 126 - 0
gramjs/sessions/CacheApiSession.js

@@ -0,0 +1,126 @@
+const MemorySession = require('./Memory')
+const AuthKey = require('../crypto/AuthKey')
+const utils = require('../Utils')
+
+const CACHE_NAME = 'GramJs'
+const STORAGE_KEY_BASE = 'GramJs-session-'
+
+class CacheApi extends MemorySession {
+    constructor(sessionId) {
+        super()
+        this._storageKey = sessionId
+        this._authKeys = {}
+    }
+
+    async load() {
+        if (!this._storageKey) {
+            return
+        }
+
+        try {
+            const json = await fetchFromCache(this._storageKey)
+            const { mainDcId, keys, hashes } = JSON.parse(json)
+            const { ipAddress, port } = await utils.getDC(mainDcId)
+
+            this.setDC(mainDcId, ipAddress, port, true)
+
+            Object.keys(keys).forEach((dcId) => {
+                if (keys[dcId] && hashes[dcId]){
+                    this._authKeys[dcId] = new AuthKey(
+                        Buffer.from(keys[dcId].data),
+                        Buffer.from(hashes[dcId].data)
+                    )
+                }
+            })
+        } catch (err) {
+            throw new Error(`Failed to retrieve or parse JSON from Cache for key ${this._storageKey}`)
+        }
+    }
+
+    setDC(dcId, serverAddress, port, skipUpdateStorage = false) {
+        this._dcId = dcId
+        this._serverAddress = serverAddress
+        this._port = port
+
+        delete this._authKeys[dcId]
+
+        if (!skipUpdateStorage) {
+            void this._updateStorage()
+        }
+    }
+
+    async save() {
+        if (!this._storageKey) {
+            this._storageKey = generateStorageKey()
+        }
+
+        await this._updateStorage()
+
+        return this._storageKey
+    }
+
+    get authKey() {
+        throw new Error('Not supported')
+    }
+
+    set authKey(value) {
+        throw new Error('Not supported')
+    }
+
+    getAuthKey(dcId = this._dcId) {
+        return this._authKeys[dcId]
+    }
+
+    setAuthKey(authKey, dcId = this._dcId) {
+        this._authKeys[dcId] = authKey
+
+        void this._updateStorage()
+    }
+
+    async _updateStorage() {
+        if (!this._storageKey) {
+            return
+        }
+
+        const sessionData = {
+            mainDcId: this._dcId,
+            keys: {},
+            hashes: {}
+        }
+
+        Object.keys(this._authKeys).map((dcId) => {
+            const authKey = this._authKeys[dcId]
+            sessionData.keys[dcId] = authKey._key
+            sessionData.hashes[dcId] = authKey._hash
+        })
+
+        await saveToCache(this._storageKey, JSON.stringify(sessionData))
+    }
+
+    async delete() {
+        const request = new Request(this._storageKey)
+        const cache = await self.caches.open(CACHE_NAME)
+        await cache.delete(request)
+    }
+}
+
+function generateStorageKey() {
+    // Creating two sessions at the same moment is not expected nor supported.
+    return `${STORAGE_KEY_BASE}${Date.now()}`
+}
+
+async function fetchFromCache(key) {
+    const request = new Request(key)
+    const cache = await self.caches.open(CACHE_NAME)
+    const cached = await cache.match(request)
+    return cached ? cached.text() : null
+}
+
+async function saveToCache(key, data) {
+    const request = new Request(key)
+    const response = new Response(data)
+    const cache = await self.caches.open(CACHE_NAME)
+    return cache.put(request, response)
+}
+
+module.exports = CacheApi

+ 20 - 19
gramjs/sessions/Memory.js

@@ -1,6 +1,5 @@
-const { TLObject } = require('../tl/tlobject')
 const utils = require('../Utils')
-const types = require('../tl/types')
+const types = require('../tl').constructors
 const Session = require('./Abstract')
 
 class MemorySession extends Session {
@@ -10,7 +9,6 @@ class MemorySession extends Session {
         this._serverAddress = null
         this._dcId = 0
         this._port = null
-        this._authKey = null
         this._takeoutId = null
 
         this._entities = new Set()
@@ -42,7 +40,7 @@ class MemorySession extends Session {
     set authKey(value) {
         this._authKey = value
     }
-
+    /* CONTEST
     get takeoutId() {
         return this._takeoutId
     }
@@ -66,7 +64,8 @@ class MemorySession extends Session {
     save() {
     }
 
-    load() {
+    async load() {
+
     }
 
     delete() {
@@ -80,7 +79,7 @@ class MemorySession extends Session {
     }
 
     _entityToRow(e) {
-        if (!(e instanceof TLObject)) {
+        if (!(e.classType === "constructor")) {
             return
         }
         let p
@@ -89,9 +88,9 @@ class MemorySession extends Session {
             p = utils.getInputPeer(e, false)
             markedId = utils.getPeerId(p)
         } catch (e) {
-            // Note: `get_input_peer` already checks for non-zero `access_hash`.
+            // Note: `get_input_peer` already checks for non-zero `accessHash`.
             // See issues #354 and #392. It also checks that the entity
-            // is not `min`, because its `access_hash` cannot be used
+            // is not `min`, because its `accessHash` cannot be used
             // anywhere (since layer 102, there are two access hashes).
             return
         }
@@ -115,18 +114,20 @@ class MemorySession extends Session {
 
     _entitiesToRows(tlo) {
         let entities = []
-        if (tlo instanceof TLObject && utils.isListLike(tlo)) {
+        if (tlo.classType === "constructor" && utils.isListLike(tlo)) {
             // This may be a list of users already for instance
             entities = tlo
         } else {
-            if ('user' in tlo) {
-                entities.push(tlo.user)
-            }
-            if ('chats' in tlo && utils.isListLike(tlo.chats)) {
-                entities.concat(tlo.chats)
-            }
-            if ('users' in tlo && utils.isListLike(tlo.users)) {
-                entities.concat(tlo.users)
+            if (tlo instanceof Object) {
+                if ('user' in tlo) {
+                    entities.push(tlo.user)
+                }
+                if ('chats' in tlo && utils.isListLike(tlo.chats)) {
+                    entities.concat(tlo.chats)
+                }
+                if ('users' in tlo && utils.isListLike(tlo.users)) {
+                    entities.concat(tlo.users)
+                }
             }
         }
         const rows = [] // Rows to add (id, hash, username, phone, name)
@@ -202,7 +203,7 @@ class MemorySession extends Session {
             return utils.getInputPeer(key)
         } else {
             // Not a TLObject or can't be cast into InputPeer
-            if (key instanceof TLObject) {
+            if (key.classType === 'constructor') {
                 key = utils.getPeerId(key)
                 exact = true
             } else {
@@ -249,7 +250,7 @@ class MemorySession extends Session {
         } else {
             throw new Error('Could not find input entity with key ' + key)
         }
-    }
+    }*/
 }
 
 module.exports = MemorySession

+ 39 - 20
gramjs/sessions/StringSession.js

@@ -27,19 +27,16 @@ class StringSession extends MemorySession {
                 throw new Error('Not a valid string')
             }
             session = session.slice(1)
-            const ipLen = session.length === 352 ? 4 : 16
             const r = StringSession.decode(session)
             const reader = new BinaryReader(r)
-            this._dcId = reader.read(1).readUInt8(0)
-            const ip = reader.read(ipLen)
-            this._port = reader.read(2).readInt16BE(0)
-            const key = reader.read(-1)
-            this._serverAddress = ip.readUInt8(0) + '.' +
-                ip.readUInt8(1) + '.' + ip.readUInt8(2) +
-                '.' + ip.readUInt8(3)
-            if (key) {
-                this._authKey = new AuthKey(key)
-            }
+            this._dcId = reader.read(1)
+                .readUInt8(0)
+            const serverAddressLen = reader.read(2)
+                .readInt16BE(0)
+            this._serverAddress = String(reader.read(serverAddressLen))
+            this._port = reader.read(2)
+                .readInt16BE(0)
+            this._key = reader.read(-1)
         }
     }
 
@@ -59,28 +56,50 @@ class StringSession extends MemorySession {
         return Buffer.from(x, 'base64')
     }
 
+    async load() {
+        if (this._key) {
+            this._authKey = new AuthKey()
+            await this._authKey.setKey(this._key)
+        }
+    }
+
     save() {
         if (!this.authKey) {
             return ''
         }
-        const ip = this.serverAddress.split('.')
         const dcBuffer = Buffer.from([this.dcId])
-        const ipBuffer = Buffer.alloc(4)
+        const addressBuffer = Buffer.from(this.serverAddress)
+        const addressLengthBuffer = Buffer.alloc(2)
+        addressLengthBuffer.writeInt16BE(addressBuffer.length, 0)
         const portBuffer = Buffer.alloc(2)
-
         portBuffer.writeInt16BE(this.port, 0)
-        for (let i = 0; i < ip.length; i++) {
-            ipBuffer.writeUInt8(parseInt(ip[i]), i)
-        }
-
 
         return CURRENT_VERSION + StringSession.encode(Buffer.concat([
             dcBuffer,
-            ipBuffer,
+            addressLengthBuffer,
+            addressBuffer,
             portBuffer,
-            this.authKey.key,
+            this.authKey.getKey(),
         ]))
     }
+
+    getAuthKey(dcId) {
+        if (dcId && dcId !== this.dcId) {
+            // Not supported.
+            return undefined
+        }
+
+        return this.authKey
+    }
+
+    setAuthKey(authKey, dcId) {
+        if (dcId && dcId !== this.dcId) {
+            // Not supported.
+            return undefined
+        }
+
+        this.authKey = authKey
+    }
 }
 
 module.exports = StringSession

+ 3 - 2
gramjs/sessions/index.js

@@ -1,8 +1,9 @@
-const JSONSession = require('./JSONSession')
 const Memory = require('./Memory')
 const StringSession = require('./StringSession')
+const CacheApiSession = require('./CacheApiSession')
+
 module.exports = {
-    JSONSession,
     Memory,
     StringSession,
+    CacheApiSession
 }

+ 19 - 0
gramjs/tl/AllTLObjects.js

@@ -0,0 +1,19 @@
+const api = require('./api')
+const LAYER = 112
+const tlobjects = {}
+
+
+for (const tl of Object.values(api)) {
+    if (tl.CONSTRUCTOR_ID) {
+        tlobjects[tl.CONSTRUCTOR_ID] = tl
+    } else {
+        for (const sub of Object.values(tl)) {
+            tlobjects[sub.CONSTRUCTOR_ID] = sub
+        }
+    }
+}
+
+module.exports = {
+    LAYER,
+    tlobjects
+}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 10113 - 0
gramjs/tl/api.d.ts


+ 391 - 0
gramjs/tl/api.js

@@ -0,0 +1,391 @@
+const {
+    parseTl,
+    serializeBytes,
+    serializeDate
+} = require('./generationHelpers')
+const { IS_NODE,toSignedLittleBuffer } = require('../Helpers')
+let tlContent,schemeContent;
+if (IS_NODE){
+    const fs = require("fs");
+
+    tlContent = fs.readFileSync(__dirname+'/static/api.tl',"utf-8");
+    schemeContent = fs.readFileSync(__dirname+'/static/schema.tl',"utf-8");
+}else{
+     tlContent = require('./static/api.tl').default;
+     schemeContent = require('./static/schema.tl').default;
+
+}
+
+/*CONTEST
+const NAMED_AUTO_CASTS = new Set([
+    'chatId,int'
+])
+const NAMED_BLACKLIST = new Set([
+    'discardEncryption'
+])
+const AUTO_CASTS = new Set([
+    'InputPeer',
+    'InputChannel',
+    'InputUser',
+    'InputDialogPeer',
+    'InputNotifyPeer',
+    'InputMedia',
+    'InputPhoto',
+    'InputMessage',
+    'InputDocument',
+    'InputChatPhoto'
+])
+
+ */
+const CACHING_SUPPORTED = typeof self !== 'undefined' && self.localStorage !== undefined
+
+const CACHE_KEY = 'GramJs:apiCache'
+
+function buildApiFromTlSchema() {
+    let definitions;
+    const fromCache = CACHING_SUPPORTED && loadFromCache()
+
+    if (fromCache) {
+        definitions = fromCache
+    } else {
+        definitions = loadFromTlSchemas()
+
+        if (CACHING_SUPPORTED) {
+            localStorage.setItem(CACHE_KEY, JSON.stringify(definitions))
+        }
+    }
+
+    return mergeWithNamespaces(
+      createClasses('constructor', definitions.constructors),
+      createClasses('request', definitions.requests)
+    )
+}
+
+function loadFromCache() {
+    const jsonCache = localStorage.getItem(CACHE_KEY)
+    return jsonCache && JSON.parse(jsonCache)
+}
+
+function loadFromTlSchemas() {
+    const [constructorParamsApi, functionParamsApi] = extractParams(tlContent)
+    const [constructorParamsSchema, functionParamsSchema] = extractParams(schemeContent)
+    const constructors = [].concat(constructorParamsApi, constructorParamsSchema)
+    const requests = [].concat(functionParamsApi, functionParamsSchema)
+
+    return { constructors, requests }
+}
+
+function mergeWithNamespaces(obj1, obj2) {
+    const result = { ...obj1 }
+
+    Object.keys(obj2).forEach((key) => {
+        if (typeof obj2[key] === 'function' || !result[key]) {
+            result[key] = obj2[key]
+        } else {
+            Object.assign(result[key], obj2[key])
+        }
+    })
+
+    return result
+}
+
+function extractParams(fileContent) {
+    const f = parseTl(fileContent, 109)
+    const constructors = []
+    const functions = []
+    for (const d of f) {
+        d.isFunction ? functions.push(d) : constructors.push(d)
+    }
+    return [constructors, functions]
+}
+
+function argToBytes(x, type) {
+    switch (type) {
+        case 'int':
+            const i = Buffer.alloc(4)
+            i.writeInt32LE(x, 0)
+            return i
+        case 'long':
+            return toSignedLittleBuffer(x, 8)
+        case 'int128':
+            return toSignedLittleBuffer(x, 16)
+        case 'int256':
+            return toSignedLittleBuffer(x, 32)
+        case 'double':
+            const d = Buffer.alloc(8)
+            d.writeDoubleLE(x, 0)
+            return d
+        case 'string':
+            return serializeBytes(x)
+        case 'Bool':
+            return x ? Buffer.from('b5757299', 'hex') : Buffer.from('379779bc', 'hex')
+        case 'true':
+            return Buffer.alloc(0)
+        case 'bytes':
+            return serializeBytes(x)
+        case 'date':
+            return serializeDate(x)
+        default:
+            return x.getBytes()
+    }
+}
+/*
+CONTEST
+async function getInputFromResolve(utils, client, peer, peerType) {
+    switch (peerType) {
+        case 'InputPeer':
+            return utils.getInputPeer(await client.getInputEntity(peer))
+        case 'InputChannel':
+            return utils.getInputChannel(await client.getInputEntity(peer))
+        case 'InputUser':
+            return utils.getInputUser(await client.getInputEntity(peer))
+        case 'InputDialogPeer':
+            return await client._getInputDialog(peer)
+        case 'InputNotifyPeer':
+            return await client._getInputNotify(peer)
+        case 'InputMedia':
+            return utils.getInputMedia(peer)
+        case 'InputPhoto':
+            return utils.getInputPhoto(peer)
+        case 'InputMessage':
+            return utils.getInputMessage(peer)
+        case 'InputDocument':
+            return utils.getInputDocument(peer)
+        case 'InputChatPhoto':
+            return utils.getInputChatPhoto(peer)
+        case 'chatId,int' :
+            return await client.getPeerId(peer, false)
+        default:
+            throw new Error('unsupported peer type : ' + peerType)
+    }
+}
+*/
+function getArgFromReader(reader, arg) {
+    if (arg.isVector) {
+        if (arg.useVectorId) {
+            reader.readInt()
+        }
+        const temp = []
+        const len = reader.readInt()
+        arg.isVector = false
+        for (let i = 0; i < len; i++) {
+            temp.push(getArgFromReader(reader, arg))
+        }
+        arg.isVector = true
+        return temp
+    } else if (arg.flagIndicator) {
+        return reader.readInt()
+    } else {
+        switch (arg.type) {
+            case 'int':
+                return reader.readInt()
+            case 'long':
+                return reader.readLong()
+            case 'int128':
+                return reader.readLargeInt(128)
+            case 'int256':
+                return reader.readLargeInt(256)
+            case 'double':
+                return reader.readDouble()
+            case 'string':
+                return reader.tgReadString()
+            case 'Bool':
+                return reader.tgReadBool()
+            case 'true':
+                return true
+            case 'bytes':
+                return reader.tgReadBytes()
+            case 'date':
+                return reader.tgReadDate()
+            default:
+                if (!arg.skipConstructorId) {
+                    return reader.tgReadObject()
+                } else {
+                    return api.constructors[arg.type].fromReader(reader)
+                }
+        }
+    }
+}
+
+function createClasses(classesType, params) {
+    const classes = {}
+    for (const classParams of params) {
+        const { name, constructorId, subclassOfId, argsConfig, namespace, result } = classParams
+        const fullName = [namespace, name].join('.').replace(/^\./, '')
+
+        class VirtualClass {
+            static CONSTRUCTOR_ID = constructorId
+            static SUBCLASS_OF_ID = subclassOfId
+            static className = fullName
+            static classType = classesType
+
+            CONSTRUCTOR_ID = constructorId
+            SUBCLASS_OF_ID = subclassOfId
+            className = fullName
+            classType = classesType
+
+            constructor(args) {
+                args = args || {}
+                Object.keys(args)
+                  .forEach((argName) => {
+                      this[argName] = args[argName]
+                  })
+            }
+
+            static fromReader(reader) {
+
+                const args = {}
+
+                for (const argName in argsConfig) {
+                    if (argsConfig.hasOwnProperty(argName)) {
+                        const arg = argsConfig[argName]
+                        if (arg.isFlag) {
+                            if (arg.type === 'true') {
+                                args[argName] = Boolean(args['flags'] & 1 << arg.flagIndex)
+                                continue
+                            }
+                            if (args['flags'] & 1 << arg.flagIndex) {
+                                args[argName] = getArgFromReader(reader, arg)
+                            } else {
+                                args[argName] = null
+                            }
+                        } else {
+                            if (arg.flagIndicator) {
+                                arg.name = 'flags'
+                            }
+                            args[argName] = getArgFromReader(reader, arg)
+                        }
+                    }
+                }
+                return new VirtualClass(args)
+            }
+
+            getBytes() {
+                // The next is pseudo-code:
+                const idForBytes = this.CONSTRUCTOR_ID
+                const c = Buffer.alloc(4)
+                c.writeUInt32LE(idForBytes, 0)
+                const buffers = [c]
+                for (const arg in argsConfig) {
+                    if (argsConfig.hasOwnProperty(arg)) {
+                        if (argsConfig[arg].isFlag) {
+                          if (this[arg]===false || this[arg]===null || this[arg]===undefined || argsConfig[arg].type==='true'){
+                              continue
+                          }
+                        }
+                        if (argsConfig[arg].isVector) {
+                            if (argsConfig[arg].useVectorId) {
+                                buffers.push(Buffer.from('15c4b51c', 'hex'))
+                            }
+                            const l = Buffer.alloc(4)
+                            l.writeInt32LE(this[arg].length, 0)
+                            buffers.push(l, Buffer.concat(this[arg].map(x => argToBytes(x, argsConfig[arg].type))))
+                        } else if (argsConfig[arg].flagIndicator) {
+                            if (!Object.values(argsConfig)
+                              .some((f) => f.isFlag)) {
+                                buffers.push(Buffer.alloc(4))
+                            } else {
+                                let flagCalculate = 0
+                                for (const f in argsConfig) {
+                                    if (argsConfig[f].isFlag) {
+                                        if (!this[f]) {
+                                            flagCalculate |= 0
+                                        } else {
+                                            flagCalculate |= 1 << argsConfig[f].flagIndex
+                                        }
+                                    }
+                                }
+                                const f = Buffer.alloc(4)
+                                f.writeUInt32LE(flagCalculate, 0)
+                                buffers.push(f)
+                            }
+                        } else {
+                            buffers.push(argToBytes(this[arg], argsConfig[arg].type))
+
+                            if (this[arg] && typeof this[arg].getBytes === 'function') {
+                                let boxed = (argsConfig[arg].type.charAt(argsConfig[arg].type.indexOf('.') + 1))
+                                boxed = boxed === boxed.toUpperCase()
+                                if (!boxed) {
+                                    buffers.shift()
+                                }
+                            }
+                        }
+                    }
+
+                }
+                return Buffer.concat(buffers)
+            }
+
+            readResult(reader) {
+                if (classesType !== 'request') {
+                    throw new Error('`readResult()` called for non-request instance')
+                }
+
+                const m = result.match(/Vector<(int|long)>/)
+                if (m) {
+                    reader.readInt()
+                    let temp = []
+                    let len = reader.readInt()
+                    if (m[1] === 'int') {
+                        for (let i = 0; i < len; i++) {
+                            temp.push(reader.readInt())
+                        }
+                    } else {
+                        for (let i = 0; i < len; i++) {
+                            temp.push(reader.readLong())
+                        }
+                    }
+                    return temp
+                } else {
+                    return reader.tgReadObject()
+                }
+            }
+
+            /*CONTEST
+            async resolve(client, utils) {
+
+                if (classesType !== 'request') {
+                    throw new Error('`resolve()` called for non-request instance')
+                }
+
+                for (const arg in argsConfig) {
+                    if (argsConfig.hasOwnProperty(arg)) {
+                        if (!AUTO_CASTS.has(argsConfig[arg].type)) {
+                            if (!NAMED_AUTO_CASTS.has(`${argsConfig[arg].name},${argsConfig[arg].type}`)) {
+                                continue
+                            }
+                        }
+                        if (argsConfig[arg].isFlag) {
+                            if (!this[arg]) {
+                                continue
+                            }
+                        }
+                        if (argsConfig[arg].isVector) {
+                            const temp = []
+                            for (const x of this[arg]) {
+                                temp.push(await getInputFromResolve(utils, client, x, argsConfig[arg].type))
+                            }
+                            this[arg] = temp
+                        } else {
+                            this[arg] = await getInputFromResolve(utils, client, this[arg], argsConfig[arg].type)
+                        }
+                    }
+                }
+            }*/
+        }
+
+        if (namespace) {
+            if (!classes[namespace]) {
+                classes[namespace] = {}
+            }
+            classes[namespace][name] = VirtualClass
+
+        } else {
+            classes[name] = VirtualClass
+        }
+    }
+
+    return classes
+}
+
+module.exports = buildApiFromTlSchema()

+ 24 - 12
gramjs/tl/core/GZIPPacked.js

@@ -1,20 +1,20 @@
-const { TLObject } = require('../tlobject')
-const struct = require('python-struct')
-const { ungzip } = require('node-gzip')
-const { gzip } = require('node-gzip')
+const { serializeBytes } = require('../index')
+const { inflate } = require('pako/dist/pako_inflate')
+//CONTEST const { deflate } = require('pako/dist/pako_deflate')
 
-class GZIPPacked extends TLObject {
-    static CONSTRUCTOR_ID = 0x3072cfa1;
+class GZIPPacked {
+    static CONSTRUCTOR_ID = 0x3072cfa1
+    static classType = 'constructor'
 
     constructor(data) {
-        super()
         this.data = data
         this.CONSTRUCTOR_ID = 0x3072cfa1
+        this.classType = 'constructor'
     }
 
     static async gzipIfSmaller(contentRelated, data) {
         if (contentRelated && data.length > 512) {
-            const gzipped = await new GZIPPacked(data).toBytes()
+            const gzipped = await (new GZIPPacked(data)).toBytes()
             if (gzipped.length < data.length) {
                 return gzipped
             }
@@ -22,10 +22,22 @@ class GZIPPacked extends TLObject {
         return data
     }
 
+    static gzip(input) {
+        return Buffer.from(input)
+        // TODO this usually makes it faster for large requests
+        //return Buffer.from(deflate(input, { level: 9, gzip: true }))
+    }
+
+    static ungzip(input) {
+        return Buffer.from(inflate(input))
+    }
+
     async toBytes() {
+        const g = Buffer.alloc(4)
+        g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0)
         return Buffer.concat([
-            struct.pack('<I', GZIPPacked.CONSTRUCTOR_ID),
-            TLObject.serializeBytes(await gzip(this.data)),
+            g,
+            serializeBytes(await GZIPPacked.gzip(this.data)),
         ])
     }
 
@@ -34,11 +46,11 @@ class GZIPPacked extends TLObject {
         if (constructor !== GZIPPacked.CONSTRUCTOR_ID) {
             throw new Error('not equal')
         }
-        return await gzip(reader.tgReadBytes())
+        return await GZIPPacked.gzip(reader.tgReadBytes())
     }
 
     static async fromReader(reader) {
-        return new GZIPPacked(await ungzip(reader.tgReadBytes()))
+        return new GZIPPacked(await GZIPPacked.ungzip(reader.tgReadBytes()))
     }
 }
 

+ 4 - 4
gramjs/tl/core/MessageContainer.js

@@ -1,9 +1,8 @@
-const { TLObject } = require('../tlobject')
 const TLMessage = require('./TLMessage')
 
-class MessageContainer extends TLObject {
+class MessageContainer {
     static CONSTRUCTOR_ID = 0x73f1f8dc;
-
+    static classType = "constructor"
     // Maximum size in bytes for the inner payload of the container.
     // Telegram will close the connection if the payload is bigger.
     // The overhead of the container itself is subtracted.
@@ -20,9 +19,10 @@ class MessageContainer extends TLObject {
     static MAXIMUM_LENGTH = 100;
 
     constructor(messages) {
-        super()
+
         this.CONSTRUCTOR_ID = 0x73f1f8dc
         this.messages = messages
+        this.classType = "constructor"
     }
 
     static async fromReader(reader) {

+ 4 - 4
gramjs/tl/core/RPCResult.js

@@ -1,16 +1,16 @@
-const { TLObject } = require('../tlobject')
-const { RpcError } = require('../types')
+const { RpcError } = require('../index').constructors
 const GZIPPacked = require('./GZIPPacked')
 
-class RPCResult extends TLObject {
+class RPCResult {
     static CONSTRUCTOR_ID = 0xf35c6d01;
+    static classType = "constructor"
 
     constructor(reqMsgId, body, error) {
-        super()
         this.CONSTRUCTOR_ID = 0xf35c6d01
         this.reqMsgId = reqMsgId
         this.body = body
         this.error = error
+        this.classType = "constructor"
     }
 
     static async fromReader(reader) {

+ 3 - 3
gramjs/tl/core/TLMessage.js

@@ -1,13 +1,13 @@
-const { TLObject } = require('../tlobject')
 
-class TLMessage extends TLObject {
+class TLMessage {
     static SIZE_OVERHEAD = 12;
+    static classType = "constructor"
 
     constructor(msgId, seqNo, obj) {
-        super()
         this.msgId = msgId
         this.seqNo = seqNo
         this.obj = obj
+        this.classType = "constructor"
     }
 }
 

+ 326 - 0
gramjs/tl/generationHelpers.js

@@ -0,0 +1,326 @@
+const { crc32 } = require('../Helpers')
+const snakeToCamelCase = (name) => {
+    const result = name.replace(/(?:^|_)([a-z])/g, (_, g) => g.toUpperCase())
+    return result.replace(/_/g, '')
+}
+const variableSnakeToCamelCase = (str) => str.replace(
+    /([-_][a-z])/g,
+    (group) => group.toUpperCase()
+        .replace('-', '')
+        .replace('_', '')
+)
+
+const CORE_TYPES = new Set([
+    0xbc799737, // boolFalse#bc799737 = Bool;
+    0x997275b5, // boolTrue#997275b5 = Bool;
+    0x3fedd339, // true#3fedd339 = True;
+    0xc4b9f9bb, // error#c4b9f9bb code:int text:string = Error;
+    0x56730bcc // null#56730bcc = Null;
+])
+const AUTH_KEY_TYPES = new Set([
+    0x05162463, // resPQ,
+    0x83c95aec, // p_q_inner_data
+    0xa9f55f95, // p_q_inner_data_dc
+    0x3c6a84d4, // p_q_inner_data_temp
+    0x56fddf88, // p_q_inner_data_temp_dc
+    0xd0e8075c, // server_DH_params_ok
+    0xb5890dba, // server_DH_inner_data
+    0x6643b654, // client_DH_inner_data
+    0xd712e4be, // req_DH_params
+    0xf5045f1f, // set_client_DH_params
+    0x3072cfa1 // gzip_packed
+])
+
+
+const fromLine = (line, isFunction) => {
+    const match = line.match(/([\w.]+)(?:#([0-9a-fA-F]+))?(?:\s{?\w+:[\w\d<>#.?!]+}?)*\s=\s([\w\d<>#.?]+);$/)
+    if (!match) {
+        // Probably "vector#1cb5c415 {t:Type} # [ t ] = Vector t;"
+        throw new Error(`Cannot parse TLObject ${line}`)
+    }
+
+    const argsMatch = findAll(/({)?(\w+):([\w\d<>#.?!]+)}?/, line)
+    const currentConfig = {
+        name: match[1],
+        constructorId: parseInt(match[2], 16),
+        argsConfig: {},
+        subclassOfId: crc32(match[3]),
+        result: match[3],
+        isFunction: isFunction,
+        namespace: null
+    }
+    if (!currentConfig.constructorId) {
+
+        let hexId = ''
+        let args
+
+        if (Object.values(currentConfig.argsConfig).length) {
+            args = ` ${Object.keys(currentConfig.argsConfig).map((arg) => arg.toString()).join(' ')}`
+        } else {
+            args = ''
+        }
+
+        const representation = `${currentConfig.name}${hexId}${args} = ${currentConfig.result}`
+            .replace(/(:|\?)bytes /g, '$1string ')
+            .replace(/</g, ' ')
+            .replace(/>|{|}/g, '')
+            .replace(/ \w+:flags\.\d+\?true/g, '')
+
+        if (currentConfig.name === 'inputMediaInvoice') {
+            // eslint-disable-next-line no-empty
+            if (currentConfig.name === 'inputMediaInvoice') {
+            }
+        }
+
+        currentConfig.constructorId = crc32(Buffer.from(representation, 'utf8'))
+    }
+    for (const [brace, name, argType] of argsMatch) {
+        if (brace === undefined) {
+            currentConfig.argsConfig[variableSnakeToCamelCase(name)] = buildArgConfig(name, argType)
+        }
+    }
+    if (currentConfig.name.includes('.')) {
+        [currentConfig.namespace, currentConfig.name] = currentConfig.name.split(/\.(.+)/)
+    }
+    currentConfig.name = snakeToCamelCase(currentConfig.name)
+    /*
+    for (const arg in currentConfig.argsConfig){
+      if (currentConfig.argsConfig.hasOwnProperty(arg)){
+        if (currentConfig.argsConfig[arg].flagIndicator){
+          delete  currentConfig.argsConfig[arg]
+        }
+      }
+    }*/
+    return currentConfig
+}
+
+function buildArgConfig(name, argType) {
+    name = name === 'self' ? 'is_self' : name
+    // Default values
+    const currentConfig = {
+        isVector: false,
+        isFlag: false,
+        skipConstructorId: false,
+        flagIndex: -1,
+        flagIndicator: true,
+        type: null,
+        useVectorId: null
+    }
+
+    // Special case: some types can be inferred, which makes it
+    // less annoying to type. Currently the only type that can
+    // be inferred is if the name is 'random_id', to which a
+    // random ID will be assigned if left as None (the default)
+    let canBeInferred = name === 'random_id'
+
+    // The type can be an indicator that other arguments will be flags
+    if (argType !== '#') {
+        currentConfig.flagIndicator = false
+        // Strip the exclamation mark always to have only the name
+        currentConfig.type = argType.replace(/^!+/, '')
+
+        // The type may be a flag (flags.IDX?REAL_TYPE)
+        // Note that 'flags' is NOT the flags name; this
+        // is determined by a previous argument
+        // However, we assume that the argument will always be called 'flags'
+        // @ts-ignore
+        const flagMatch = currentConfig.type.match(/flags.(\d+)\?([\w<>.]+)/)
+
+        if (flagMatch) {
+            currentConfig.isFlag = true
+            currentConfig.flagIndex = Number(flagMatch[1]);
+            // Update the type to match the exact type, not the "flagged" one
+            [, , currentConfig.type] = flagMatch
+        }
+
+        // Then check if the type is a Vector<REAL_TYPE>
+        // @ts-ignore
+        const vectorMatch = currentConfig.type.match(/[Vv]ector<([\w\d.]+)>/)
+
+        if (vectorMatch) {
+            currentConfig.isVector = true
+
+            // If the type's first letter is not uppercase, then
+            // it is a constructor and we use (read/write) its ID.
+            // @ts-ignore
+            currentConfig.useVectorId = currentConfig.type.charAt(0) === 'V';
+
+            // Update the type to match the one inside the vector
+            [, currentConfig.type] = vectorMatch
+        }
+
+        // See use_vector_id. An example of such case is ipPort in
+        // help.configSpecial
+        // @ts-ignore
+        if (/^[a-z]$/.test(currentConfig.type.split('.')
+            .pop()
+            .charAt(0)
+        )
+        ) {
+            currentConfig.skipConstructorId = true
+        }
+
+        // The name may contain "date" in it, if this is the case and
+        // the type is "int", we can safely assume that this should be
+        // treated as a "date" object. Note that this is not a valid
+        // Telegram object, but it's easier to work with
+        // if (
+        //     this.type === 'int' &&
+        //     (/(\b|_)([dr]ate|until|since)(\b|_)/.test(name) ||
+        //         ['expires', 'expires_at', 'was_online'].includes(name))
+        // ) {
+        //     this.type = 'date';
+        // }
+    }
+    return currentConfig
+}
+
+
+const parseTl = function* (content, layer, methods = [], ignoreIds = CORE_TYPES) {
+    const methodInfo = (methods || []).reduce((o, m) => ({ ...o, [m.name]: m }), {})
+    const objAll = []
+    const objByName = {}
+    const objByType = {}
+
+    const file = content
+
+    let isFunction = false
+
+    for (let line of file.split('\n')) {
+        const commentIndex = line.indexOf('//')
+
+        if (commentIndex !== -1) {
+            line = line.slice(0, commentIndex)
+        }
+
+        line = line.trim()
+
+        if (!line) {
+            continue
+        }
+
+        const match = line.match(/---(\w+)---/)
+
+        if (match) {
+            const [, followingTypes] = match
+            isFunction = followingTypes === 'functions'
+            continue
+        }
+
+        try {
+            const result = fromLine(line, isFunction)
+
+            if (ignoreIds.has(result.constructorId)) {
+                continue
+            }
+
+            objAll.push(result)
+
+            if (!result.isFunction) {
+                if (!objByType[result.result]) {
+                    objByType[result.result] = []
+                }
+
+                objByName[result.name] = result
+                objByType[result.result].push(result)
+            }
+        } catch (e) {
+            if (!e.toString().includes('vector#1cb5c415')) {
+                throw e
+            }
+        }
+    }
+
+    // Once all objects have been parsed, replace the
+    // string type from the arguments with references
+    for (const obj of objAll) {
+        //console.log(obj)
+        if (AUTH_KEY_TYPES.has(obj.constructorId)) {
+            for (const arg in obj.argsConfig) {
+                if (obj.argsConfig[arg].type === 'string') {
+                    obj.argsConfig[arg].type = 'bytes'
+                }
+            }
+        }
+    }
+
+    for (const obj of objAll) {
+        yield obj
+    }
+
+}
+
+const findAll = (regex, str, matches = []) => {
+    if (!regex.flags.includes(`g`)) {
+        regex = new RegExp(regex.source, `g`)
+    }
+
+    const res = regex.exec(str)
+
+    if (res) {
+        matches.push(res.slice(1))
+        findAll(regex, str, matches)
+    }
+
+    return matches
+}
+
+function serializeBytes(data) {
+    if (!(data instanceof Buffer)) {
+        if (typeof data == 'string') {
+            data = Buffer.from(data)
+        } else {
+            throw Error(`Bytes or str expected, not ${data.constructor.name}`)
+        }
+    }
+    const r = []
+    let padding
+    if (data.length < 254) {
+        padding = (data.length + 1) % 4
+        if (padding !== 0) {
+            padding = 4 - padding
+        }
+        r.push(Buffer.from([data.length]))
+        r.push(data)
+    } else {
+        padding = data.length % 4
+        if (padding !== 0) {
+            padding = 4 - padding
+        }
+        r.push(Buffer.from([254, data.length % 256, (data.length >> 8) % 256, (data.length >> 16) % 256]))
+        r.push(data)
+    }
+    r.push(Buffer.alloc(padding)
+        .fill(0))
+
+    return Buffer.concat(r)
+
+}
+
+function serializeDate(dt) {
+    if (!dt) {
+        return Buffer.alloc(4)
+            .fill(0)
+    }
+    if (dt instanceof Date) {
+        dt = Math.floor((Date.now() - dt.getTime()) / 1000)
+    }
+    if (typeof dt == 'number') {
+        const t = Buffer.alloc(4)
+        t.writeInt32LE(dt, 0)
+        return t
+    }
+    throw Error(`Cannot interpret "${dt}" as a date`)
+}
+
+module.exports = {
+    findAll,
+    parseTl,
+    buildArgConfig,
+    fromLine,
+    CORE_TYPES,
+    serializeDate,
+    serializeBytes,
+    snakeToCamelCase,
+    variableSnakeToCamelCase
+}

+ 8 - 9
gramjs/tl/index.js

@@ -1,13 +1,12 @@
-const types = require('./types')
-const functions = require('./functions')
-const custom = require('./custom')
+const api = require('./api')
+const { serializeBytes, serializeDate } = require('./generationHelpers')
 const patched = null
-const { TLObject, TLRequest } = require('./tlobject')
+
 module.exports = {
-    types,
-    functions,
-    custom,
+    // TODO Refactor internal usages to always use `api`.
+    constructors: api,
+    requests: api,
     patched,
-    TLObject,
-    TLRequest,
+    serializeBytes,
+    serializeDate
 }

+ 1500 - 0
gramjs/tl/static/api.tl

@@ -0,0 +1,1500 @@
+///////////////////////////////
+/////////////////// Layer cons
+///////////////////////////////
+
+//invokeAfterMsg#cb9f372d msg_id:long query:!X = X;
+//invokeAfterMsgs#3dc4b4f0 msg_ids:Vector<long> query:!X = X;
+//invokeWithLayer1#53835315 query:!X = X;
+//invokeWithLayer2#289dd1f6 query:!X = X;
+//invokeWithLayer3#b7475268 query:!X = X;
+//invokeWithLayer4#dea0d430 query:!X = X;
+//invokeWithLayer5#417a57ae query:!X = X;
+//invokeWithLayer6#3a64d54d query:!X = X;
+//invokeWithLayer7#a5be56d3 query:!X = X;
+//invokeWithLayer8#e9abd9fd query:!X = X;
+//invokeWithLayer9#76715a63 query:!X = X;
+//invokeWithLayer10#39620c41 query:!X = X;
+//invokeWithLayer11#a6b88fdf query:!X = X;
+//invokeWithLayer12#dda60d3c query:!X = X;
+//invokeWithLayer13#427c8ea2 query:!X = X;
+//invokeWithLayer14#2b9b08fa query:!X = X;
+//invokeWithLayer15#b4418b64 query:!X = X;
+//invokeWithLayer16#cf5f0987 query:!X = X;
+//invokeWithLayer17#50858a19 query:!X = X;
+//invokeWithLayer18#1c900537 query:!X = X;
+//invokeWithLayer#da9b0d0d layer:int query:!X = X; // after 18 layer
+
+///////////////////////////////
+///////// Main application API
+///////////////////////////////
+
+boolFalse#bc799737 = Bool;
+boolTrue#997275b5 = Bool;
+
+true#3fedd339 = True;
+
+vector#1cb5c415 {t:Type} # [ t ] = Vector t;
+
+error#c4b9f9bb code:int text:string = Error;
+
+null#56730bcc = Null;
+
+inputPeerEmpty#7f3b18ea = InputPeer;
+inputPeerSelf#7da07ec9 = InputPeer;
+inputPeerChat#179be863 chat_id:int = InputPeer;
+inputPeerUser#7b8e7de6 user_id:int access_hash:long = InputPeer;
+inputPeerChannel#20adaef8 channel_id:int access_hash:long = InputPeer;
+inputPeerUserFromMessage#17bae2e6 peer:InputPeer msg_id:int user_id:int = InputPeer;
+inputPeerChannelFromMessage#9c95f7bb peer:InputPeer msg_id:int channel_id:int = InputPeer;
+
+inputUserEmpty#b98886cf = InputUser;
+inputUserSelf#f7c1b13f = InputUser;
+inputUser#d8292816 user_id:int access_hash:long = InputUser;
+inputUserFromMessage#2d117597 peer:InputPeer msg_id:int user_id:int = InputUser;
+
+inputPhoneContact#f392b7f4 client_id:long phone:string first_name:string last_name:string = InputContact;
+
+inputFile#f52ff27f id:long parts:int name:string md5_checksum:string = InputFile;
+inputFileBig#fa4f0bb5 id:long parts:int name:string = InputFile;
+
+inputMediaEmpty#9664f57f = InputMedia;
+inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
+inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia;
+inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
+inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia;
+inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
+inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia;
+inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia;
+inputMediaGifExternal#4843b0fd url:string q:string = InputMedia;
+inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
+inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
+inputMediaGame#d33f43f3 id:InputGame = InputMedia;
+inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
+inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
+inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> solution:flags.1?string solution_entities:flags.1?Vector<MessageEntity> = InputMedia;
+inputMediaDice#e66fbf7b emoticon:string = InputMedia;
+
+inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
+inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
+inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto;
+
+inputGeoPointEmpty#e4c123d6 = InputGeoPoint;
+inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint;
+
+inputPhotoEmpty#1cd7bf0d = InputPhoto;
+inputPhoto#3bb3b94a id:long access_hash:long file_reference:bytes = InputPhoto;
+
+inputFileLocation#dfdaabe1 volume_id:long local_id:int secret:long file_reference:bytes = InputFileLocation;
+inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation;
+inputDocumentFileLocation#bad07584 id:long access_hash:long file_reference:bytes thumb_size:string = InputFileLocation;
+inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation;
+inputTakeoutFileLocation#29be5899 = InputFileLocation;
+inputPhotoFileLocation#40181ffe id:long access_hash:long file_reference:bytes thumb_size:string = InputFileLocation;
+inputPhotoLegacyFileLocation#d83466f3 id:long access_hash:long file_reference:bytes volume_id:long local_id:int secret:long = InputFileLocation;
+inputPeerPhotoFileLocation#27d69997 flags:# big:flags.0?true peer:InputPeer volume_id:long local_id:int = InputFileLocation;
+inputStickerSetThumb#dbaeae9 stickerset:InputStickerSet volume_id:long local_id:int = InputFileLocation;
+
+peerUser#9db1bc6d user_id:int = Peer;
+peerChat#bad0e5bb chat_id:int = Peer;
+peerChannel#bddde532 channel_id:int = Peer;
+
+storage.fileUnknown#aa963b05 = storage.FileType;
+storage.filePartial#40bc6f52 = storage.FileType;
+storage.fileJpeg#7efe0e = storage.FileType;
+storage.fileGif#cae1aadf = storage.FileType;
+storage.filePng#a4f63c0 = storage.FileType;
+storage.filePdf#ae1e508d = storage.FileType;
+storage.fileMp3#528a0677 = storage.FileType;
+storage.fileMov#4b09ebbc = storage.FileType;
+storage.fileMp4#b3cea0e4 = storage.FileType;
+storage.fileWebp#1081464c = storage.FileType;
+
+userEmpty#200250ba id:int = User;
+user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
+
+userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
+userProfilePhoto#ecd75d8c photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto;
+
+userStatusEmpty#9d05049 = UserStatus;
+userStatusOnline#edb93949 expires:int = UserStatus;
+userStatusOffline#8c703f was_online:int = UserStatus;
+userStatusRecently#e26f42f1 = UserStatus;
+userStatusLastWeek#7bf09fc = UserStatus;
+userStatusLastMonth#77ebc742 = UserStatus;
+
+chatEmpty#9ba2d800 id:int = Chat;
+chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
+chatForbidden#7328bdb id:int title:string = Chat;
+channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
+channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
+
+chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull;
+channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull;
+
+chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
+chatParticipantCreator#da13538a user_id:int = ChatParticipant;
+chatParticipantAdmin#e2d6e436 user_id:int inviter_id:int date:int = ChatParticipant;
+
+chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0?ChatParticipant = ChatParticipants;
+chatParticipants#3f460fed chat_id:int participants:Vector<ChatParticipant> version:int = ChatParticipants;
+
+chatPhotoEmpty#37c1011c = ChatPhoto;
+chatPhoto#475cdbd5 photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto;
+
+messageEmpty#83e5de54 id:int = Message;
+message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;
+messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message;
+
+messageMediaEmpty#3ded6320 = MessageMedia;
+messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia;
+messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia;
+messageMediaContact#cbf24940 phone_number:string first_name:string last_name:string vcard:string user_id:int = MessageMedia;
+messageMediaUnsupported#9f84f49e = MessageMedia;
+messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia;
+messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
+messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia;
+messageMediaGame#fdb19008 game:Game = MessageMedia;
+messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
+messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia;
+messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia;
+messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia;
+
+messageActionEmpty#b6aef7b0 = MessageAction;
+messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction;
+messageActionChatEditTitle#b5a1ce5a title:string = MessageAction;
+messageActionChatEditPhoto#7fcb13a8 photo:Photo = MessageAction;
+messageActionChatDeletePhoto#95e3fbef = MessageAction;
+messageActionChatAddUser#488a7337 users:Vector<int> = MessageAction;
+messageActionChatDeleteUser#b2ae9b0c user_id:int = MessageAction;
+messageActionChatJoinedByLink#f89cf5e8 inviter_id:int = MessageAction;
+messageActionChannelCreate#95d2ac92 title:string = MessageAction;
+messageActionChatMigrateTo#51bdb021 channel_id:int = MessageAction;
+messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageAction;
+messageActionPinMessage#94bd38ed = MessageAction;
+messageActionHistoryClear#9fbab604 = MessageAction;
+messageActionGameScore#92a72876 game_id:long score:int = MessageAction;
+messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction;
+messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction;
+messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction;
+messageActionScreenshotTaken#4792929b = MessageAction;
+messageActionCustomAction#fae69f56 message:string = MessageAction;
+messageActionBotAllowed#abe9affe domain:string = MessageAction;
+messageActionSecureValuesSentMe#1b287353 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted = MessageAction;
+messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = MessageAction;
+messageActionContactSignUp#f3f25f76 = MessageAction;
+
+dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
+dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
+
+photoEmpty#2331b22d id:long = Photo;
+photo#d07504a5 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector<PhotoSize> dc_id:int = Photo;
+
+photoSizeEmpty#e17e23c type:string = PhotoSize;
+photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
+photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
+photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize;
+
+geoPointEmpty#1117dd5f = GeoPoint;
+geoPoint#296f104 long:double lat:double access_hash:long = GeoPoint;
+
+auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode;
+
+auth.authorization#cd050916 flags:# tmp_sessions:flags.0?int user:User = auth.Authorization;
+auth.authorizationSignUpRequired#44747e9a flags:# terms_of_service:flags.0?help.TermsOfService = auth.Authorization;
+
+auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorization;
+
+inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer;
+inputNotifyUsers#193b4417 = InputNotifyPeer;
+inputNotifyChats#4a95e84e = InputNotifyPeer;
+inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer;
+
+inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings;
+
+peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
+
+peerSettings#818426cd flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true = PeerSettings;
+
+wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper;
+wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
+
+inputReportReasonSpam#58dbcab8 = ReportReason;
+inputReportReasonViolence#1e22c78d = ReportReason;
+inputReportReasonPornography#2e59d922 = ReportReason;
+inputReportReasonChildAbuse#adf44ee3 = ReportReason;
+inputReportReasonOther#e1746d0a text:string = ReportReason;
+inputReportReasonCopyright#9b89f93a = ReportReason;
+inputReportReasonGeoIrrelevant#dbd4feed = ReportReason;
+
+userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull;
+
+contact#f911c994 user_id:int mutual:Bool = Contact;
+
+importedContact#d0028438 user_id:int client_id:long = ImportedContact;
+
+contactBlocked#561bc879 user_id:int date:int = ContactBlocked;
+
+contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus;
+
+contacts.contactsNotModified#b74ba9d2 = contacts.Contacts;
+contacts.contacts#eae87e42 contacts:Vector<Contact> saved_count:int users:Vector<User> = contacts.Contacts;
+
+contacts.importedContacts#77d01c3b imported:Vector<ImportedContact> popular_invites:Vector<PopularContact> retry_contacts:Vector<long> users:Vector<User> = contacts.ImportedContacts;
+
+contacts.blocked#1c138d15 blocked:Vector<ContactBlocked> users:Vector<User> = contacts.Blocked;
+contacts.blockedSlice#900802a1 count:int blocked:Vector<ContactBlocked> users:Vector<User> = contacts.Blocked;
+
+messages.dialogs#15ba6c40 dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Dialogs;
+messages.dialogsSlice#71e094f3 count:int dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Dialogs;
+messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs;
+
+messages.messages#8c718e87 messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
+messages.messagesSlice#c8edce1e flags:# inexact:flags.1?true count:int next_rate:flags.0?int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
+messages.channelMessages#99262e37 flags:# inexact:flags.1?true pts:int count:int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
+messages.messagesNotModified#74535f21 count:int = messages.Messages;
+
+messages.chats#64ff9fd5 chats:Vector<Chat> = messages.Chats;
+messages.chatsSlice#9cd81144 count:int chats:Vector<Chat> = messages.Chats;
+
+messages.chatFull#e5d7d19c full_chat:ChatFull chats:Vector<Chat> users:Vector<User> = messages.ChatFull;
+
+messages.affectedHistory#b45c69d1 pts:int pts_count:int offset:int = messages.AffectedHistory;
+
+inputMessagesFilterEmpty#57e2f66c = MessagesFilter;
+inputMessagesFilterPhotos#9609a51c = MessagesFilter;
+inputMessagesFilterVideo#9fc00e65 = MessagesFilter;
+inputMessagesFilterPhotoVideo#56e9f0e4 = MessagesFilter;
+inputMessagesFilterDocument#9eddf188 = MessagesFilter;
+inputMessagesFilterUrl#7ef0dd87 = MessagesFilter;
+inputMessagesFilterGif#ffc86587 = MessagesFilter;
+inputMessagesFilterVoice#50f5c392 = MessagesFilter;
+inputMessagesFilterMusic#3751b49e = MessagesFilter;
+inputMessagesFilterChatPhotos#3a20ecb8 = MessagesFilter;
+inputMessagesFilterPhoneCalls#80c99768 flags:# missed:flags.0?true = MessagesFilter;
+inputMessagesFilterRoundVoice#7a7c17a4 = MessagesFilter;
+inputMessagesFilterRoundVideo#b549da53 = MessagesFilter;
+inputMessagesFilterMyMentions#c1f8e69a = MessagesFilter;
+inputMessagesFilterGeo#e7026d0d = MessagesFilter;
+inputMessagesFilterContacts#e062db83 = MessagesFilter;
+
+updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
+updateMessageID#4e90bfd6 id:int random_id:long = Update;
+updateDeleteMessages#a20db0e5 messages:Vector<int> pts:int pts_count:int = Update;
+updateUserTyping#5c486927 user_id:int action:SendMessageAction = Update;
+updateChatUserTyping#9a65ea1f chat_id:int user_id:int action:SendMessageAction = Update;
+updateChatParticipants#7761198 participants:ChatParticipants = Update;
+updateUserStatus#1bfbd823 user_id:int status:UserStatus = Update;
+updateUserName#a7332b73 user_id:int first_name:string last_name:string username:string = Update;
+updateUserPhoto#95313b0c user_id:int date:int photo:UserProfilePhoto previous:Bool = Update;
+updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update;
+updateEncryptedChatTyping#1710f156 chat_id:int = Update;
+updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update;
+updateEncryptedMessagesRead#38fe25b7 chat_id:int max_date:int date:int = Update;
+updateChatParticipantAdd#ea4b0e5c chat_id:int user_id:int inviter_id:int date:int version:int = Update;
+updateChatParticipantDelete#6e5f8c22 chat_id:int user_id:int version:int = Update;
+updateDcOptions#8e5e9873 dc_options:Vector<DcOption> = Update;
+updateUserBlocked#80ece81a user_id:int blocked:Bool = Update;
+updateNotifySettings#bec268ef peer:NotifyPeer notify_settings:PeerNotifySettings = Update;
+updateServiceNotification#ebe46819 flags:# popup:flags.0?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector<MessageEntity> = Update;
+updatePrivacy#ee3b272a key:PrivacyKey rules:Vector<PrivacyRule> = Update;
+updateUserPhone#12b9417b user_id:int phone:string = Update;
+updateReadHistoryInbox#9c974fdf flags:# folder_id:flags.0?int peer:Peer max_id:int still_unread_count:int pts:int pts_count:int = Update;
+updateReadHistoryOutbox#2f2f21bf peer:Peer max_id:int pts:int pts_count:int = Update;
+updateWebPage#7f891213 webpage:WebPage pts:int pts_count:int = Update;
+updateReadMessagesContents#68c13933 messages:Vector<int> pts:int pts_count:int = Update;
+updateChannelTooLong#eb0467fb flags:# channel_id:int pts:flags.0?int = Update;
+updateChannel#b6d45656 channel_id:int = Update;
+updateNewChannelMessage#62ba04d9 message:Message pts:int pts_count:int = Update;
+updateReadChannelInbox#330b5424 flags:# folder_id:flags.0?int channel_id:int max_id:int still_unread_count:int pts:int = Update;
+updateDeleteChannelMessages#c37521c9 channel_id:int messages:Vector<int> pts:int pts_count:int = Update;
+updateChannelMessageViews#98a12b4b channel_id:int id:int views:int = Update;
+updateChatParticipantAdmin#b6901959 chat_id:int user_id:int is_admin:Bool version:int = Update;
+updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update;
+updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector<long> = Update;
+updateStickerSets#43ae3dec = Update;
+updateSavedGifs#9375341e = Update;
+updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint offset:string = Update;
+updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update;
+updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update;
+updateChannelPinnedMessage#98592475 channel_id:int id:int = Update;
+updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
+updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update;
+updateInlineBotCallbackQuery#f9d27a5a flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
+updateReadChannelOutbox#25d6c9c7 channel_id:int max_id:int = Update;
+updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update;
+updateReadFeaturedStickers#571d2742 = Update;
+updateRecentStickers#9a422c20 = Update;
+updateConfig#a229dd06 = Update;
+updatePtsChanged#3354678f = Update;
+updateChannelWebPage#40771900 channel_id:int webpage:WebPage pts:int pts_count:int = Update;
+updateDialogPinned#6e6fe51c flags:# pinned:flags.0?true folder_id:flags.1?int peer:DialogPeer = Update;
+updatePinnedDialogs#fa0f3ca2 flags:# folder_id:flags.1?int order:flags.0?Vector<DialogPeer> = Update;
+updateBotWebhookJSON#8317c0c3 data:DataJSON = Update;
+updateBotWebhookJSONQuery#9b9240a6 query_id:long data:DataJSON timeout:int = Update;
+updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update;
+updateBotPrecheckoutQuery#5d2f3aa9 flags:# query_id:long user_id:int payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update;
+updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update;
+updateLangPackTooLong#46560264 lang_code:string = Update;
+updateLangPack#56022f4d difference:LangPackDifference = Update;
+updateFavedStickers#e511996d = Update;
+updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector<int> = Update;
+updateContactsReset#7084a7be = Update;
+updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update;
+updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update;
+updateUserPinnedMessage#4c43da18 user_id:int id:int = Update;
+updateChatPinnedMessage#e10db349 chat_id:int id:int version:int = Update;
+updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update;
+updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update;
+updateFolderPeers#19360dc0 folder_peers:Vector<FolderPeer> pts:int pts_count:int = Update;
+updatePeerSettings#6a7e7366 peer:Peer settings:PeerSettings = Update;
+updatePeerLocated#b4afcfb0 peers:Vector<PeerLocated> = Update;
+updateNewScheduledMessage#39a51dfb message:Message = Update;
+updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector<int> = Update;
+updateTheme#8216fba3 theme:Theme = Update;
+updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update;
+updateLoginToken#564fe691 = Update;
+updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> = Update;
+updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update;
+updateDialogFilterOrder#a5d72105 order:Vector<int> = Update;
+updateDialogFilters#3504914f = Update;
+
+updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
+
+updates.differenceEmpty#5d75a138 date:int seq:int = updates.Difference;
+updates.difference#f49ca0 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> state:updates.State = updates.Difference;
+updates.differenceSlice#a8fb1981 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> intermediate_state:updates.State = updates.Difference;
+updates.differenceTooLong#4afe8f6d pts:int = updates.Difference;
+
+updatesTooLong#e317af7e = Updates;
+updateShortMessage#914fbf11 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector<MessageEntity> = Updates;
+updateShortChatMessage#16812688 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector<MessageEntity> = Updates;
+updateShort#78d4dec1 update:Update date:int = Updates;
+updatesCombined#725b04c3 updates:Vector<Update> users:Vector<User> chats:Vector<Chat> date:int seq_start:int seq:int = Updates;
+updates#74ae4240 updates:Vector<Update> users:Vector<User> chats:Vector<Chat> date:int seq:int = Updates;
+updateShortSentMessage#11f1331c flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector<MessageEntity> = Updates;
+
+photos.photos#8dca6aa5 photos:Vector<Photo> users:Vector<User> = photos.Photos;
+photos.photosSlice#15051f54 count:int photos:Vector<Photo> users:Vector<User> = photos.Photos;
+
+photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
+
+upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
+upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector<FileHash> = upload.File;
+
+dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
+
+config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config;
+
+nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
+
+help.appUpdate#1da7158f flags:# can_not_skip:flags.0?true id:int version:string text:string entities:Vector<MessageEntity> document:flags.1?Document url:flags.2?string = help.AppUpdate;
+help.noAppUpdate#c45a6536 = help.AppUpdate;
+
+help.inviteText#18cb9f78 message:string = help.InviteText;
+
+encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat;
+encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat;
+encryptedChatRequested#c878527e id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat;
+encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat;
+encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat;
+
+inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat;
+
+encryptedFileEmpty#c21f497e = EncryptedFile;
+encryptedFile#4a70994c id:long access_hash:long size:int dc_id:int key_fingerprint:int = EncryptedFile;
+
+inputEncryptedFileEmpty#1837c364 = InputEncryptedFile;
+inputEncryptedFileUploaded#64bd0306 id:long parts:int md5_checksum:string key_fingerprint:int = InputEncryptedFile;
+inputEncryptedFile#5a17b5e5 id:long access_hash:long = InputEncryptedFile;
+inputEncryptedFileBigUploaded#2dc173c8 id:long parts:int key_fingerprint:int = InputEncryptedFile;
+
+encryptedMessage#ed18c118 random_id:long chat_id:int date:int bytes:bytes file:EncryptedFile = EncryptedMessage;
+encryptedMessageService#23734b06 random_id:long chat_id:int date:int bytes:bytes = EncryptedMessage;
+
+messages.dhConfigNotModified#c0e24635 random:bytes = messages.DhConfig;
+messages.dhConfig#2c221edd g:int p:bytes version:int random:bytes = messages.DhConfig;
+
+messages.sentEncryptedMessage#560f8935 date:int = messages.SentEncryptedMessage;
+messages.sentEncryptedFile#9493ff32 date:int file:EncryptedFile = messages.SentEncryptedMessage;
+
+inputDocumentEmpty#72f0eaae = InputDocument;
+inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument;
+
+documentEmpty#36f8c871 id:long = Document;
+document#9ba29cc1 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector<PhotoSize> dc_id:int attributes:Vector<DocumentAttribute> = Document;
+
+help.support#17c6b5f6 phone_number:string user:User = help.Support;
+
+notifyPeer#9fd40bd8 peer:Peer = NotifyPeer;
+notifyUsers#b4c83b4c = NotifyPeer;
+notifyChats#c007cec3 = NotifyPeer;
+notifyBroadcasts#d612e8ef = NotifyPeer;
+
+sendMessageTypingAction#16bf744e = SendMessageAction;
+sendMessageCancelAction#fd5ec8f5 = SendMessageAction;
+sendMessageRecordVideoAction#a187d66f = SendMessageAction;
+sendMessageUploadVideoAction#e9763aec progress:int = SendMessageAction;
+sendMessageRecordAudioAction#d52f73f7 = SendMessageAction;
+sendMessageUploadAudioAction#f351d7ab progress:int = SendMessageAction;
+sendMessageUploadPhotoAction#d1d34a26 progress:int = SendMessageAction;
+sendMessageUploadDocumentAction#aa0cd9e4 progress:int = SendMessageAction;
+sendMessageGeoLocationAction#176f8ba1 = SendMessageAction;
+sendMessageChooseContactAction#628cbc6f = SendMessageAction;
+sendMessageGamePlayAction#dd6a8f48 = SendMessageAction;
+sendMessageRecordRoundAction#88f27fbc = SendMessageAction;
+sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction;
+
+contacts.found#b3134d9d my_results:Vector<Peer> results:Vector<Peer> chats:Vector<Chat> users:Vector<User> = contacts.Found;
+
+inputPrivacyKeyStatusTimestamp#4f96cb18 = InputPrivacyKey;
+inputPrivacyKeyChatInvite#bdfb0426 = InputPrivacyKey;
+inputPrivacyKeyPhoneCall#fabadc5f = InputPrivacyKey;
+inputPrivacyKeyPhoneP2P#db9e70d2 = InputPrivacyKey;
+inputPrivacyKeyForwards#a4dd4c08 = InputPrivacyKey;
+inputPrivacyKeyProfilePhoto#5719bacc = InputPrivacyKey;
+inputPrivacyKeyPhoneNumber#352dafa = InputPrivacyKey;
+inputPrivacyKeyAddedByPhone#d1219bdd = InputPrivacyKey;
+
+privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey;
+privacyKeyChatInvite#500e6dfa = PrivacyKey;
+privacyKeyPhoneCall#3d662b7b = PrivacyKey;
+privacyKeyPhoneP2P#39491cc8 = PrivacyKey;
+privacyKeyForwards#69ec56a3 = PrivacyKey;
+privacyKeyProfilePhoto#96151fed = PrivacyKey;
+privacyKeyPhoneNumber#d19ae46d = PrivacyKey;
+privacyKeyAddedByPhone#42ffd42b = PrivacyKey;
+
+inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule;
+inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule;
+inputPrivacyValueAllowUsers#131cc67f users:Vector<InputUser> = InputPrivacyRule;
+inputPrivacyValueDisallowContacts#ba52007 = InputPrivacyRule;
+inputPrivacyValueDisallowAll#d66b66c9 = InputPrivacyRule;
+inputPrivacyValueDisallowUsers#90110467 users:Vector<InputUser> = InputPrivacyRule;
+inputPrivacyValueAllowChatParticipants#4c81c1ba chats:Vector<int> = InputPrivacyRule;
+inputPrivacyValueDisallowChatParticipants#d82363af chats:Vector<int> = InputPrivacyRule;
+
+privacyValueAllowContacts#fffe1bac = PrivacyRule;
+privacyValueAllowAll#65427b82 = PrivacyRule;
+privacyValueAllowUsers#4d5bbe0c users:Vector<int> = PrivacyRule;
+privacyValueDisallowContacts#f888fa1a = PrivacyRule;
+privacyValueDisallowAll#8b73e763 = PrivacyRule;
+privacyValueDisallowUsers#c7f49b7 users:Vector<int> = PrivacyRule;
+privacyValueAllowChatParticipants#18be796b chats:Vector<int> = PrivacyRule;
+privacyValueDisallowChatParticipants#acae0690 chats:Vector<int> = PrivacyRule;
+
+account.privacyRules#50a04e45 rules:Vector<PrivacyRule> chats:Vector<Chat> users:Vector<User> = account.PrivacyRules;
+
+accountDaysTTL#b8d0afdf days:int = AccountDaysTTL;
+
+documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute;
+documentAttributeAnimated#11b58939 = DocumentAttribute;
+documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute;
+documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_streaming:flags.1?true duration:int w:int h:int = DocumentAttribute;
+documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
+documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
+documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
+
+messages.stickersNotModified#f1749a22 = messages.Stickers;
+messages.stickers#e4599bbd hash:int stickers:Vector<Document> = messages.Stickers;
+
+stickerPack#12b299d4 emoticon:string documents:Vector<long> = StickerPack;
+
+messages.allStickersNotModified#e86602c3 = messages.AllStickers;
+messages.allStickers#edfd405f hash:int sets:Vector<StickerSet> = messages.AllStickers;
+
+messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMessages;
+
+webPageEmpty#eb1477e8 id:long = WebPage;
+webPagePending#c586da1c id:long date:int = WebPage;
+webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector<WebPageAttribute> = WebPage;
+webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage;
+
+authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization;
+
+account.authorizations#1250abde authorizations:Vector<Authorization> = account.Authorizations;
+
+account.password#ad2641f8 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes = account.Password;
+
+account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings;
+
+account.passwordInputSettings#c23727c9 flags:# new_algo:flags.0?PasswordKdfAlgo new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_settings:flags.2?SecureSecretSettings = account.PasswordInputSettings;
+
+auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
+
+receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage;
+
+chatInviteEmpty#69df3769 = ExportedChatInvite;
+chatInviteExported#fc2e05bc link:string = ExportedChatInvite;
+
+chatInviteAlready#5a686d7c chat:Chat = ChatInvite;
+chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
+
+inputStickerSetEmpty#ffb62b95 = InputStickerSet;
+inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet;
+inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
+inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
+inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
+
+stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet;
+
+messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
+
+botCommand#c27ac8c7 command:string description:string = BotCommand;
+
+botInfo#98e81d3a user_id:int description:string commands:Vector<BotCommand> = BotInfo;
+
+keyboardButton#a2fa4880 text:string = KeyboardButton;
+keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton;
+keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton;
+keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton;
+keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton;
+keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton;
+keyboardButtonGame#50f41ccf text:string = KeyboardButton;
+keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
+keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton;
+inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton;
+keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
+
+keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
+
+replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup;
+replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags.2?true = ReplyMarkup;
+replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> = ReplyMarkup;
+replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;
+
+messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity;
+messageEntityMention#fa04579d offset:int length:int = MessageEntity;
+messageEntityHashtag#6f635b0d offset:int length:int = MessageEntity;
+messageEntityBotCommand#6cef8ac7 offset:int length:int = MessageEntity;
+messageEntityUrl#6ed02538 offset:int length:int = MessageEntity;
+messageEntityEmail#64e475c2 offset:int length:int = MessageEntity;
+messageEntityBold#bd610bc9 offset:int length:int = MessageEntity;
+messageEntityItalic#826f8b60 offset:int length:int = MessageEntity;
+messageEntityCode#28a20571 offset:int length:int = MessageEntity;
+messageEntityPre#73924be0 offset:int length:int language:string = MessageEntity;
+messageEntityTextUrl#76a6d327 offset:int length:int url:string = MessageEntity;
+messageEntityMentionName#352dca58 offset:int length:int user_id:int = MessageEntity;
+inputMessageEntityMentionName#208e68c9 offset:int length:int user_id:InputUser = MessageEntity;
+messageEntityPhone#9b69e34b offset:int length:int = MessageEntity;
+messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity;
+messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity;
+messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity;
+messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity;
+messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity;
+
+inputChannelEmpty#ee8c1e86 = InputChannel;
+inputChannel#afeb712e channel_id:int access_hash:long = InputChannel;
+inputChannelFromMessage#2a286531 peer:InputPeer msg_id:int channel_id:int = InputChannel;
+
+contacts.resolvedPeer#7f077ad9 peer:Peer chats:Vector<Chat> users:Vector<User> = contacts.ResolvedPeer;
+
+messageRange#ae30253 min_id:int max_id:int = MessageRange;
+
+updates.channelDifferenceEmpty#3e11affb flags:# final:flags.0?true pts:int timeout:flags.1?int = updates.ChannelDifference;
+updates.channelDifferenceTooLong#a4bcc6fe flags:# final:flags.0?true timeout:flags.1?int dialog:Dialog messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = updates.ChannelDifference;
+updates.channelDifference#2064674e flags:# final:flags.0?true pts:int timeout:flags.1?int new_messages:Vector<Message> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> = updates.ChannelDifference;
+
+channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter;
+channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector<MessageRange> = ChannelMessagesFilter;
+
+channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant;
+channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant;
+channelParticipantCreator#808d15a4 flags:# user_id:int rank:flags.0?string = ChannelParticipant;
+channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant;
+channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant;
+
+channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter;
+channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter;
+channelParticipantsKicked#a3b54985 q:string = ChannelParticipantsFilter;
+channelParticipantsBots#b0d1865b = ChannelParticipantsFilter;
+channelParticipantsBanned#1427a5e1 q:string = ChannelParticipantsFilter;
+channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter;
+channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter;
+
+channels.channelParticipants#f56ee2a8 count:int participants:Vector<ChannelParticipant> users:Vector<User> = channels.ChannelParticipants;
+channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants;
+
+channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector<User> = channels.ChannelParticipant;
+
+help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector<MessageEntity> min_age_confirm:flags.1?int = help.TermsOfService;
+
+foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif;
+foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif;
+
+messages.foundGifs#450a1c0a next_offset:int results:Vector<FoundGif> = messages.FoundGifs;
+
+messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs;
+messages.savedGifs#2e0709a5 hash:int gifs:Vector<Document> = messages.SavedGifs;
+
+inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+
+inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult;
+inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult;
+inputBotInlineResultDocument#fff8fdc4 flags:# id:string type:string title:flags.1?string description:flags.2?string document:InputDocument send_message:InputBotInlineMessage = InputBotInlineResult;
+inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult;
+
+botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+
+botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult;
+botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult;
+
+messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM results:Vector<BotInlineResult> cache_time:int users:Vector<User> = messages.BotResults;
+
+exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink;
+
+messageFwdHeader#ec338270 flags:# from_id:flags.0?int from_name:flags.5?string date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int = MessageFwdHeader;
+
+auth.codeTypeSms#72a3158c = auth.CodeType;
+auth.codeTypeCall#741cd3e3 = auth.CodeType;
+auth.codeTypeFlashCall#226ccefb = auth.CodeType;
+
+auth.sentCodeTypeApp#3dbb5986 length:int = auth.SentCodeType;
+auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType;
+auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType;
+auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType;
+
+messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer;
+
+messages.messageEditData#26b5dde6 flags:# caption:flags.0?true = messages.MessageEditData;
+
+inputBotInlineMessageID#890c3d89 dc_id:int id:long access_hash:long = InputBotInlineMessageID;
+
+inlineBotSwitchPM#3c20629f text:string start_param:string = InlineBotSwitchPM;
+
+messages.peerDialogs#3371c354 dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> state:updates.State = messages.PeerDialogs;
+
+topPeer#edcdc05b peer:Peer rating:double = TopPeer;
+
+topPeerCategoryBotsPM#ab661b5b = TopPeerCategory;
+topPeerCategoryBotsInline#148677e2 = TopPeerCategory;
+topPeerCategoryCorrespondents#637b7ed = TopPeerCategory;
+topPeerCategoryGroups#bd17a14a = TopPeerCategory;
+topPeerCategoryChannels#161d9628 = TopPeerCategory;
+topPeerCategoryPhoneCalls#1e76a78c = TopPeerCategory;
+topPeerCategoryForwardUsers#a8406ca9 = TopPeerCategory;
+topPeerCategoryForwardChats#fbeec0f0 = TopPeerCategory;
+
+topPeerCategoryPeers#fb834291 category:TopPeerCategory count:int peers:Vector<TopPeer> = TopPeerCategoryPeers;
+
+contacts.topPeersNotModified#de266ef5 = contacts.TopPeers;
+contacts.topPeers#70b772a8 categories:Vector<TopPeerCategoryPeers> chats:Vector<Chat> users:Vector<User> = contacts.TopPeers;
+contacts.topPeersDisabled#b52c939d = contacts.TopPeers;
+
+draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage;
+draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage;
+
+messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers;
+messages.featuredStickers#b6abc341 hash:int count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
+
+messages.recentStickersNotModified#b17f890 = messages.RecentStickers;
+messages.recentStickers#22f3afb3 hash:int packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers;
+
+messages.archivedStickers#4fcba9c8 count:int sets:Vector<StickerSetCovered> = messages.ArchivedStickers;
+
+messages.stickerSetInstallResultSuccess#38641628 = messages.StickerSetInstallResult;
+messages.stickerSetInstallResultArchive#35e410a8 sets:Vector<StickerSetCovered> = messages.StickerSetInstallResult;
+
+stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered;
+stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector<Document> = StickerSetCovered;
+
+maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords;
+
+inputStickeredMediaPhoto#4a992157 id:InputPhoto = InputStickeredMedia;
+inputStickeredMediaDocument#438865b id:InputDocument = InputStickeredMedia;
+
+game#bdf9653b flags:# id:long access_hash:long short_name:string title:string description:string photo:Photo document:flags.0?Document = Game;
+
+inputGameID#32c3e77 id:long access_hash:long = InputGame;
+inputGameShortName#c331e80a bot_id:InputUser short_name:string = InputGame;
+
+highScore#58fffcd0 pos:int user_id:int score:int = HighScore;
+
+messages.highScores#9a3bfd99 scores:Vector<HighScore> users:Vector<User> = messages.HighScores;
+
+textEmpty#dc3d824f = RichText;
+textPlain#744694e0 text:string = RichText;
+textBold#6724abc4 text:RichText = RichText;
+textItalic#d912a59c text:RichText = RichText;
+textUnderline#c12622c4 text:RichText = RichText;
+textStrike#9bf8bb95 text:RichText = RichText;
+textFixed#6c3f19b9 text:RichText = RichText;
+textUrl#3c2884c1 text:RichText url:string webpage_id:long = RichText;
+textEmail#de5a0dd6 text:RichText email:string = RichText;
+textConcat#7e6260d7 texts:Vector<RichText> = RichText;
+textSubscript#ed6a8504 text:RichText = RichText;
+textSuperscript#c7fb5e01 text:RichText = RichText;
+textMarked#34b8621 text:RichText = RichText;
+textPhone#1ccb966a text:RichText phone:string = RichText;
+textImage#81ccf4f document_id:long w:int h:int = RichText;
+textAnchor#35553762 text:RichText name:string = RichText;
+
+pageBlockUnsupported#13567e8a = PageBlock;
+pageBlockTitle#70abc3fd text:RichText = PageBlock;
+pageBlockSubtitle#8ffa9a1f text:RichText = PageBlock;
+pageBlockAuthorDate#baafe5e0 author:RichText published_date:int = PageBlock;
+pageBlockHeader#bfd064ec text:RichText = PageBlock;
+pageBlockSubheader#f12bb6e1 text:RichText = PageBlock;
+pageBlockParagraph#467a0766 text:RichText = PageBlock;
+pageBlockPreformatted#c070d93e text:RichText language:string = PageBlock;
+pageBlockFooter#48870999 text:RichText = PageBlock;
+pageBlockDivider#db20b188 = PageBlock;
+pageBlockAnchor#ce0d37b0 name:string = PageBlock;
+pageBlockList#e4e88011 items:Vector<PageListItem> = PageBlock;
+pageBlockBlockquote#263d7c26 text:RichText caption:RichText = PageBlock;
+pageBlockPullquote#4f4456d3 text:RichText caption:RichText = PageBlock;
+pageBlockPhoto#1759c560 flags:# photo_id:long caption:PageCaption url:flags.0?string webpage_id:flags.0?long = PageBlock;
+pageBlockVideo#7c8fe7b6 flags:# autoplay:flags.0?true loop:flags.1?true video_id:long caption:PageCaption = PageBlock;
+pageBlockCover#39f23300 cover:PageBlock = PageBlock;
+pageBlockEmbed#a8718dc5 flags:# full_width:flags.0?true allow_scrolling:flags.3?true url:flags.1?string html:flags.2?string poster_photo_id:flags.4?long w:flags.5?int h:flags.5?int caption:PageCaption = PageBlock;
+pageBlockEmbedPost#f259a80b url:string webpage_id:long author_photo_id:long author:string date:int blocks:Vector<PageBlock> caption:PageCaption = PageBlock;
+pageBlockCollage#65a0fa4d items:Vector<PageBlock> caption:PageCaption = PageBlock;
+pageBlockSlideshow#31f9590 items:Vector<PageBlock> caption:PageCaption = PageBlock;
+pageBlockChannel#ef1751b5 channel:Chat = PageBlock;
+pageBlockAudio#804361ea audio_id:long caption:PageCaption = PageBlock;
+pageBlockKicker#1e148390 text:RichText = PageBlock;
+pageBlockTable#bf4dea82 flags:# bordered:flags.0?true striped:flags.1?true title:RichText rows:Vector<PageTableRow> = PageBlock;
+pageBlockOrderedList#9a8ae1e1 items:Vector<PageListOrderedItem> = PageBlock;
+pageBlockDetails#76768bed flags:# open:flags.0?true blocks:Vector<PageBlock> title:RichText = PageBlock;
+pageBlockRelatedArticles#16115a96 title:RichText articles:Vector<PageRelatedArticle> = PageBlock;
+pageBlockMap#a44f3ef6 geo:GeoPoint zoom:int w:int h:int caption:PageCaption = PageBlock;
+
+phoneCallDiscardReasonMissed#85e42301 = PhoneCallDiscardReason;
+phoneCallDiscardReasonDisconnect#e095c1a0 = PhoneCallDiscardReason;
+phoneCallDiscardReasonHangup#57adc690 = PhoneCallDiscardReason;
+phoneCallDiscardReasonBusy#faf7e8c9 = PhoneCallDiscardReason;
+
+dataJSON#7d748d04 data:string = DataJSON;
+
+labeledPrice#cb296bf8 label:string amount:long = LabeledPrice;
+
+invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> = Invoice;
+
+paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge;
+
+postAddress#1e8caaeb street_line1:string street_line2:string city:string state:string country_iso2:string post_code:string = PostAddress;
+
+paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string email:flags.2?string shipping_address:flags.3?PostAddress = PaymentRequestedInfo;
+
+paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials;
+
+webDocument#1c570ed1 url:string access_hash:long size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
+webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
+
+inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = InputWebDocument;
+
+inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation;
+inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w:int h:int zoom:int scale:int = InputWebFileLocation;
+
+upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile;
+
+payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
+
+payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = payments.ValidatedRequestedInfo;
+
+payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult;
+payments.paymentVerificationNeeded#d8411139 url:string = payments.PaymentResult;
+
+payments.paymentReceipt#500911e1 flags:# date:int bot_id:int invoice:Invoice provider_id:int info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption currency:string total_amount:long credentials_title:string users:Vector<User> = payments.PaymentReceipt;
+
+payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo;
+
+inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;
+inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
+inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials;
+inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials;
+
+account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPassword;
+
+shippingOption#b6213cdf id:string title:string prices:Vector<LabeledPrice> = ShippingOption;
+
+inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_coords:flags.0?MaskCoords = InputStickerSetItem;
+
+inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall;
+
+phoneCallEmpty#5366c915 id:long = PhoneCall;
+phoneCallWaiting#1b8f4ad1 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
+phoneCallRequested#87eabb53 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall;
+phoneCallAccepted#997c454a flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall;
+phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector<PhoneConnection> start_date:int = PhoneCall;
+phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.5?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
+
+phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
+
+phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector<string> = PhoneCallProtocol;
+
+phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector<User> = phone.PhoneCall;
+
+upload.cdnFileReuploadNeeded#eea8e46e request_token:bytes = upload.CdnFile;
+upload.cdnFile#a99fca4f bytes:bytes = upload.CdnFile;
+
+cdnPublicKey#c982eaba dc_id:int public_key:string = CdnPublicKey;
+
+cdnConfig#5725e40a public_keys:Vector<CdnPublicKey> = CdnConfig;
+
+langPackString#cad181f6 key:string value:string = LangPackString;
+langPackStringPluralized#6c47ac9f flags:# key:string zero_value:flags.0?string one_value:flags.1?string two_value:flags.2?string few_value:flags.3?string many_value:flags.4?string other_value:string = LangPackString;
+langPackStringDeleted#2979eeb2 key:string = LangPackString;
+
+langPackDifference#f385c1f6 lang_code:string from_version:int version:int strings:Vector<LangPackString> = LangPackDifference;
+
+langPackLanguage#eeca5ce3 flags:# official:flags.0?true rtl:flags.2?true beta:flags.3?true name:string native_name:string lang_code:string base_lang_code:flags.1?string plural_code:string strings_count:int translated_count:int translations_url:string = LangPackLanguage;
+
+channelAdminLogEventActionChangeTitle#e6dfb825 prev_value:string new_value:string = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeAbout#55188a2e prev_value:string new_value:string = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeUsername#6a4afc38 prev_value:string new_value:string = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangePhoto#434bd2af prev_photo:Photo new_photo:Photo = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleInvites#1b7907ae new_value:Bool = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleSignatures#26ae0971 new_value:Bool = ChannelAdminLogEventAction;
+channelAdminLogEventActionUpdatePinned#e9e82c18 message:Message = ChannelAdminLogEventAction;
+channelAdminLogEventActionEditMessage#709b2405 prev_message:Message new_message:Message = ChannelAdminLogEventAction;
+channelAdminLogEventActionDeleteMessage#42e047bb message:Message = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantJoin#183040d3 = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantLeave#f89777f2 = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantInvite#e31c34d8 participant:ChannelParticipant = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantToggleBan#e6d83d7e prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantToggleAdmin#d5676710 prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeStickerSet#b1c3caa7 prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction;
+channelAdminLogEventActionTogglePreHistoryHidden#5f5c95f1 new_value:Bool = ChannelAdminLogEventAction;
+channelAdminLogEventActionDefaultBannedRights#2df5fc0a prev_banned_rights:ChatBannedRights new_banned_rights:ChatBannedRights = ChannelAdminLogEventAction;
+channelAdminLogEventActionStopPoll#8f079643 message:Message = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeLinkedChat#a26f881b prev_value:int new_value:int = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeLocation#e6b76ae prev_value:ChannelLocation new_value:ChannelLocation = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleSlowMode#53909779 prev_value:int new_value:int = ChannelAdminLogEventAction;
+
+channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
+
+channels.adminLogResults#ed8af74d events:Vector<ChannelAdminLogEvent> chats:Vector<Chat> users:Vector<User> = channels.AdminLogResults;
+
+channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true = ChannelAdminLogEventsFilter;
+
+popularContact#5ce14175 client_id:long importers:int = PopularContact;
+
+messages.favedStickersNotModified#9e8fa6d3 = messages.FavedStickers;
+messages.favedStickers#f37f2f16 hash:int packs:Vector<StickerPack> stickers:Vector<Document> = messages.FavedStickers;
+
+recentMeUrlUnknown#46e1d13d url:string = RecentMeUrl;
+recentMeUrlUser#8dbc3336 url:string user_id:int = RecentMeUrl;
+recentMeUrlChat#a01b22f9 url:string chat_id:int = RecentMeUrl;
+recentMeUrlChatInvite#eb49081d url:string chat_invite:ChatInvite = RecentMeUrl;
+recentMeUrlStickerSet#bc0a57dc url:string set:StickerSetCovered = RecentMeUrl;
+
+help.recentMeUrls#e0310d7 urls:Vector<RecentMeUrl> chats:Vector<Chat> users:Vector<User> = help.RecentMeUrls;
+
+inputSingleMedia#1cc6e91f flags:# media:InputMedia random_id:long message:string entities:flags.0?Vector<MessageEntity> = InputSingleMedia;
+
+webAuthorization#cac943f2 hash:long bot_id:int domain:string browser:string platform:string date_created:int date_active:int ip:string region:string = WebAuthorization;
+
+account.webAuthorizations#ed56c9fc authorizations:Vector<WebAuthorization> users:Vector<User> = account.WebAuthorizations;
+
+inputMessageID#a676a322 id:int = InputMessage;
+inputMessageReplyTo#bad88395 id:int = InputMessage;
+inputMessagePinned#86872538 = InputMessage;
+
+inputDialogPeer#fcaafeb7 peer:InputPeer = InputDialogPeer;
+inputDialogPeerFolder#64600527 folder_id:int = InputDialogPeer;
+
+dialogPeer#e56dbf05 peer:Peer = DialogPeer;
+dialogPeerFolder#514519e2 folder_id:int = DialogPeer;
+
+messages.foundStickerSetsNotModified#d54b65d = messages.FoundStickerSets;
+messages.foundStickerSets#5108d648 hash:int sets:Vector<StickerSetCovered> = messages.FoundStickerSets;
+
+fileHash#6242c773 offset:int limit:int hash:bytes = FileHash;
+
+inputClientProxy#75588b3f address:string port:int = InputClientProxy;
+
+help.proxyDataEmpty#e09e1fb8 expires:int = help.ProxyData;
+help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector<Chat> users:Vector<User> = help.ProxyData;
+
+help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate;
+help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = help.TermsOfServiceUpdate;
+
+inputSecureFileUploaded#3334b0f0 id:long parts:int md5_checksum:string file_hash:bytes secret:bytes = InputSecureFile;
+inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile;
+
+secureFileEmpty#64199744 = SecureFile;
+secureFile#e0277a62 id:long access_hash:long size:int dc_id:int date:int file_hash:bytes secret:bytes = SecureFile;
+
+secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData;
+
+securePlainPhone#7d6099dd phone:string = SecurePlainData;
+securePlainEmail#21ec5a5f email:string = SecurePlainData;
+
+secureValueTypePersonalDetails#9d2a81e3 = SecureValueType;
+secureValueTypePassport#3dac6a00 = SecureValueType;
+secureValueTypeDriverLicense#6e425c4 = SecureValueType;
+secureValueTypeIdentityCard#a0d0744b = SecureValueType;
+secureValueTypeInternalPassport#99a48f23 = SecureValueType;
+secureValueTypeAddress#cbe31e26 = SecureValueType;
+secureValueTypeUtilityBill#fc36954e = SecureValueType;
+secureValueTypeBankStatement#89137c0d = SecureValueType;
+secureValueTypeRentalAgreement#8b883488 = SecureValueType;
+secureValueTypePassportRegistration#99e3806a = SecureValueType;
+secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType;
+secureValueTypePhone#b320aadb = SecureValueType;
+secureValueTypeEmail#8e3ca7ee = SecureValueType;
+
+secureValue#187fa0ca flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile translation:flags.6?Vector<SecureFile> files:flags.4?Vector<SecureFile> plain_data:flags.5?SecurePlainData hash:bytes = SecureValue;
+
+inputSecureValue#db21d0a7 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile translation:flags.6?Vector<InputSecureFile> files:flags.4?Vector<InputSecureFile> plain_data:flags.5?SecurePlainData = InputSecureValue;
+
+secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash;
+
+secureValueErrorData#e8a40bd9 type:SecureValueType data_hash:bytes field:string text:string = SecureValueError;
+secureValueErrorFrontSide#be3dfa type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorReverseSide#868a2aa5 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorFile#7a700873 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
+secureValueError#869d758f type:SecureValueType hash:bytes text:string = SecureValueError;
+secureValueErrorTranslationFile#a1144770 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorTranslationFiles#34636dd8 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
+
+secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted;
+
+account.authorizationForm#ad2e1cd8 flags:# required_types:Vector<SecureRequiredType> values:Vector<SecureValue> errors:Vector<SecureValueError> users:Vector<User> privacy_policy_url:flags.0?string = account.AuthorizationForm;
+
+account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEmailCode;
+
+help.deepLinkInfoEmpty#66afa166 = help.DeepLinkInfo;
+help.deepLinkInfo#6a4ee832 flags:# update_app:flags.0?true message:string entities:flags.1?Vector<MessageEntity> = help.DeepLinkInfo;
+
+savedPhoneContact#1142bd56 phone:string first_name:string last_name:string date:int = SavedContact;
+
+account.takeout#4dba4501 id:long = account.Takeout;
+
+passwordKdfAlgoUnknown#d45ab096 = PasswordKdfAlgo;
+passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow#3a912d4a salt1:bytes salt2:bytes g:int p:bytes = PasswordKdfAlgo;
+
+securePasswordKdfAlgoUnknown#4a8537 = SecurePasswordKdfAlgo;
+securePasswordKdfAlgoPBKDF2HMACSHA512iter100000#bbf2dda0 salt:bytes = SecurePasswordKdfAlgo;
+securePasswordKdfAlgoSHA512#86471d92 salt:bytes = SecurePasswordKdfAlgo;
+
+secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:bytes secure_secret_id:long = SecureSecretSettings;
+
+inputCheckPasswordEmpty#9880f658 = InputCheckPasswordSRP;
+inputCheckPasswordSRP#d27ff082 srp_id:long A:bytes M1:bytes = InputCheckPasswordSRP;
+
+secureRequiredType#829d99da flags:# native_names:flags.0?true selfie_required:flags.1?true translation_required:flags.2?true type:SecureValueType = SecureRequiredType;
+secureRequiredTypeOneOf#27477b4 types:Vector<SecureRequiredType> = SecureRequiredType;
+
+help.passportConfigNotModified#bfb9f457 = help.PassportConfig;
+help.passportConfig#a098d6af hash:int countries_langs:DataJSON = help.PassportConfig;
+
+inputAppEvent#1d1b1245 time:double type:string peer:long data:JSONValue = InputAppEvent;
+
+jsonObjectValue#c0de1bd9 key:string value:JSONValue = JSONObjectValue;
+
+jsonNull#3f6d7b68 = JSONValue;
+jsonBool#c7345e6a value:Bool = JSONValue;
+jsonNumber#2be0dfa4 value:double = JSONValue;
+jsonString#b71e767a value:string = JSONValue;
+jsonArray#f7444763 value:Vector<JSONValue> = JSONValue;
+jsonObject#99c1d49d value:Vector<JSONObjectValue> = JSONValue;
+
+pageTableCell#34566b6a flags:# header:flags.0?true align_center:flags.3?true align_right:flags.4?true valign_middle:flags.5?true valign_bottom:flags.6?true text:flags.7?RichText colspan:flags.1?int rowspan:flags.2?int = PageTableCell;
+
+pageTableRow#e0c0c5e5 cells:Vector<PageTableCell> = PageTableRow;
+
+pageCaption#6f747657 text:RichText credit:RichText = PageCaption;
+
+pageListItemText#b92fb6cd text:RichText = PageListItem;
+pageListItemBlocks#25e073fc blocks:Vector<PageBlock> = PageListItem;
+
+pageListOrderedItemText#5e068047 num:string text:RichText = PageListOrderedItem;
+pageListOrderedItemBlocks#98dd8936 num:string blocks:Vector<PageBlock> = PageListOrderedItem;
+
+pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long title:flags.0?string description:flags.1?string photo_id:flags.2?long author:flags.3?string published_date:flags.4?int = PageRelatedArticle;
+
+page#98657f0d flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> views:flags.3?int = Page;
+
+help.supportName#8c05f1c9 name:string = help.SupportName;
+
+help.userInfoEmpty#f3ae2eed = help.UserInfo;
+help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:string date:int = help.UserInfo;
+
+pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
+
+poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> close_period:flags.4?int close_date:flags.5?int = Poll;
+
+pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
+
+pollResults#badcc1a3 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<int> solution:flags.4?string solution_entities:flags.4?Vector<MessageEntity> = PollResults;
+
+chatOnlines#f041e250 onlines:int = ChatOnlines;
+
+statsURL#47a971e0 url:string = StatsURL;
+
+chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true = ChatAdminRights;
+
+chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights;
+
+inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper;
+inputWallPaperSlug#72091c80 slug:string = InputWallPaper;
+inputWallPaperNoFile#8427bbac = InputWallPaper;
+
+account.wallPapersNotModified#1c199183 = account.WallPapers;
+account.wallPapers#702b65a9 hash:int wallpapers:Vector<WallPaper> = account.WallPapers;
+
+codeSettings#debebe83 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true = CodeSettings;
+
+wallPaperSettings#5086cf8 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings;
+
+autoDownloadSettings#e04232f3 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:int file_size_max:int video_upload_maxbitrate:int = AutoDownloadSettings;
+
+account.autoDownloadSettings#63cacf26 low:AutoDownloadSettings medium:AutoDownloadSettings high:AutoDownloadSettings = account.AutoDownloadSettings;
+
+emojiKeyword#d5b3b9f9 keyword:string emoticons:Vector<string> = EmojiKeyword;
+emojiKeywordDeleted#236df622 keyword:string emoticons:Vector<string> = EmojiKeyword;
+
+emojiKeywordsDifference#5cc761bd lang_code:string from_version:int version:int keywords:Vector<EmojiKeyword> = EmojiKeywordsDifference;
+
+emojiURL#a575739d url:string = EmojiURL;
+
+emojiLanguage#b3fb5361 lang_code:string = EmojiLanguage;
+
+fileLocationToBeDeprecated#bc7fc6cd volume_id:long local_id:int = FileLocation;
+
+folder#ff544e65 flags:# autofill_new_broadcasts:flags.0?true autofill_public_groups:flags.1?true autofill_new_correspondents:flags.2?true id:int title:string photo:flags.3?ChatPhoto = Folder;
+
+inputFolderPeer#fbd2c296 peer:InputPeer folder_id:int = InputFolderPeer;
+
+folderPeer#e9baa668 peer:Peer folder_id:int = FolderPeer;
+
+messages.searchCounter#e844ebff flags:# inexact:flags.1?true filter:MessagesFilter count:int = messages.SearchCounter;
+
+urlAuthResultRequest#92d33a0e flags:# request_write_access:flags.0?true bot:User domain:string = UrlAuthResult;
+urlAuthResultAccepted#8f8c0e4e url:string = UrlAuthResult;
+urlAuthResultDefault#a9d6db1f = UrlAuthResult;
+
+channelLocationEmpty#bfb5ad8b = ChannelLocation;
+channelLocation#209b82db geo_point:GeoPoint address:string = ChannelLocation;
+
+peerLocated#ca461b5d peer:Peer expires:int distance:int = PeerLocated;
+peerSelfLocated#f8ec284b expires:int = PeerLocated;
+
+restrictionReason#d072acb4 platform:string reason:string text:string = RestrictionReason;
+
+inputTheme#3c5693e9 id:long access_hash:long = InputTheme;
+inputThemeSlug#f5890df1 slug:string = InputTheme;
+
+theme#28f1114 flags:# creator:flags.0?true default:flags.1?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:int = Theme;
+
+account.themesNotModified#f41eb622 = account.Themes;
+account.themes#7f676421 hash:int themes:Vector<Theme> = account.Themes;
+
+auth.loginToken#629f1980 expires:int token:bytes = auth.LoginToken;
+auth.loginTokenMigrateTo#68e9916 dc_id:int token:bytes = auth.LoginToken;
+auth.loginTokenSuccess#390d5c5e authorization:auth.Authorization = auth.LoginToken;
+
+account.contentSettings#57e28221 flags:# sensitive_enabled:flags.0?true sensitive_can_change:flags.1?true = account.ContentSettings;
+
+messages.inactiveChats#a927fec5 dates:Vector<int> chats:Vector<Chat> users:Vector<User> = messages.InactiveChats;
+
+baseThemeClassic#c3a12462 = BaseTheme;
+baseThemeDay#fbd81688 = BaseTheme;
+baseThemeNight#b7b31ea8 = BaseTheme;
+baseThemeTinted#6d5f77ee = BaseTheme;
+baseThemeArctic#5b11125a = BaseTheme;
+
+inputThemeSettings#bd507cd1 flags:# base_theme:BaseTheme accent_color:int message_top_color:flags.0?int message_bottom_color:flags.0?int wallpaper:flags.1?InputWallPaper wallpaper_settings:flags.1?WallPaperSettings = InputThemeSettings;
+
+themeSettings#9c14984a flags:# base_theme:BaseTheme accent_color:int message_top_color:flags.0?int message_bottom_color:flags.0?int wallpaper:flags.1?WallPaper = ThemeSettings;
+
+webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector<Document> settings:flags.1?ThemeSettings = WebPageAttribute;
+
+messageUserVote#a28e5559 user_id:int option:bytes date:int = MessageUserVote;
+messageUserVoteInputOption#36377430 user_id:int date:int = MessageUserVote;
+messageUserVoteMultiple#e8fe0de user_id:int options:Vector<bytes> date:int = MessageUserVote;
+
+messages.votesList#823f649 flags:# count:int votes:Vector<MessageUserVote> users:Vector<User> next_offset:flags.0?string = messages.VotesList;
+
+bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl;
+
+payments.bankCardData#3e24e573 title:string open_urls:Vector<BankCardOpenUrl> = payments.BankCardData;
+
+dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector<InputPeer> include_peers:Vector<InputPeer> exclude_peers:Vector<InputPeer> = DialogFilter;
+
+dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested;
+
+statsDateRangeDays#b637edaf min_date:int max_date:int = StatsDateRangeDays;
+
+statsAbsValueAndPrev#cb43acde current:double previous:double = StatsAbsValueAndPrev;
+
+statsPercentValue#cbce2fe0 part:double total:double = StatsPercentValue;
+
+statsGraphAsync#4a27eb2d token:string = StatsGraph;
+statsGraphError#bedc9822 error:string = StatsGraph;
+statsGraph#8ea464b6 flags:# json:DataJSON zoom_token:flags.0?string = StatsGraph;
+
+messageInteractionCounters#ad4fc9bd msg_id:int views:int forwards:int = MessageInteractionCounters;
+
+stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueAndPrev views_per_post:StatsAbsValueAndPrev shares_per_post:StatsAbsValueAndPrev enabled_notifications:StatsPercentValue growth_graph:StatsGraph followers_graph:StatsGraph mute_graph:StatsGraph top_hours_graph:StatsGraph interactions_graph:StatsGraph iv_interactions_graph:StatsGraph views_by_source_graph:StatsGraph new_followers_by_source_graph:StatsGraph languages_graph:StatsGraph recent_message_interactions:Vector<MessageInteractionCounters> = stats.BroadcastStats;
+
+---functions---
+
+invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
+invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
+initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X;
+invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
+invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
+invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X;
+invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X;
+
+auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode;
+auth.signUp#80eee427 phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization;
+auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization;
+auth.logOut#5717da40 = Bool;
+auth.resetAuthorizations#9fab0d1a = Bool;
+auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization;
+auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization;
+auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool;
+auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization;
+auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization;
+auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery;
+auth.recoverPassword#4ea56e92 code:string = auth.Authorization;
+auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode;
+auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool;
+auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector<long> = Bool;
+auth.exportLoginToken#b1b41517 api_id:int api_hash:string except_ids:Vector<int> = auth.LoginToken;
+auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken;
+auth.acceptLoginToken#e894ad4d token:bytes = Authorization;
+
+account.registerDevice#68976c6f flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector<int> = Bool;
+account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector<int> = Bool;
+account.updateNotifySettings#84be5b93 peer:InputNotifyPeer settings:InputPeerNotifySettings = Bool;
+account.getNotifySettings#12b3ad31 peer:InputNotifyPeer = PeerNotifySettings;
+account.resetNotifySettings#db7e1747 = Bool;
+account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User;
+account.updateStatus#6628562c offline:Bool = Bool;
+account.getWallPapers#aabb1763 hash:int = account.WallPapers;
+account.reportPeer#ae189d5f peer:InputPeer reason:ReportReason = Bool;
+account.checkUsername#2714d86c username:string = Bool;
+account.updateUsername#3e0bdd7c username:string = User;
+account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules;
+account.setPrivacy#c9f81ce8 key:InputPrivacyKey rules:Vector<InputPrivacyRule> = account.PrivacyRules;
+account.deleteAccount#418d4e0b reason:string = Bool;
+account.getAccountTTL#8fc711d = AccountDaysTTL;
+account.setAccountTTL#2442485e ttl:AccountDaysTTL = Bool;
+account.sendChangePhoneCode#82574ae5 phone_number:string settings:CodeSettings = auth.SentCode;
+account.changePhone#70c32edb phone_number:string phone_code_hash:string phone_code:string = User;
+account.updateDeviceLocked#38df3532 period:int = Bool;
+account.getAuthorizations#e320c158 = account.Authorizations;
+account.resetAuthorization#df77f3bc hash:long = Bool;
+account.getPassword#548a30f5 = account.Password;
+account.getPasswordSettings#9cd4eaf9 password:InputCheckPasswordSRP = account.PasswordSettings;
+account.updatePasswordSettings#a59b102f password:InputCheckPasswordSRP new_settings:account.PasswordInputSettings = Bool;
+account.sendConfirmPhoneCode#1b3faa88 hash:string settings:CodeSettings = auth.SentCode;
+account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool;
+account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword;
+account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
+account.resetWebAuthorization#2d01b9ef hash:long = Bool;
+account.resetWebAuthorizations#682d2594 = Bool;
+account.getAllSecureValues#b288bc7d = Vector<SecureValue>;
+account.getSecureValue#73665bc2 types:Vector<SecureValueType> = Vector<SecureValue>;
+account.saveSecureValue#899fe31d value:InputSecureValue secure_secret_id:long = SecureValue;
+account.deleteSecureValue#b880bc4b types:Vector<SecureValueType> = Bool;
+account.getAuthorizationForm#b86ba8e1 bot_id:int scope:string public_key:string = account.AuthorizationForm;
+account.acceptAuthorization#e7027c94 bot_id:int scope:string public_key:string value_hashes:Vector<SecureValueHash> credentials:SecureCredentialsEncrypted = Bool;
+account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode;
+account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool;
+account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode;
+account.verifyEmail#ecba39db email:string code:string = Bool;
+account.initTakeoutSession#f05b4804 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?int = account.Takeout;
+account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool;
+account.confirmPasswordEmail#8fdf1920 code:string = Bool;
+account.resendPasswordEmail#7a7f2a15 = Bool;
+account.cancelPasswordEmail#c1cbd5b6 = Bool;
+account.getContactSignUpNotification#9f07c728 = Bool;
+account.setContactSignUpNotification#cff43f61 silent:Bool = Bool;
+account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates;
+account.getWallPaper#fc8ddbea wallpaper:InputWallPaper = WallPaper;
+account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper;
+account.saveWallPaper#6c5a5b37 wallpaper:InputWallPaper unsave:Bool settings:WallPaperSettings = Bool;
+account.installWallPaper#feed5769 wallpaper:InputWallPaper settings:WallPaperSettings = Bool;
+account.resetWallPapers#bb3b9804 = Bool;
+account.getAutoDownloadSettings#56da0b3f = account.AutoDownloadSettings;
+account.saveAutoDownloadSettings#76f36233 flags:# low:flags.0?true high:flags.1?true settings:AutoDownloadSettings = Bool;
+account.uploadTheme#1c3db333 flags:# file:InputFile thumb:flags.0?InputFile file_name:string mime_type:string = Document;
+account.createTheme#8432c21f flags:# slug:string title:string document:flags.2?InputDocument settings:flags.3?InputThemeSettings = Theme;
+account.updateTheme#5cb367d5 flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?InputThemeSettings = Theme;
+account.saveTheme#f257106c theme:InputTheme unsave:Bool = Bool;
+account.installTheme#7ae43737 flags:# dark:flags.0?true format:flags.1?string theme:flags.1?InputTheme = Bool;
+account.getTheme#8d9d742b format:string theme:InputTheme document_id:long = Theme;
+account.getThemes#285946f8 format:string hash:int = account.Themes;
+account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
+account.getContentSettings#8b9b4dae = account.ContentSettings;
+account.getMultiWallPapers#65ad71dc wallpapers:Vector<InputWallPaper> = Vector<WallPaper>;
+
+users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
+users.getFullUser#ca30a5b1 id:InputUser = UserFull;
+users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector<SecureValueError> = Bool;
+
+contacts.getContactIDs#2caa4a42 hash:int = Vector<int>;
+contacts.getStatuses#c4a353ee = Vector<ContactStatus>;
+contacts.getContacts#c023849f hash:int = contacts.Contacts;
+contacts.importContacts#2c800be5 contacts:Vector<InputContact> = contacts.ImportedContacts;
+contacts.deleteContacts#96a0e00 id:Vector<InputUser> = Updates;
+contacts.deleteByPhones#1013fd9e phones:Vector<string> = Bool;
+contacts.block#332b49fc id:InputUser = Bool;
+contacts.unblock#e54100bd id:InputUser = Bool;
+contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked;
+contacts.search#11f812d8 q:string limit:int = contacts.Found;
+contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer;
+contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers;
+contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool;
+contacts.resetSaved#879537f1 = Bool;
+contacts.getSaved#82f1e39f = Vector<SavedContact>;
+contacts.toggleTopPeers#8514bdda enabled:Bool = Bool;
+contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates;
+contacts.acceptContact#f831a20f id:InputUser = Updates;
+contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoPoint self_expires:flags.0?int = Updates;
+
+messages.getMessages#63c66506 id:Vector<InputMessage> = messages.Messages;
+messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs;
+messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
+messages.search#8614ef68 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
+messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages;
+messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory;
+messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector<int> = messages.AffectedMessages;
+messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
+messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool;
+messages.sendMessage#520c3870 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int = Updates;
+messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int = Updates;
+messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true grouped:flags.9?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer schedule_date:flags.10?int = Updates;
+messages.reportSpam#cf1592db peer:InputPeer = Bool;
+messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings;
+messages.report#bd82b658 peer:InputPeer id:Vector<int> reason:ReportReason = Bool;
+messages.getChats#3c6aa187 id:Vector<int> = messages.Chats;
+messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull;
+messages.editChatTitle#dc452855 chat_id:int title:string = Updates;
+messages.editChatPhoto#ca4c79d8 chat_id:int photo:InputChatPhoto = Updates;
+messages.addChatUser#f9a0aa09 chat_id:int user_id:InputUser fwd_limit:int = Updates;
+messages.deleteChatUser#e0611f16 chat_id:int user_id:InputUser = Updates;
+messages.createChat#9cb126e users:Vector<InputUser> title:string = Updates;
+messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig;
+messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat;
+messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat;
+messages.discardEncryption#edd923c5 chat_id:int = Bool;
+messages.setEncryptedTyping#791451ed peer:InputEncryptedChat typing:Bool = Bool;
+messages.readEncryptedHistory#7f4b690a peer:InputEncryptedChat max_date:int = Bool;
+messages.sendEncrypted#a9776773 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage;
+messages.sendEncryptedFile#9a901b66 peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage;
+messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage;
+messages.receivedQueue#55a5bb66 max_qts:int = Vector<long>;
+messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool;
+messages.readMessageContents#36a73f77 id:Vector<int> = messages.AffectedMessages;
+messages.getStickers#43d4f2c emoticon:string hash:int = messages.Stickers;
+messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers;
+messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector<MessageEntity> = MessageMedia;
+messages.exportChatInvite#df7534c peer:InputPeer = ExportedChatInvite;
+messages.checkChatInvite#3eadb1bb hash:string = ChatInvite;
+messages.importChatInvite#6c50051c hash:string = Updates;
+messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet;
+messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult;
+messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool;
+messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates;
+messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector<int> increment:Bool = Vector<int>;
+messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool;
+messages.migrateChat#15a3b8e3 chat_id:int = Updates;
+messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
+messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector<long> = Bool;
+messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document;
+messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs;
+messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs;
+messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool;
+messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults;
+messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector<InputBotInlineResult> cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool;
+messages.sendInlineBotResult#220815b0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string schedule_date:flags.10?int = Updates;
+messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData;
+messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.15?int = Updates;
+messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Bool;
+messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer;
+messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;
+messages.getPeerDialogs#e470bcfd peers:Vector<InputDialogPeer> = messages.PeerDialogs;
+messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector<MessageEntity> = Bool;
+messages.getAllDrafts#6a3f8d65 = Updates;
+messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers;
+messages.readFeaturedStickers#5b118126 id:Vector<long> = Bool;
+messages.getRecentStickers#5ea192c9 flags:# attached:flags.0?true hash:int = messages.RecentStickers;
+messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool;
+messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool;
+messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true offset_id:long limit:int = messages.ArchivedStickers;
+messages.getMaskStickers#65b8c79f hash:int = messages.AllStickers;
+messages.getAttachedStickers#cc5b67cc media:InputStickeredMedia = Vector<StickerSetCovered>;
+messages.setGameScore#8ef8ecc0 flags:# edit_message:flags.0?true force:flags.1?true peer:InputPeer id:int user_id:InputUser score:int = Updates;
+messages.setInlineGameScore#15ad9f64 flags:# edit_message:flags.0?true force:flags.1?true id:InputBotInlineMessageID user_id:InputUser score:int = Bool;
+messages.getGameHighScores#e822649d peer:InputPeer id:int user_id:InputUser = messages.HighScores;
+messages.getInlineGameHighScores#f635e1b id:InputBotInlineMessageID user_id:InputUser = messages.HighScores;
+messages.getCommonChats#d0a48c4 user_id:InputUser max_id:int limit:int = messages.Chats;
+messages.getAllChats#eba80ff0 except_ids:Vector<int> = messages.Chats;
+messages.getWebPage#32ca8f91 url:string hash:int = WebPage;
+messages.toggleDialogPin#a731e257 flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
+messages.reorderPinnedDialogs#3b1adf37 flags:# force:flags.0?true folder_id:int order:Vector<InputDialogPeer> = Bool;
+messages.getPinnedDialogs#d6b94df2 folder_id:int = messages.PeerDialogs;
+messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = Bool;
+messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool;
+messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia;
+messages.sendScreenshotNotification#c97df020 peer:InputPeer reply_to_msg_id:int random_id:long = Updates;
+messages.getFavedStickers#21ce0b0e hash:int = messages.FavedStickers;
+messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool;
+messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
+messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory;
+messages.getRecentLocations#bbc45b09 peer:InputPeer limit:int hash:int = messages.Messages;
+messages.sendMultiMedia#cc0110cb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector<InputSingleMedia> schedule_date:flags.10?int = Updates;
+messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
+messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:string hash:int = messages.FoundStickerSets;
+messages.getSplitRanges#1cff7e08 = Vector<MessageRange>;
+messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool;
+messages.getDialogUnreadMarks#22e24e22 = Vector<DialogPeer>;
+messages.clearAllDrafts#7e58ee9c = Bool;
+messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true peer:InputPeer id:int = Updates;
+messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector<bytes> = Updates;
+messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates;
+messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines;
+messages.getStatsURL#812c2ae6 flags:# dark:flags.0?true peer:InputPeer params:string = StatsURL;
+messages.editChatAbout#def60797 peer:InputPeer about:string = Bool;
+messages.editChatDefaultBannedRights#a5866b41 peer:InputPeer banned_rights:ChatBannedRights = Updates;
+messages.getEmojiKeywords#35a0e062 lang_code:string = EmojiKeywordsDifference;
+messages.getEmojiKeywordsDifference#1508b6af lang_code:string from_version:int = EmojiKeywordsDifference;
+messages.getEmojiKeywordsLanguages#4e9963b2 lang_codes:Vector<string> = Vector<EmojiLanguage>;
+messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL;
+messages.getSearchCounters#732eef00 peer:InputPeer filters:Vector<MessagesFilter> = Vector<messages.SearchCounter>;
+messages.requestUrlAuth#e33f5613 peer:InputPeer msg_id:int button_id:int = UrlAuthResult;
+messages.acceptUrlAuth#f729ea98 flags:# write_allowed:flags.0?true peer:InputPeer msg_id:int button_id:int = UrlAuthResult;
+messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool;
+messages.getScheduledHistory#e2c2685b peer:InputPeer hash:int = messages.Messages;
+messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector<int> = messages.Messages;
+messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
+messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector<int> = Updates;
+messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList;
+messages.toggleStickerSets#b5052fea flags:# uninstall:flags.0?true archive:flags.1?true unarchive:flags.2?true stickersets:Vector<InputStickerSet> = Bool;
+messages.getDialogFilters#f19ed96d = Vector<DialogFilter>;
+messages.getSuggestedDialogFilters#a29cd42c = Vector<DialogFilterSuggested>;
+messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool;
+messages.updateDialogFiltersOrder#c563c1e4 order:Vector<int> = Bool;
+messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = messages.FeaturedStickers;
+
+updates.getState#edd4882a = updates.State;
+updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
+updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
+
+photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto;
+photos.uploadProfilePhoto#4f32c098 file:InputFile = photos.Photo;
+photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
+photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
+
+upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool;
+upload.getFile#b15a9afc flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:int limit:int = upload.File;
+upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool;
+upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile;
+upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile;
+upload.reuploadCdnFile#9b2754a8 file_token:bytes request_token:bytes = Vector<FileHash>;
+upload.getCdnFileHashes#4da54231 file_token:bytes offset:int = Vector<FileHash>;
+upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector<FileHash>;
+
+help.getConfig#c4f9186b = Config;
+help.getNearestDc#1fb33026 = NearestDc;
+help.getAppUpdate#522d5a7d source:string = help.AppUpdate;
+help.getInviteText#4d392343 = help.InviteText;
+help.getSupport#9cdf08cd = help.Support;
+help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
+help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
+help.getCdnConfig#52029342 = CdnConfig;
+help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls;
+help.getProxyData#3d7758e1 = help.ProxyData;
+help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate;
+help.acceptTermsOfService#ee72f79a id:DataJSON = Bool;
+help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo;
+help.getAppConfig#98914110 = JSONValue;
+help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
+help.getPassportConfig#c661ad08 hash:int = help.PassportConfig;
+help.getSupportName#d360e72c = help.SupportName;
+help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo;
+help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector<MessageEntity> = help.UserInfo;
+
+channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
+channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
+channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory;
+channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector<int> = Bool;
+channels.getMessages#ad8c9a23 channel:InputChannel id:Vector<InputMessage> = messages.Messages;
+channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants;
+channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant;
+channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
+channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull;
+channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates;
+channels.editAdmin#d33c8902 channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights rank:string = Updates;
+channels.editTitle#566decd0 channel:InputChannel title:string = Updates;
+channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates;
+channels.checkUsername#10e6bd2c channel:InputChannel username:string = Bool;
+channels.updateUsername#3514b3de channel:InputChannel username:string = Bool;
+channels.joinChannel#24b524c5 channel:InputChannel = Updates;
+channels.leaveChannel#f836aa95 channel:InputChannel = Updates;
+channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector<InputUser> = Updates;
+channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
+channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink;
+channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
+channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats;
+channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates;
+channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
+channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
+channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
+channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool;
+channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
+channels.getLeftChannels#8341ecc0 offset:int = messages.Chats;
+channels.getGroupsForDiscussion#f5dad378 = messages.Chats;
+channels.setDiscussionGroup#40582bb2 broadcast:InputChannel group:InputChannel = Bool;
+channels.editCreator#8f38cd1f channel:InputChannel user_id:InputUser password:InputCheckPasswordSRP = Updates;
+channels.editLocation#58e63f6d channel:InputChannel geo_point:InputGeoPoint address:string = Bool;
+channels.toggleSlowMode#edd49ef0 channel:InputChannel seconds:int = Updates;
+channels.getInactiveChannels#11e831ee = messages.InactiveChats;
+
+bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
+bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
+bots.setBotCommands#805d46f6 commands:Vector<BotCommand> = Bool;
+
+payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm;
+payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt;
+payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
+payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult;
+payments.getSavedInfo#227d824b = payments.SavedInfo;
+payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
+payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
+
+stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> = messages.StickerSet;
+stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
+stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet;
+stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;
+stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet;
+
+phone.getCallConfig#55451fa9 = DataJSON;
+phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
+phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
+phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
+phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool;
+phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates;
+phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
+phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
+
+langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
+langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
+langpack.getDifference#cd984aa5 lang_pack:string lang_code:string from_version:int = LangPackDifference;
+langpack.getLanguages#42c6978f lang_pack:string = Vector<LangPackLanguage>;
+langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage;
+
+folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
+folders.deleteFolder#1c295881 folder_id:int = Updates;
+
+stats.getBroadcastStats#e6300dba flags:# dark:flags.0?true channel:InputChannel tz_offset:int = stats.BroadcastStats;
+stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
+
+// LAYER 112

+ 113 - 0
gramjs/tl/static/schema.tl

@@ -0,0 +1,113 @@
+// Core types (no need to gen)
+
+//vector#1cb5c415 {t:Type} # [ t ] = Vector t;
+
+///////////////////////////////
+/// Authorization key creation
+///////////////////////////////
+
+resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector<long> = ResPQ;
+
+p_q_inner_data#83c95aec pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data;
+p_q_inner_data_dc#a9f55f95 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data;
+p_q_inner_data_temp#3c6a84d4 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data;
+p_q_inner_data_temp_dc#56fddf88 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data;
+
+server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params;
+server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params;
+
+server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:string g_a:string server_time:int = Server_DH_inner_data;
+
+client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:string = Client_DH_Inner_Data;
+
+dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set_client_DH_params_answer;
+dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer;
+dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = Set_client_DH_params_answer;
+
+destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes;
+destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes;
+destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
+
+---functions---
+
+req_pq#60469778 nonce:int128 = ResPQ;
+req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
+
+req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_data:string = Server_DH_Params;
+
+set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:string = Set_client_DH_params_answer;
+
+destroy_auth_key#d1435160 = DestroyAuthKeyRes;
+
+///////////////////////////////
+////////////// System messages
+///////////////////////////////
+
+---types---
+
+msgs_ack#62d6b459 msg_ids:Vector<long> = MsgsAck;
+
+bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int error_code:int = BadMsgNotification;
+bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long = BadMsgNotification;
+
+msgs_state_req#da69fb52 msg_ids:Vector<long> = MsgsStateReq;
+msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
+msgs_all_info#8cc0d131 msg_ids:Vector<long> info:string = MsgsAllInfo;
+
+msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
+msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
+
+msg_resend_req#7d861a08 msg_ids:Vector<long> = MsgResendReq;
+
+//rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult; // parsed manually
+
+rpc_error#2144ca19 error_code:int error_message:string = RpcError;
+
+rpc_answer_unknown#5e2ad36e = RpcDropAnswer;
+rpc_answer_dropped_running#cd78e586 = RpcDropAnswer;
+rpc_answer_dropped#a43ad8b7 msg_id:long seq_no:int bytes:int = RpcDropAnswer;
+
+future_salt#0949d9dc valid_since:int valid_until:int salt:long = FutureSalt;
+future_salts#ae500895 req_msg_id:long now:int salts:vector<future_salt> = FutureSalts;
+
+pong#347773c5 msg_id:long ping_id:long = Pong;
+
+destroy_session_ok#e22045fc session_id:long = DestroySessionRes;
+destroy_session_none#62d350c9 session_id:long = DestroySessionRes;
+
+new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = NewSession;
+
+//message msg_id:long seqno:int bytes:int body:Object = Message; // parsed manually
+//msg_container#73f1f8dc messages:vector<message> = MessageContainer; // parsed manually
+//msg_copy#e06046b2 orig_message:Message = MessageCopy; // parsed manually, not used - use msg_container
+//gzip_packed#3072cfa1 packed_data:string = Object; // parsed manually
+
+http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait;
+
+//ipPort ipv4:int port:int = IpPort;
+//help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector<ipPort> = help.ConfigSimple;
+
+ipPort#d433ad73 ipv4:int port:int = IpPort;
+ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort;
+accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector<IpPort> = AccessPointRule;
+help.configSimple#5a592a6c date:int expires:int rules:vector<AccessPointRule> = help.ConfigSimple;
+
+tlsClientHello blocks:vector<TlsBlock> = TlsClientHello;
+
+tlsBlockString data:string = TlsBlock;
+tlsBlockRandom length:int = TlsBlock;
+tlsBlockZero length:int = TlsBlock;
+tlsBlockDomain = TlsBlock;
+tlsBlockGrease seed:int = TlsBlock;
+tlsBlockScope entries:Vector<TlsBlock> = TlsBlock;
+
+---functions---
+
+rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;
+
+get_future_salts#b921bd04 num:int = FutureSalts;
+
+ping#7abe77ec ping_id:long = Pong;
+ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;
+
+destroy_session#e7512126 session_id:long = DestroySessionRes;

+ 57 - 0
gramjs/tl/types-generator/generate.js

@@ -0,0 +1,57 @@
+const path = require('path')
+const fs = require('fs')
+
+const { parseTl } = require('../generationHelpers')
+const templateFn = require('./template')
+
+const INPUT_FILE = path.resolve(__dirname, '../static/api.tl')
+const SCHEMA_FILE = path.resolve(__dirname, '../static/schema.tl')
+
+const OUTPUT_FILE = path.resolve(__dirname, '../api.d.ts')
+
+function main() {
+    const tlContent = fs.readFileSync(INPUT_FILE, 'utf-8')
+    const apiConfig = extractParams(tlContent)
+    const schemeContent = fs.readFileSync(SCHEMA_FILE, 'utf-8')
+    const schemeConfig = extractParams(schemeContent)
+    const types = [...apiConfig.types, ...schemeConfig.types]
+    const functions = [...apiConfig.functions, ...schemeConfig.functions]
+    const constructors = [...apiConfig.constructors, ...schemeConfig.constructors]
+    const generated = templateFn({ types: types, functions: functions, constructors: constructors })
+
+    fs.writeFileSync(OUTPUT_FILE, generated)
+}
+
+function extractParams(fileContent) {
+    const defInterator = parseTl(fileContent, 109)
+    const types = {}
+    const constructors = []
+    const functions = []
+
+    for (const def of defInterator) {
+        if (def.isFunction) {
+            functions.push(def)
+        } else {
+            if (!types[def.result]) {
+                let [namespace, name] = def.result.includes('.') ? def.result.split('.') : [undefined, def.result]
+
+                types[def.result] = {
+                    namespace,
+                    name,
+                    constructors: []
+                }
+            }
+
+            types[def.result].constructors.push(def.namespace ? `${def.namespace}.${def.name}` : def.name)
+            constructors.push(def)
+        }
+    }
+
+    return {
+        types: Object.values(types),
+        constructors,
+        functions
+    }
+}
+
+main()

+ 219 - 0
gramjs/tl/types-generator/template.js

@@ -0,0 +1,219 @@
+// Not sure what they are for.
+const WEIRD_TYPES = new Set(['Bool', 'X', 'Type'])
+
+module.exports = ({ types, constructors, functions }) => {
+    function groupByKey(collection, key) {
+        return collection.reduce((byKey, member) => {
+            const keyValue = member[key] || '_'
+
+            if (!byKey[keyValue]) {
+                byKey[keyValue] = [member]
+            } else {
+                byKey[keyValue].push(member)
+            }
+
+            return byKey
+        }, {})
+    }
+
+    function renderTypes(types, indent) {
+        return types.map(({ name, constructors }) => `
+      ${!constructors.length ? '// ' : ''}export type Type${upperFirst(name)} = ${constructors.map((name) => name)
+            .join(' | ')};
+    `.trim())
+            .join(`\n${indent}`)
+    }
+
+    function renderConstructors(constructors, indent) {
+        return constructors.map(({ name, argsConfig }) => {
+            const argKeys = Object.keys(argsConfig)
+
+            if (!argKeys.length) {
+                return `export class ${upperFirst(name)} extends VirtualClass<void> {};`
+            }
+
+            let hasRequiredArgs = argKeys.some((argName) => argName !== 'flags' && !argsConfig[argName].isFlag)
+
+            return `
+      export class ${upperFirst(name)} extends VirtualClass<{
+${indent}  ${Object.keys(argsConfig)
+            .map((argName) => `
+        ${renderArg(argName, argsConfig[argName])};
+      `.trim())
+            .join(`\n${indent}  `)}
+${indent}}${!hasRequiredArgs ? ` | void` : ''}> {
+${indent}  ${Object.keys(argsConfig)
+            .map((argName) => `
+        ${renderArg(argName, argsConfig[argName])};
+      `.trim())
+            .join(`\n${indent}  `)}
+${indent}};`.trim()
+        })
+        .join(`\n${indent}`)
+    }
+
+    function renderRequests(requests, indent) {
+        return requests.map(({ name, argsConfig, result }) => {
+            const argKeys = Object.keys(argsConfig)
+
+            if (!argKeys.length) {
+                return `export class ${upperFirst(name)} extends Request<void, ${renderResult(result)}> {};`
+            }
+
+            let hasRequiredArgs = argKeys.some((argName) => argName !== 'flags' && !argsConfig[argName].isFlag)
+
+            return `
+      export class ${upperFirst(name)} extends Request<Partial<{
+${indent}  ${argKeys.map((argName) => `
+        ${renderArg(argName, argsConfig[argName])};
+      `.trim())
+            .join(`\n${indent}  `)}
+${indent}}${!hasRequiredArgs ? ` | void` : ''}>, ${renderResult(result)}> {
+${indent}  ${argKeys.map((argName) => `
+        ${renderArg(argName, argsConfig[argName])};
+      `.trim())
+            .join(`\n${indent}  `)}
+${indent}};`.trim()
+        })
+        .join(`\n${indent}`)
+    }
+
+    function renderResult(result) {
+        const vectorMatch = result.match(/[Vv]ector<([\w\d.]+)>/)
+        const isVector = Boolean(vectorMatch)
+        const scalarValue = isVector ? vectorMatch[1] : result
+        const isTlType = Boolean(scalarValue.match(/^[A-Z]/)) || scalarValue.includes('.')
+
+        return renderValueType(scalarValue, isVector, isTlType)
+    }
+
+    function renderArg(argName, argConfig) {
+        const {
+            isVector, isFlag, skipConstructorId, type
+        } = argConfig
+
+        const valueType = renderValueType(type, isVector, !skipConstructorId)
+
+        return `${argName === 'flags' ? '// ' : ''}${argName}${isFlag ? '?' : ''}: ${valueType}`
+    }
+
+    function renderValueType(type, isVector, isTlType) {
+        if (WEIRD_TYPES.has(type)) {
+            return type
+        }
+
+        let resType
+
+        if (typeof type === 'string' && isTlType) {
+            resType = renderTypeName(type)
+        } else {
+            resType = type
+        }
+
+        if (isVector) {
+            resType = `${resType}[]`
+        }
+
+        return resType
+    }
+
+    function renderTypeName(typeName) {
+        return typeName.includes('.') ? typeName.replace('.', '.Type') : `Api.Type${typeName}`
+    }
+
+    function upperFirst(str) {
+        return `${str[0].toUpperCase()}${str.slice(1)}`
+    }
+
+    const typesByNs = groupByKey(types, 'namespace')
+    const constructorsByNs = groupByKey(constructors, 'namespace')
+    const requestsByNs = groupByKey(functions, 'namespace')
+
+    // language=TypeScript
+    return `
+// This file is autogenerated. All changes will be overwritten.
+
+import { BigInteger } from 'big-integer';
+
+export default Api;
+
+namespace Api {
+
+  type AnyClass = new (...args: any[]) => any;
+  type I<T extends AnyClass> = InstanceType<T>;
+  type ValuesOf<T> = T[keyof T];
+  type AnyLiteral = Record<string, any>;
+
+  type Reader = any; // To be defined.
+  type Client = any; // To be defined.
+  type Utils = any; // To be defined.
+
+  type X = unknown;
+  type Type = unknown;
+  type Bool = boolean;
+  type int = number;
+  type int128 = number;
+  type int256 = number;
+  type long = BigInteger;
+  type bytes = Buffer;
+
+  class VirtualClass<Args extends AnyLiteral> {
+    static CONSTRUCTOR_ID: number;
+    static SUBCLASS_OF_ID: number;
+    static className: string;
+    static classType: 'constructor' | 'request';
+
+    static serializeBytes(data: Buffer | string): Buffer;
+    static serializeDate(date: Date | number): Buffer;
+    static fromReader(reader: Reader): VirtualClass<Args>;
+
+    CONSTRUCTOR_ID: number;
+    SUBCLASS_OF_ID: number;
+    className: string;
+    classType: 'constructor' | 'request';
+
+    constructor(args: Args);
+  }
+
+  class Request<Args, Response> extends VirtualClass<Partial<Args>> {
+    static readResult(reader: Reader): Buffer;
+    static resolve(client: Client, utils: Utils): Promise<void>;
+
+    __response: Response;
+  }
+
+  ${renderTypes(typesByNs._, '  ')}
+  ${Object.keys(typesByNs)
+        .map(namespace => namespace !== '_' ? `
+  export namespace ${namespace} {
+    ${renderTypes(typesByNs[namespace], '    ')}
+  }` : '')
+        .join('\n')}
+
+  ${renderConstructors(constructorsByNs._, '  ')}
+  ${Object.keys(constructorsByNs)
+        .map(namespace => namespace !== '_' ? `
+  export namespace ${namespace} {
+    ${renderConstructors(constructorsByNs[namespace], '    ')}
+  }` : '')
+        .join('\n')}
+
+  ${renderRequests(requestsByNs._, '  ')}
+  ${Object.keys(requestsByNs)
+        .map(namespace => namespace !== '_' ? `
+  export namespace ${namespace} {
+    ${renderRequests(requestsByNs[namespace], '    ')}
+  }` : '')
+        .join('\n')}
+
+  export type AnyRequest = ${requestsByNs._.map(({ name }) => upperFirst(name))
+        .join(' | ')}
+    | ${Object.keys(requestsByNs)
+        .filter(ns => ns !== '_')
+        .map(ns => requestsByNs[ns].map(({ name }) => `${ns}.${upperFirst(name)}`)
+            .join(' | '))
+        .join('\n    | ')};
+
+}
+`
+}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 489 - 338
package-lock.json


+ 41 - 32
package.json

@@ -5,8 +5,10 @@
   "main": "index.js",
   "scripts": {
     "test": "jest",
+    "postinstall": "npm run tsc",
+    "tsc": "tsc",
     "lint": "eslint .",
-    "postinstall": "node index.js gen tl errors"
+    "generate": "node ./gramjs/tl/types-generator/generate.js"
   },
   "repository": {
     "type": "git",
@@ -20,38 +22,45 @@
     "url": "https://github.com/gram-js/gramjs/issues"
   },
   "homepage": "https://github.com/gram-js/gramjs#readme",
-  "dependencies": {
-    "@babel/core": "^7.7.7",
-    "@babel/plugin-proposal-class-properties": "^7.7.0",
-    "@babel/preset-env": "^7.7.7",
-    "aes-js": "^3.1.2",
-    "babel-loader": "^8.0.6",
-    "babel-register": "^6.26.0",
-    "better-sqlite3": "^5.4.3",
-    "chalk": "^2.4.2",
-    "crc": "^3.8.0",
-    "csv-parse": "^4.4.6",
-    "fast-csv": "^3.4.0",
-    "glob": "^7.1.4",
-    "mime-types": "^2.1.25",
-    "net": "^1.0.2",
-    "node-gzip": "^1.1.2",
-    "node-rsa": "^1.0.6",
-    "python-struct": "^1.1.1",
-    "regenerator-runtime": "^0.13.3",
-    "sqlite3": "^4.1.1",
-    "stack-trace": "0.0.10",
-    "string-format": "^2.0.0",
-    "websocket": "^1.0.30"
-  },
   "devDependencies": {
-    "@babel/plugin-transform-async-to-generator": "^7.7.0",
+    "@babel/core": "^7.10.2",
+    "@babel/plugin-proposal-class-properties": "^7.7.0",
+    "@babel/preset-env": "^7.10.2",
+    "@babel/preset-react": "^7.10.1",
+    "@babel/preset-typescript": "^7.10.1",
+    "@types/croppie": "^2.5.4",
+    "@types/css-font-loading-module": "0.0.2",
+    "@types/dom-mediacapture-record": "^1.0.3",
+    "@types/resize-observer-browser": "^0.1.3",
+    "@typescript-eslint/eslint-plugin": "^2.6.1",
+    "@typescript-eslint/parser": "^2.6.1",
+    "autoprefixer": "^9.8.0",
     "babel-eslint": "^10.0.3",
-    "babel-jest": "^24.9.0",
-    "eslint": "^6.5.1",
-    "jest": "^24.9.0",
-    "webpack": "^4.41.2",
-    "webpack-cli": "^3.3.10",
-    "websocket-as-promised": "^0.10.1"
+    "babel-loader": "^8.1.0",
+    "eslint": "^7.2.0",
+    "eslint-config-airbnb-typescript": "^6.0.0",
+    "eslint-config-react-app": "^5.0.2",
+    "eslint-import-resolver-webpack": "^0.12.1",
+    "eslint-plugin-flowtype": "^3.13.0",
+    "eslint-plugin-import": "^2.20.2",
+    "eslint-plugin-jsx-a11y": "^6.2.3",
+    "eslint-plugin-no-async-without-await": "^1.2.0",
+    "lint-staged": "^9.4.2",
+    "raw-loader": "^4.0.1",
+    "ts-loader": "^8.0.9",
+    "ts-node": "^8.9.1",
+    "typescript": "^3.9.7",
+    "url-loader": "^4.1.0",
+    "webpack": "^4.43.0",
+    "webpack-cli": "^3.3.11"
+  },
+  "dependencies": {
+    "@cryptography/aes": "^0.1.1",
+    "async-mutex": "^0.1.4",
+    "big-integer": "painor/BigInteger.js",
+    "browser-or-node": "^1.3.0",
+    "jest": "^26.6.3",
+    "pako": "^1.0.10",
+    "websocket": "^1.0.30"
   }
 }

+ 1500 - 0
static/api.tl

@@ -0,0 +1,1500 @@
+///////////////////////////////
+/////////////////// Layer cons
+///////////////////////////////
+
+//invokeAfterMsg#cb9f372d msg_id:long query:!X = X;
+//invokeAfterMsgs#3dc4b4f0 msg_ids:Vector<long> query:!X = X;
+//invokeWithLayer1#53835315 query:!X = X;
+//invokeWithLayer2#289dd1f6 query:!X = X;
+//invokeWithLayer3#b7475268 query:!X = X;
+//invokeWithLayer4#dea0d430 query:!X = X;
+//invokeWithLayer5#417a57ae query:!X = X;
+//invokeWithLayer6#3a64d54d query:!X = X;
+//invokeWithLayer7#a5be56d3 query:!X = X;
+//invokeWithLayer8#e9abd9fd query:!X = X;
+//invokeWithLayer9#76715a63 query:!X = X;
+//invokeWithLayer10#39620c41 query:!X = X;
+//invokeWithLayer11#a6b88fdf query:!X = X;
+//invokeWithLayer12#dda60d3c query:!X = X;
+//invokeWithLayer13#427c8ea2 query:!X = X;
+//invokeWithLayer14#2b9b08fa query:!X = X;
+//invokeWithLayer15#b4418b64 query:!X = X;
+//invokeWithLayer16#cf5f0987 query:!X = X;
+//invokeWithLayer17#50858a19 query:!X = X;
+//invokeWithLayer18#1c900537 query:!X = X;
+//invokeWithLayer#da9b0d0d layer:int query:!X = X; // after 18 layer
+
+///////////////////////////////
+///////// Main application API
+///////////////////////////////
+
+boolFalse#bc799737 = Bool;
+boolTrue#997275b5 = Bool;
+
+true#3fedd339 = True;
+
+vector#1cb5c415 {t:Type} # [ t ] = Vector t;
+
+error#c4b9f9bb code:int text:string = Error;
+
+null#56730bcc = Null;
+
+inputPeerEmpty#7f3b18ea = InputPeer;
+inputPeerSelf#7da07ec9 = InputPeer;
+inputPeerChat#179be863 chat_id:int = InputPeer;
+inputPeerUser#7b8e7de6 user_id:int access_hash:long = InputPeer;
+inputPeerChannel#20adaef8 channel_id:int access_hash:long = InputPeer;
+inputPeerUserFromMessage#17bae2e6 peer:InputPeer msg_id:int user_id:int = InputPeer;
+inputPeerChannelFromMessage#9c95f7bb peer:InputPeer msg_id:int channel_id:int = InputPeer;
+
+inputUserEmpty#b98886cf = InputUser;
+inputUserSelf#f7c1b13f = InputUser;
+inputUser#d8292816 user_id:int access_hash:long = InputUser;
+inputUserFromMessage#2d117597 peer:InputPeer msg_id:int user_id:int = InputUser;
+
+inputPhoneContact#f392b7f4 client_id:long phone:string first_name:string last_name:string = InputContact;
+
+inputFile#f52ff27f id:long parts:int name:string md5_checksum:string = InputFile;
+inputFileBig#fa4f0bb5 id:long parts:int name:string = InputFile;
+
+inputMediaEmpty#9664f57f = InputMedia;
+inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
+inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia;
+inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
+inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia;
+inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
+inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia;
+inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia;
+inputMediaGifExternal#4843b0fd url:string q:string = InputMedia;
+inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
+inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
+inputMediaGame#d33f43f3 id:InputGame = InputMedia;
+inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
+inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
+inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> solution:flags.1?string solution_entities:flags.1?Vector<MessageEntity> = InputMedia;
+inputMediaDice#e66fbf7b emoticon:string = InputMedia;
+
+inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
+inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
+inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto;
+
+inputGeoPointEmpty#e4c123d6 = InputGeoPoint;
+inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint;
+
+inputPhotoEmpty#1cd7bf0d = InputPhoto;
+inputPhoto#3bb3b94a id:long access_hash:long file_reference:bytes = InputPhoto;
+
+inputFileLocation#dfdaabe1 volume_id:long local_id:int secret:long file_reference:bytes = InputFileLocation;
+inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation;
+inputDocumentFileLocation#bad07584 id:long access_hash:long file_reference:bytes thumb_size:string = InputFileLocation;
+inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation;
+inputTakeoutFileLocation#29be5899 = InputFileLocation;
+inputPhotoFileLocation#40181ffe id:long access_hash:long file_reference:bytes thumb_size:string = InputFileLocation;
+inputPhotoLegacyFileLocation#d83466f3 id:long access_hash:long file_reference:bytes volume_id:long local_id:int secret:long = InputFileLocation;
+inputPeerPhotoFileLocation#27d69997 flags:# big:flags.0?true peer:InputPeer volume_id:long local_id:int = InputFileLocation;
+inputStickerSetThumb#dbaeae9 stickerset:InputStickerSet volume_id:long local_id:int = InputFileLocation;
+
+peerUser#9db1bc6d user_id:int = Peer;
+peerChat#bad0e5bb chat_id:int = Peer;
+peerChannel#bddde532 channel_id:int = Peer;
+
+storage.fileUnknown#aa963b05 = storage.FileType;
+storage.filePartial#40bc6f52 = storage.FileType;
+storage.fileJpeg#7efe0e = storage.FileType;
+storage.fileGif#cae1aadf = storage.FileType;
+storage.filePng#a4f63c0 = storage.FileType;
+storage.filePdf#ae1e508d = storage.FileType;
+storage.fileMp3#528a0677 = storage.FileType;
+storage.fileMov#4b09ebbc = storage.FileType;
+storage.fileMp4#b3cea0e4 = storage.FileType;
+storage.fileWebp#1081464c = storage.FileType;
+
+userEmpty#200250ba id:int = User;
+user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
+
+userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
+userProfilePhoto#ecd75d8c photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto;
+
+userStatusEmpty#9d05049 = UserStatus;
+userStatusOnline#edb93949 expires:int = UserStatus;
+userStatusOffline#8c703f was_online:int = UserStatus;
+userStatusRecently#e26f42f1 = UserStatus;
+userStatusLastWeek#7bf09fc = UserStatus;
+userStatusLastMonth#77ebc742 = UserStatus;
+
+chatEmpty#9ba2d800 id:int = Chat;
+chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
+chatForbidden#7328bdb id:int title:string = Chat;
+channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
+channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
+
+chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull;
+channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull;
+
+chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
+chatParticipantCreator#da13538a user_id:int = ChatParticipant;
+chatParticipantAdmin#e2d6e436 user_id:int inviter_id:int date:int = ChatParticipant;
+
+chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0?ChatParticipant = ChatParticipants;
+chatParticipants#3f460fed chat_id:int participants:Vector<ChatParticipant> version:int = ChatParticipants;
+
+chatPhotoEmpty#37c1011c = ChatPhoto;
+chatPhoto#475cdbd5 photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto;
+
+messageEmpty#83e5de54 id:int = Message;
+message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;
+messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message;
+
+messageMediaEmpty#3ded6320 = MessageMedia;
+messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia;
+messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia;
+messageMediaContact#cbf24940 phone_number:string first_name:string last_name:string vcard:string user_id:int = MessageMedia;
+messageMediaUnsupported#9f84f49e = MessageMedia;
+messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia;
+messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
+messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia;
+messageMediaGame#fdb19008 game:Game = MessageMedia;
+messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
+messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia;
+messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia;
+messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia;
+
+messageActionEmpty#b6aef7b0 = MessageAction;
+messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction;
+messageActionChatEditTitle#b5a1ce5a title:string = MessageAction;
+messageActionChatEditPhoto#7fcb13a8 photo:Photo = MessageAction;
+messageActionChatDeletePhoto#95e3fbef = MessageAction;
+messageActionChatAddUser#488a7337 users:Vector<int> = MessageAction;
+messageActionChatDeleteUser#b2ae9b0c user_id:int = MessageAction;
+messageActionChatJoinedByLink#f89cf5e8 inviter_id:int = MessageAction;
+messageActionChannelCreate#95d2ac92 title:string = MessageAction;
+messageActionChatMigrateTo#51bdb021 channel_id:int = MessageAction;
+messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageAction;
+messageActionPinMessage#94bd38ed = MessageAction;
+messageActionHistoryClear#9fbab604 = MessageAction;
+messageActionGameScore#92a72876 game_id:long score:int = MessageAction;
+messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction;
+messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction;
+messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction;
+messageActionScreenshotTaken#4792929b = MessageAction;
+messageActionCustomAction#fae69f56 message:string = MessageAction;
+messageActionBotAllowed#abe9affe domain:string = MessageAction;
+messageActionSecureValuesSentMe#1b287353 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted = MessageAction;
+messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = MessageAction;
+messageActionContactSignUp#f3f25f76 = MessageAction;
+
+dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
+dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
+
+photoEmpty#2331b22d id:long = Photo;
+photo#d07504a5 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector<PhotoSize> dc_id:int = Photo;
+
+photoSizeEmpty#e17e23c type:string = PhotoSize;
+photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
+photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
+photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize;
+
+geoPointEmpty#1117dd5f = GeoPoint;
+geoPoint#296f104 long:double lat:double access_hash:long = GeoPoint;
+
+auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode;
+
+auth.authorization#cd050916 flags:# tmp_sessions:flags.0?int user:User = auth.Authorization;
+auth.authorizationSignUpRequired#44747e9a flags:# terms_of_service:flags.0?help.TermsOfService = auth.Authorization;
+
+auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorization;
+
+inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer;
+inputNotifyUsers#193b4417 = InputNotifyPeer;
+inputNotifyChats#4a95e84e = InputNotifyPeer;
+inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer;
+
+inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings;
+
+peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
+
+peerSettings#818426cd flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true = PeerSettings;
+
+wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper;
+wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
+
+inputReportReasonSpam#58dbcab8 = ReportReason;
+inputReportReasonViolence#1e22c78d = ReportReason;
+inputReportReasonPornography#2e59d922 = ReportReason;
+inputReportReasonChildAbuse#adf44ee3 = ReportReason;
+inputReportReasonOther#e1746d0a text:string = ReportReason;
+inputReportReasonCopyright#9b89f93a = ReportReason;
+inputReportReasonGeoIrrelevant#dbd4feed = ReportReason;
+
+userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull;
+
+contact#f911c994 user_id:int mutual:Bool = Contact;
+
+importedContact#d0028438 user_id:int client_id:long = ImportedContact;
+
+contactBlocked#561bc879 user_id:int date:int = ContactBlocked;
+
+contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus;
+
+contacts.contactsNotModified#b74ba9d2 = contacts.Contacts;
+contacts.contacts#eae87e42 contacts:Vector<Contact> saved_count:int users:Vector<User> = contacts.Contacts;
+
+contacts.importedContacts#77d01c3b imported:Vector<ImportedContact> popular_invites:Vector<PopularContact> retry_contacts:Vector<long> users:Vector<User> = contacts.ImportedContacts;
+
+contacts.blocked#1c138d15 blocked:Vector<ContactBlocked> users:Vector<User> = contacts.Blocked;
+contacts.blockedSlice#900802a1 count:int blocked:Vector<ContactBlocked> users:Vector<User> = contacts.Blocked;
+
+messages.dialogs#15ba6c40 dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Dialogs;
+messages.dialogsSlice#71e094f3 count:int dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Dialogs;
+messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs;
+
+messages.messages#8c718e87 messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
+messages.messagesSlice#c8edce1e flags:# inexact:flags.1?true count:int next_rate:flags.0?int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
+messages.channelMessages#99262e37 flags:# inexact:flags.1?true pts:int count:int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
+messages.messagesNotModified#74535f21 count:int = messages.Messages;
+
+messages.chats#64ff9fd5 chats:Vector<Chat> = messages.Chats;
+messages.chatsSlice#9cd81144 count:int chats:Vector<Chat> = messages.Chats;
+
+messages.chatFull#e5d7d19c full_chat:ChatFull chats:Vector<Chat> users:Vector<User> = messages.ChatFull;
+
+messages.affectedHistory#b45c69d1 pts:int pts_count:int offset:int = messages.AffectedHistory;
+
+inputMessagesFilterEmpty#57e2f66c = MessagesFilter;
+inputMessagesFilterPhotos#9609a51c = MessagesFilter;
+inputMessagesFilterVideo#9fc00e65 = MessagesFilter;
+inputMessagesFilterPhotoVideo#56e9f0e4 = MessagesFilter;
+inputMessagesFilterDocument#9eddf188 = MessagesFilter;
+inputMessagesFilterUrl#7ef0dd87 = MessagesFilter;
+inputMessagesFilterGif#ffc86587 = MessagesFilter;
+inputMessagesFilterVoice#50f5c392 = MessagesFilter;
+inputMessagesFilterMusic#3751b49e = MessagesFilter;
+inputMessagesFilterChatPhotos#3a20ecb8 = MessagesFilter;
+inputMessagesFilterPhoneCalls#80c99768 flags:# missed:flags.0?true = MessagesFilter;
+inputMessagesFilterRoundVoice#7a7c17a4 = MessagesFilter;
+inputMessagesFilterRoundVideo#b549da53 = MessagesFilter;
+inputMessagesFilterMyMentions#c1f8e69a = MessagesFilter;
+inputMessagesFilterGeo#e7026d0d = MessagesFilter;
+inputMessagesFilterContacts#e062db83 = MessagesFilter;
+
+updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
+updateMessageID#4e90bfd6 id:int random_id:long = Update;
+updateDeleteMessages#a20db0e5 messages:Vector<int> pts:int pts_count:int = Update;
+updateUserTyping#5c486927 user_id:int action:SendMessageAction = Update;
+updateChatUserTyping#9a65ea1f chat_id:int user_id:int action:SendMessageAction = Update;
+updateChatParticipants#7761198 participants:ChatParticipants = Update;
+updateUserStatus#1bfbd823 user_id:int status:UserStatus = Update;
+updateUserName#a7332b73 user_id:int first_name:string last_name:string username:string = Update;
+updateUserPhoto#95313b0c user_id:int date:int photo:UserProfilePhoto previous:Bool = Update;
+updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update;
+updateEncryptedChatTyping#1710f156 chat_id:int = Update;
+updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update;
+updateEncryptedMessagesRead#38fe25b7 chat_id:int max_date:int date:int = Update;
+updateChatParticipantAdd#ea4b0e5c chat_id:int user_id:int inviter_id:int date:int version:int = Update;
+updateChatParticipantDelete#6e5f8c22 chat_id:int user_id:int version:int = Update;
+updateDcOptions#8e5e9873 dc_options:Vector<DcOption> = Update;
+updateUserBlocked#80ece81a user_id:int blocked:Bool = Update;
+updateNotifySettings#bec268ef peer:NotifyPeer notify_settings:PeerNotifySettings = Update;
+updateServiceNotification#ebe46819 flags:# popup:flags.0?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector<MessageEntity> = Update;
+updatePrivacy#ee3b272a key:PrivacyKey rules:Vector<PrivacyRule> = Update;
+updateUserPhone#12b9417b user_id:int phone:string = Update;
+updateReadHistoryInbox#9c974fdf flags:# folder_id:flags.0?int peer:Peer max_id:int still_unread_count:int pts:int pts_count:int = Update;
+updateReadHistoryOutbox#2f2f21bf peer:Peer max_id:int pts:int pts_count:int = Update;
+updateWebPage#7f891213 webpage:WebPage pts:int pts_count:int = Update;
+updateReadMessagesContents#68c13933 messages:Vector<int> pts:int pts_count:int = Update;
+updateChannelTooLong#eb0467fb flags:# channel_id:int pts:flags.0?int = Update;
+updateChannel#b6d45656 channel_id:int = Update;
+updateNewChannelMessage#62ba04d9 message:Message pts:int pts_count:int = Update;
+updateReadChannelInbox#330b5424 flags:# folder_id:flags.0?int channel_id:int max_id:int still_unread_count:int pts:int = Update;
+updateDeleteChannelMessages#c37521c9 channel_id:int messages:Vector<int> pts:int pts_count:int = Update;
+updateChannelMessageViews#98a12b4b channel_id:int id:int views:int = Update;
+updateChatParticipantAdmin#b6901959 chat_id:int user_id:int is_admin:Bool version:int = Update;
+updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update;
+updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector<long> = Update;
+updateStickerSets#43ae3dec = Update;
+updateSavedGifs#9375341e = Update;
+updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint offset:string = Update;
+updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update;
+updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update;
+updateChannelPinnedMessage#98592475 channel_id:int id:int = Update;
+updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
+updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update;
+updateInlineBotCallbackQuery#f9d27a5a flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
+updateReadChannelOutbox#25d6c9c7 channel_id:int max_id:int = Update;
+updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update;
+updateReadFeaturedStickers#571d2742 = Update;
+updateRecentStickers#9a422c20 = Update;
+updateConfig#a229dd06 = Update;
+updatePtsChanged#3354678f = Update;
+updateChannelWebPage#40771900 channel_id:int webpage:WebPage pts:int pts_count:int = Update;
+updateDialogPinned#6e6fe51c flags:# pinned:flags.0?true folder_id:flags.1?int peer:DialogPeer = Update;
+updatePinnedDialogs#fa0f3ca2 flags:# folder_id:flags.1?int order:flags.0?Vector<DialogPeer> = Update;
+updateBotWebhookJSON#8317c0c3 data:DataJSON = Update;
+updateBotWebhookJSONQuery#9b9240a6 query_id:long data:DataJSON timeout:int = Update;
+updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update;
+updateBotPrecheckoutQuery#5d2f3aa9 flags:# query_id:long user_id:int payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update;
+updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update;
+updateLangPackTooLong#46560264 lang_code:string = Update;
+updateLangPack#56022f4d difference:LangPackDifference = Update;
+updateFavedStickers#e511996d = Update;
+updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector<int> = Update;
+updateContactsReset#7084a7be = Update;
+updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update;
+updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update;
+updateUserPinnedMessage#4c43da18 user_id:int id:int = Update;
+updateChatPinnedMessage#e10db349 chat_id:int id:int version:int = Update;
+updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update;
+updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update;
+updateFolderPeers#19360dc0 folder_peers:Vector<FolderPeer> pts:int pts_count:int = Update;
+updatePeerSettings#6a7e7366 peer:Peer settings:PeerSettings = Update;
+updatePeerLocated#b4afcfb0 peers:Vector<PeerLocated> = Update;
+updateNewScheduledMessage#39a51dfb message:Message = Update;
+updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector<int> = Update;
+updateTheme#8216fba3 theme:Theme = Update;
+updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update;
+updateLoginToken#564fe691 = Update;
+updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> = Update;
+updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update;
+updateDialogFilterOrder#a5d72105 order:Vector<int> = Update;
+updateDialogFilters#3504914f = Update;
+
+updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
+
+updates.differenceEmpty#5d75a138 date:int seq:int = updates.Difference;
+updates.difference#f49ca0 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> state:updates.State = updates.Difference;
+updates.differenceSlice#a8fb1981 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> intermediate_state:updates.State = updates.Difference;
+updates.differenceTooLong#4afe8f6d pts:int = updates.Difference;
+
+updatesTooLong#e317af7e = Updates;
+updateShortMessage#914fbf11 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector<MessageEntity> = Updates;
+updateShortChatMessage#16812688 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector<MessageEntity> = Updates;
+updateShort#78d4dec1 update:Update date:int = Updates;
+updatesCombined#725b04c3 updates:Vector<Update> users:Vector<User> chats:Vector<Chat> date:int seq_start:int seq:int = Updates;
+updates#74ae4240 updates:Vector<Update> users:Vector<User> chats:Vector<Chat> date:int seq:int = Updates;
+updateShortSentMessage#11f1331c flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector<MessageEntity> = Updates;
+
+photos.photos#8dca6aa5 photos:Vector<Photo> users:Vector<User> = photos.Photos;
+photos.photosSlice#15051f54 count:int photos:Vector<Photo> users:Vector<User> = photos.Photos;
+
+photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
+
+upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
+upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector<FileHash> = upload.File;
+
+dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
+
+config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config;
+
+nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
+
+help.appUpdate#1da7158f flags:# can_not_skip:flags.0?true id:int version:string text:string entities:Vector<MessageEntity> document:flags.1?Document url:flags.2?string = help.AppUpdate;
+help.noAppUpdate#c45a6536 = help.AppUpdate;
+
+help.inviteText#18cb9f78 message:string = help.InviteText;
+
+encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat;
+encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat;
+encryptedChatRequested#c878527e id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat;
+encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat;
+encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat;
+
+inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat;
+
+encryptedFileEmpty#c21f497e = EncryptedFile;
+encryptedFile#4a70994c id:long access_hash:long size:int dc_id:int key_fingerprint:int = EncryptedFile;
+
+inputEncryptedFileEmpty#1837c364 = InputEncryptedFile;
+inputEncryptedFileUploaded#64bd0306 id:long parts:int md5_checksum:string key_fingerprint:int = InputEncryptedFile;
+inputEncryptedFile#5a17b5e5 id:long access_hash:long = InputEncryptedFile;
+inputEncryptedFileBigUploaded#2dc173c8 id:long parts:int key_fingerprint:int = InputEncryptedFile;
+
+encryptedMessage#ed18c118 random_id:long chat_id:int date:int bytes:bytes file:EncryptedFile = EncryptedMessage;
+encryptedMessageService#23734b06 random_id:long chat_id:int date:int bytes:bytes = EncryptedMessage;
+
+messages.dhConfigNotModified#c0e24635 random:bytes = messages.DhConfig;
+messages.dhConfig#2c221edd g:int p:bytes version:int random:bytes = messages.DhConfig;
+
+messages.sentEncryptedMessage#560f8935 date:int = messages.SentEncryptedMessage;
+messages.sentEncryptedFile#9493ff32 date:int file:EncryptedFile = messages.SentEncryptedMessage;
+
+inputDocumentEmpty#72f0eaae = InputDocument;
+inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument;
+
+documentEmpty#36f8c871 id:long = Document;
+document#9ba29cc1 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector<PhotoSize> dc_id:int attributes:Vector<DocumentAttribute> = Document;
+
+help.support#17c6b5f6 phone_number:string user:User = help.Support;
+
+notifyPeer#9fd40bd8 peer:Peer = NotifyPeer;
+notifyUsers#b4c83b4c = NotifyPeer;
+notifyChats#c007cec3 = NotifyPeer;
+notifyBroadcasts#d612e8ef = NotifyPeer;
+
+sendMessageTypingAction#16bf744e = SendMessageAction;
+sendMessageCancelAction#fd5ec8f5 = SendMessageAction;
+sendMessageRecordVideoAction#a187d66f = SendMessageAction;
+sendMessageUploadVideoAction#e9763aec progress:int = SendMessageAction;
+sendMessageRecordAudioAction#d52f73f7 = SendMessageAction;
+sendMessageUploadAudioAction#f351d7ab progress:int = SendMessageAction;
+sendMessageUploadPhotoAction#d1d34a26 progress:int = SendMessageAction;
+sendMessageUploadDocumentAction#aa0cd9e4 progress:int = SendMessageAction;
+sendMessageGeoLocationAction#176f8ba1 = SendMessageAction;
+sendMessageChooseContactAction#628cbc6f = SendMessageAction;
+sendMessageGamePlayAction#dd6a8f48 = SendMessageAction;
+sendMessageRecordRoundAction#88f27fbc = SendMessageAction;
+sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction;
+
+contacts.found#b3134d9d my_results:Vector<Peer> results:Vector<Peer> chats:Vector<Chat> users:Vector<User> = contacts.Found;
+
+inputPrivacyKeyStatusTimestamp#4f96cb18 = InputPrivacyKey;
+inputPrivacyKeyChatInvite#bdfb0426 = InputPrivacyKey;
+inputPrivacyKeyPhoneCall#fabadc5f = InputPrivacyKey;
+inputPrivacyKeyPhoneP2P#db9e70d2 = InputPrivacyKey;
+inputPrivacyKeyForwards#a4dd4c08 = InputPrivacyKey;
+inputPrivacyKeyProfilePhoto#5719bacc = InputPrivacyKey;
+inputPrivacyKeyPhoneNumber#352dafa = InputPrivacyKey;
+inputPrivacyKeyAddedByPhone#d1219bdd = InputPrivacyKey;
+
+privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey;
+privacyKeyChatInvite#500e6dfa = PrivacyKey;
+privacyKeyPhoneCall#3d662b7b = PrivacyKey;
+privacyKeyPhoneP2P#39491cc8 = PrivacyKey;
+privacyKeyForwards#69ec56a3 = PrivacyKey;
+privacyKeyProfilePhoto#96151fed = PrivacyKey;
+privacyKeyPhoneNumber#d19ae46d = PrivacyKey;
+privacyKeyAddedByPhone#42ffd42b = PrivacyKey;
+
+inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule;
+inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule;
+inputPrivacyValueAllowUsers#131cc67f users:Vector<InputUser> = InputPrivacyRule;
+inputPrivacyValueDisallowContacts#ba52007 = InputPrivacyRule;
+inputPrivacyValueDisallowAll#d66b66c9 = InputPrivacyRule;
+inputPrivacyValueDisallowUsers#90110467 users:Vector<InputUser> = InputPrivacyRule;
+inputPrivacyValueAllowChatParticipants#4c81c1ba chats:Vector<int> = InputPrivacyRule;
+inputPrivacyValueDisallowChatParticipants#d82363af chats:Vector<int> = InputPrivacyRule;
+
+privacyValueAllowContacts#fffe1bac = PrivacyRule;
+privacyValueAllowAll#65427b82 = PrivacyRule;
+privacyValueAllowUsers#4d5bbe0c users:Vector<int> = PrivacyRule;
+privacyValueDisallowContacts#f888fa1a = PrivacyRule;
+privacyValueDisallowAll#8b73e763 = PrivacyRule;
+privacyValueDisallowUsers#c7f49b7 users:Vector<int> = PrivacyRule;
+privacyValueAllowChatParticipants#18be796b chats:Vector<int> = PrivacyRule;
+privacyValueDisallowChatParticipants#acae0690 chats:Vector<int> = PrivacyRule;
+
+account.privacyRules#50a04e45 rules:Vector<PrivacyRule> chats:Vector<Chat> users:Vector<User> = account.PrivacyRules;
+
+accountDaysTTL#b8d0afdf days:int = AccountDaysTTL;
+
+documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute;
+documentAttributeAnimated#11b58939 = DocumentAttribute;
+documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute;
+documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_streaming:flags.1?true duration:int w:int h:int = DocumentAttribute;
+documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
+documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
+documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
+
+messages.stickersNotModified#f1749a22 = messages.Stickers;
+messages.stickers#e4599bbd hash:int stickers:Vector<Document> = messages.Stickers;
+
+stickerPack#12b299d4 emoticon:string documents:Vector<long> = StickerPack;
+
+messages.allStickersNotModified#e86602c3 = messages.AllStickers;
+messages.allStickers#edfd405f hash:int sets:Vector<StickerSet> = messages.AllStickers;
+
+messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMessages;
+
+webPageEmpty#eb1477e8 id:long = WebPage;
+webPagePending#c586da1c id:long date:int = WebPage;
+webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector<WebPageAttribute> = WebPage;
+webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage;
+
+authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization;
+
+account.authorizations#1250abde authorizations:Vector<Authorization> = account.Authorizations;
+
+account.password#ad2641f8 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes = account.Password;
+
+account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings;
+
+account.passwordInputSettings#c23727c9 flags:# new_algo:flags.0?PasswordKdfAlgo new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_settings:flags.2?SecureSecretSettings = account.PasswordInputSettings;
+
+auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
+
+receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage;
+
+chatInviteEmpty#69df3769 = ExportedChatInvite;
+chatInviteExported#fc2e05bc link:string = ExportedChatInvite;
+
+chatInviteAlready#5a686d7c chat:Chat = ChatInvite;
+chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
+
+inputStickerSetEmpty#ffb62b95 = InputStickerSet;
+inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet;
+inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
+inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
+inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
+
+stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet;
+
+messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
+
+botCommand#c27ac8c7 command:string description:string = BotCommand;
+
+botInfo#98e81d3a user_id:int description:string commands:Vector<BotCommand> = BotInfo;
+
+keyboardButton#a2fa4880 text:string = KeyboardButton;
+keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton;
+keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton;
+keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton;
+keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton;
+keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton;
+keyboardButtonGame#50f41ccf text:string = KeyboardButton;
+keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
+keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton;
+inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton;
+keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
+
+keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
+
+replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup;
+replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags.2?true = ReplyMarkup;
+replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> = ReplyMarkup;
+replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;
+
+messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity;
+messageEntityMention#fa04579d offset:int length:int = MessageEntity;
+messageEntityHashtag#6f635b0d offset:int length:int = MessageEntity;
+messageEntityBotCommand#6cef8ac7 offset:int length:int = MessageEntity;
+messageEntityUrl#6ed02538 offset:int length:int = MessageEntity;
+messageEntityEmail#64e475c2 offset:int length:int = MessageEntity;
+messageEntityBold#bd610bc9 offset:int length:int = MessageEntity;
+messageEntityItalic#826f8b60 offset:int length:int = MessageEntity;
+messageEntityCode#28a20571 offset:int length:int = MessageEntity;
+messageEntityPre#73924be0 offset:int length:int language:string = MessageEntity;
+messageEntityTextUrl#76a6d327 offset:int length:int url:string = MessageEntity;
+messageEntityMentionName#352dca58 offset:int length:int user_id:int = MessageEntity;
+inputMessageEntityMentionName#208e68c9 offset:int length:int user_id:InputUser = MessageEntity;
+messageEntityPhone#9b69e34b offset:int length:int = MessageEntity;
+messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity;
+messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity;
+messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity;
+messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity;
+messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity;
+
+inputChannelEmpty#ee8c1e86 = InputChannel;
+inputChannel#afeb712e channel_id:int access_hash:long = InputChannel;
+inputChannelFromMessage#2a286531 peer:InputPeer msg_id:int channel_id:int = InputChannel;
+
+contacts.resolvedPeer#7f077ad9 peer:Peer chats:Vector<Chat> users:Vector<User> = contacts.ResolvedPeer;
+
+messageRange#ae30253 min_id:int max_id:int = MessageRange;
+
+updates.channelDifferenceEmpty#3e11affb flags:# final:flags.0?true pts:int timeout:flags.1?int = updates.ChannelDifference;
+updates.channelDifferenceTooLong#a4bcc6fe flags:# final:flags.0?true timeout:flags.1?int dialog:Dialog messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = updates.ChannelDifference;
+updates.channelDifference#2064674e flags:# final:flags.0?true pts:int timeout:flags.1?int new_messages:Vector<Message> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> = updates.ChannelDifference;
+
+channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter;
+channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector<MessageRange> = ChannelMessagesFilter;
+
+channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant;
+channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant;
+channelParticipantCreator#808d15a4 flags:# user_id:int rank:flags.0?string = ChannelParticipant;
+channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant;
+channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant;
+
+channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter;
+channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter;
+channelParticipantsKicked#a3b54985 q:string = ChannelParticipantsFilter;
+channelParticipantsBots#b0d1865b = ChannelParticipantsFilter;
+channelParticipantsBanned#1427a5e1 q:string = ChannelParticipantsFilter;
+channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter;
+channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter;
+
+channels.channelParticipants#f56ee2a8 count:int participants:Vector<ChannelParticipant> users:Vector<User> = channels.ChannelParticipants;
+channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants;
+
+channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector<User> = channels.ChannelParticipant;
+
+help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector<MessageEntity> min_age_confirm:flags.1?int = help.TermsOfService;
+
+foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif;
+foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif;
+
+messages.foundGifs#450a1c0a next_offset:int results:Vector<FoundGif> = messages.FoundGifs;
+
+messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs;
+messages.savedGifs#2e0709a5 hash:int gifs:Vector<Document> = messages.SavedGifs;
+
+inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+
+inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult;
+inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult;
+inputBotInlineResultDocument#fff8fdc4 flags:# id:string type:string title:flags.1?string description:flags.2?string document:InputDocument send_message:InputBotInlineMessage = InputBotInlineResult;
+inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult;
+
+botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+
+botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult;
+botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult;
+
+messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM results:Vector<BotInlineResult> cache_time:int users:Vector<User> = messages.BotResults;
+
+exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink;
+
+messageFwdHeader#ec338270 flags:# from_id:flags.0?int from_name:flags.5?string date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int = MessageFwdHeader;
+
+auth.codeTypeSms#72a3158c = auth.CodeType;
+auth.codeTypeCall#741cd3e3 = auth.CodeType;
+auth.codeTypeFlashCall#226ccefb = auth.CodeType;
+
+auth.sentCodeTypeApp#3dbb5986 length:int = auth.SentCodeType;
+auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType;
+auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType;
+auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType;
+
+messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer;
+
+messages.messageEditData#26b5dde6 flags:# caption:flags.0?true = messages.MessageEditData;
+
+inputBotInlineMessageID#890c3d89 dc_id:int id:long access_hash:long = InputBotInlineMessageID;
+
+inlineBotSwitchPM#3c20629f text:string start_param:string = InlineBotSwitchPM;
+
+messages.peerDialogs#3371c354 dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> state:updates.State = messages.PeerDialogs;
+
+topPeer#edcdc05b peer:Peer rating:double = TopPeer;
+
+topPeerCategoryBotsPM#ab661b5b = TopPeerCategory;
+topPeerCategoryBotsInline#148677e2 = TopPeerCategory;
+topPeerCategoryCorrespondents#637b7ed = TopPeerCategory;
+topPeerCategoryGroups#bd17a14a = TopPeerCategory;
+topPeerCategoryChannels#161d9628 = TopPeerCategory;
+topPeerCategoryPhoneCalls#1e76a78c = TopPeerCategory;
+topPeerCategoryForwardUsers#a8406ca9 = TopPeerCategory;
+topPeerCategoryForwardChats#fbeec0f0 = TopPeerCategory;
+
+topPeerCategoryPeers#fb834291 category:TopPeerCategory count:int peers:Vector<TopPeer> = TopPeerCategoryPeers;
+
+contacts.topPeersNotModified#de266ef5 = contacts.TopPeers;
+contacts.topPeers#70b772a8 categories:Vector<TopPeerCategoryPeers> chats:Vector<Chat> users:Vector<User> = contacts.TopPeers;
+contacts.topPeersDisabled#b52c939d = contacts.TopPeers;
+
+draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage;
+draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage;
+
+messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers;
+messages.featuredStickers#b6abc341 hash:int count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
+
+messages.recentStickersNotModified#b17f890 = messages.RecentStickers;
+messages.recentStickers#22f3afb3 hash:int packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers;
+
+messages.archivedStickers#4fcba9c8 count:int sets:Vector<StickerSetCovered> = messages.ArchivedStickers;
+
+messages.stickerSetInstallResultSuccess#38641628 = messages.StickerSetInstallResult;
+messages.stickerSetInstallResultArchive#35e410a8 sets:Vector<StickerSetCovered> = messages.StickerSetInstallResult;
+
+stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered;
+stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector<Document> = StickerSetCovered;
+
+maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords;
+
+inputStickeredMediaPhoto#4a992157 id:InputPhoto = InputStickeredMedia;
+inputStickeredMediaDocument#438865b id:InputDocument = InputStickeredMedia;
+
+game#bdf9653b flags:# id:long access_hash:long short_name:string title:string description:string photo:Photo document:flags.0?Document = Game;
+
+inputGameID#32c3e77 id:long access_hash:long = InputGame;
+inputGameShortName#c331e80a bot_id:InputUser short_name:string = InputGame;
+
+highScore#58fffcd0 pos:int user_id:int score:int = HighScore;
+
+messages.highScores#9a3bfd99 scores:Vector<HighScore> users:Vector<User> = messages.HighScores;
+
+textEmpty#dc3d824f = RichText;
+textPlain#744694e0 text:string = RichText;
+textBold#6724abc4 text:RichText = RichText;
+textItalic#d912a59c text:RichText = RichText;
+textUnderline#c12622c4 text:RichText = RichText;
+textStrike#9bf8bb95 text:RichText = RichText;
+textFixed#6c3f19b9 text:RichText = RichText;
+textUrl#3c2884c1 text:RichText url:string webpage_id:long = RichText;
+textEmail#de5a0dd6 text:RichText email:string = RichText;
+textConcat#7e6260d7 texts:Vector<RichText> = RichText;
+textSubscript#ed6a8504 text:RichText = RichText;
+textSuperscript#c7fb5e01 text:RichText = RichText;
+textMarked#34b8621 text:RichText = RichText;
+textPhone#1ccb966a text:RichText phone:string = RichText;
+textImage#81ccf4f document_id:long w:int h:int = RichText;
+textAnchor#35553762 text:RichText name:string = RichText;
+
+pageBlockUnsupported#13567e8a = PageBlock;
+pageBlockTitle#70abc3fd text:RichText = PageBlock;
+pageBlockSubtitle#8ffa9a1f text:RichText = PageBlock;
+pageBlockAuthorDate#baafe5e0 author:RichText published_date:int = PageBlock;
+pageBlockHeader#bfd064ec text:RichText = PageBlock;
+pageBlockSubheader#f12bb6e1 text:RichText = PageBlock;
+pageBlockParagraph#467a0766 text:RichText = PageBlock;
+pageBlockPreformatted#c070d93e text:RichText language:string = PageBlock;
+pageBlockFooter#48870999 text:RichText = PageBlock;
+pageBlockDivider#db20b188 = PageBlock;
+pageBlockAnchor#ce0d37b0 name:string = PageBlock;
+pageBlockList#e4e88011 items:Vector<PageListItem> = PageBlock;
+pageBlockBlockquote#263d7c26 text:RichText caption:RichText = PageBlock;
+pageBlockPullquote#4f4456d3 text:RichText caption:RichText = PageBlock;
+pageBlockPhoto#1759c560 flags:# photo_id:long caption:PageCaption url:flags.0?string webpage_id:flags.0?long = PageBlock;
+pageBlockVideo#7c8fe7b6 flags:# autoplay:flags.0?true loop:flags.1?true video_id:long caption:PageCaption = PageBlock;
+pageBlockCover#39f23300 cover:PageBlock = PageBlock;
+pageBlockEmbed#a8718dc5 flags:# full_width:flags.0?true allow_scrolling:flags.3?true url:flags.1?string html:flags.2?string poster_photo_id:flags.4?long w:flags.5?int h:flags.5?int caption:PageCaption = PageBlock;
+pageBlockEmbedPost#f259a80b url:string webpage_id:long author_photo_id:long author:string date:int blocks:Vector<PageBlock> caption:PageCaption = PageBlock;
+pageBlockCollage#65a0fa4d items:Vector<PageBlock> caption:PageCaption = PageBlock;
+pageBlockSlideshow#31f9590 items:Vector<PageBlock> caption:PageCaption = PageBlock;
+pageBlockChannel#ef1751b5 channel:Chat = PageBlock;
+pageBlockAudio#804361ea audio_id:long caption:PageCaption = PageBlock;
+pageBlockKicker#1e148390 text:RichText = PageBlock;
+pageBlockTable#bf4dea82 flags:# bordered:flags.0?true striped:flags.1?true title:RichText rows:Vector<PageTableRow> = PageBlock;
+pageBlockOrderedList#9a8ae1e1 items:Vector<PageListOrderedItem> = PageBlock;
+pageBlockDetails#76768bed flags:# open:flags.0?true blocks:Vector<PageBlock> title:RichText = PageBlock;
+pageBlockRelatedArticles#16115a96 title:RichText articles:Vector<PageRelatedArticle> = PageBlock;
+pageBlockMap#a44f3ef6 geo:GeoPoint zoom:int w:int h:int caption:PageCaption = PageBlock;
+
+phoneCallDiscardReasonMissed#85e42301 = PhoneCallDiscardReason;
+phoneCallDiscardReasonDisconnect#e095c1a0 = PhoneCallDiscardReason;
+phoneCallDiscardReasonHangup#57adc690 = PhoneCallDiscardReason;
+phoneCallDiscardReasonBusy#faf7e8c9 = PhoneCallDiscardReason;
+
+dataJSON#7d748d04 data:string = DataJSON;
+
+labeledPrice#cb296bf8 label:string amount:long = LabeledPrice;
+
+invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> = Invoice;
+
+paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge;
+
+postAddress#1e8caaeb street_line1:string street_line2:string city:string state:string country_iso2:string post_code:string = PostAddress;
+
+paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string email:flags.2?string shipping_address:flags.3?PostAddress = PaymentRequestedInfo;
+
+paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials;
+
+webDocument#1c570ed1 url:string access_hash:long size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
+webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
+
+inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = InputWebDocument;
+
+inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation;
+inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w:int h:int zoom:int scale:int = InputWebFileLocation;
+
+upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile;
+
+payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
+
+payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = payments.ValidatedRequestedInfo;
+
+payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult;
+payments.paymentVerificationNeeded#d8411139 url:string = payments.PaymentResult;
+
+payments.paymentReceipt#500911e1 flags:# date:int bot_id:int invoice:Invoice provider_id:int info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption currency:string total_amount:long credentials_title:string users:Vector<User> = payments.PaymentReceipt;
+
+payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo;
+
+inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;
+inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
+inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials;
+inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials;
+
+account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPassword;
+
+shippingOption#b6213cdf id:string title:string prices:Vector<LabeledPrice> = ShippingOption;
+
+inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_coords:flags.0?MaskCoords = InputStickerSetItem;
+
+inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall;
+
+phoneCallEmpty#5366c915 id:long = PhoneCall;
+phoneCallWaiting#1b8f4ad1 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
+phoneCallRequested#87eabb53 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall;
+phoneCallAccepted#997c454a flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall;
+phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector<PhoneConnection> start_date:int = PhoneCall;
+phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.5?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
+
+phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
+
+phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector<string> = PhoneCallProtocol;
+
+phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector<User> = phone.PhoneCall;
+
+upload.cdnFileReuploadNeeded#eea8e46e request_token:bytes = upload.CdnFile;
+upload.cdnFile#a99fca4f bytes:bytes = upload.CdnFile;
+
+cdnPublicKey#c982eaba dc_id:int public_key:string = CdnPublicKey;
+
+cdnConfig#5725e40a public_keys:Vector<CdnPublicKey> = CdnConfig;
+
+langPackString#cad181f6 key:string value:string = LangPackString;
+langPackStringPluralized#6c47ac9f flags:# key:string zero_value:flags.0?string one_value:flags.1?string two_value:flags.2?string few_value:flags.3?string many_value:flags.4?string other_value:string = LangPackString;
+langPackStringDeleted#2979eeb2 key:string = LangPackString;
+
+langPackDifference#f385c1f6 lang_code:string from_version:int version:int strings:Vector<LangPackString> = LangPackDifference;
+
+langPackLanguage#eeca5ce3 flags:# official:flags.0?true rtl:flags.2?true beta:flags.3?true name:string native_name:string lang_code:string base_lang_code:flags.1?string plural_code:string strings_count:int translated_count:int translations_url:string = LangPackLanguage;
+
+channelAdminLogEventActionChangeTitle#e6dfb825 prev_value:string new_value:string = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeAbout#55188a2e prev_value:string new_value:string = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeUsername#6a4afc38 prev_value:string new_value:string = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangePhoto#434bd2af prev_photo:Photo new_photo:Photo = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleInvites#1b7907ae new_value:Bool = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleSignatures#26ae0971 new_value:Bool = ChannelAdminLogEventAction;
+channelAdminLogEventActionUpdatePinned#e9e82c18 message:Message = ChannelAdminLogEventAction;
+channelAdminLogEventActionEditMessage#709b2405 prev_message:Message new_message:Message = ChannelAdminLogEventAction;
+channelAdminLogEventActionDeleteMessage#42e047bb message:Message = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantJoin#183040d3 = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantLeave#f89777f2 = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantInvite#e31c34d8 participant:ChannelParticipant = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantToggleBan#e6d83d7e prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantToggleAdmin#d5676710 prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeStickerSet#b1c3caa7 prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction;
+channelAdminLogEventActionTogglePreHistoryHidden#5f5c95f1 new_value:Bool = ChannelAdminLogEventAction;
+channelAdminLogEventActionDefaultBannedRights#2df5fc0a prev_banned_rights:ChatBannedRights new_banned_rights:ChatBannedRights = ChannelAdminLogEventAction;
+channelAdminLogEventActionStopPoll#8f079643 message:Message = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeLinkedChat#a26f881b prev_value:int new_value:int = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeLocation#e6b76ae prev_value:ChannelLocation new_value:ChannelLocation = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleSlowMode#53909779 prev_value:int new_value:int = ChannelAdminLogEventAction;
+
+channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
+
+channels.adminLogResults#ed8af74d events:Vector<ChannelAdminLogEvent> chats:Vector<Chat> users:Vector<User> = channels.AdminLogResults;
+
+channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true = ChannelAdminLogEventsFilter;
+
+popularContact#5ce14175 client_id:long importers:int = PopularContact;
+
+messages.favedStickersNotModified#9e8fa6d3 = messages.FavedStickers;
+messages.favedStickers#f37f2f16 hash:int packs:Vector<StickerPack> stickers:Vector<Document> = messages.FavedStickers;
+
+recentMeUrlUnknown#46e1d13d url:string = RecentMeUrl;
+recentMeUrlUser#8dbc3336 url:string user_id:int = RecentMeUrl;
+recentMeUrlChat#a01b22f9 url:string chat_id:int = RecentMeUrl;
+recentMeUrlChatInvite#eb49081d url:string chat_invite:ChatInvite = RecentMeUrl;
+recentMeUrlStickerSet#bc0a57dc url:string set:StickerSetCovered = RecentMeUrl;
+
+help.recentMeUrls#e0310d7 urls:Vector<RecentMeUrl> chats:Vector<Chat> users:Vector<User> = help.RecentMeUrls;
+
+inputSingleMedia#1cc6e91f flags:# media:InputMedia random_id:long message:string entities:flags.0?Vector<MessageEntity> = InputSingleMedia;
+
+webAuthorization#cac943f2 hash:long bot_id:int domain:string browser:string platform:string date_created:int date_active:int ip:string region:string = WebAuthorization;
+
+account.webAuthorizations#ed56c9fc authorizations:Vector<WebAuthorization> users:Vector<User> = account.WebAuthorizations;
+
+inputMessageID#a676a322 id:int = InputMessage;
+inputMessageReplyTo#bad88395 id:int = InputMessage;
+inputMessagePinned#86872538 = InputMessage;
+
+inputDialogPeer#fcaafeb7 peer:InputPeer = InputDialogPeer;
+inputDialogPeerFolder#64600527 folder_id:int = InputDialogPeer;
+
+dialogPeer#e56dbf05 peer:Peer = DialogPeer;
+dialogPeerFolder#514519e2 folder_id:int = DialogPeer;
+
+messages.foundStickerSetsNotModified#d54b65d = messages.FoundStickerSets;
+messages.foundStickerSets#5108d648 hash:int sets:Vector<StickerSetCovered> = messages.FoundStickerSets;
+
+fileHash#6242c773 offset:int limit:int hash:bytes = FileHash;
+
+inputClientProxy#75588b3f address:string port:int = InputClientProxy;
+
+help.proxyDataEmpty#e09e1fb8 expires:int = help.ProxyData;
+help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector<Chat> users:Vector<User> = help.ProxyData;
+
+help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate;
+help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = help.TermsOfServiceUpdate;
+
+inputSecureFileUploaded#3334b0f0 id:long parts:int md5_checksum:string file_hash:bytes secret:bytes = InputSecureFile;
+inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile;
+
+secureFileEmpty#64199744 = SecureFile;
+secureFile#e0277a62 id:long access_hash:long size:int dc_id:int date:int file_hash:bytes secret:bytes = SecureFile;
+
+secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData;
+
+securePlainPhone#7d6099dd phone:string = SecurePlainData;
+securePlainEmail#21ec5a5f email:string = SecurePlainData;
+
+secureValueTypePersonalDetails#9d2a81e3 = SecureValueType;
+secureValueTypePassport#3dac6a00 = SecureValueType;
+secureValueTypeDriverLicense#6e425c4 = SecureValueType;
+secureValueTypeIdentityCard#a0d0744b = SecureValueType;
+secureValueTypeInternalPassport#99a48f23 = SecureValueType;
+secureValueTypeAddress#cbe31e26 = SecureValueType;
+secureValueTypeUtilityBill#fc36954e = SecureValueType;
+secureValueTypeBankStatement#89137c0d = SecureValueType;
+secureValueTypeRentalAgreement#8b883488 = SecureValueType;
+secureValueTypePassportRegistration#99e3806a = SecureValueType;
+secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType;
+secureValueTypePhone#b320aadb = SecureValueType;
+secureValueTypeEmail#8e3ca7ee = SecureValueType;
+
+secureValue#187fa0ca flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile translation:flags.6?Vector<SecureFile> files:flags.4?Vector<SecureFile> plain_data:flags.5?SecurePlainData hash:bytes = SecureValue;
+
+inputSecureValue#db21d0a7 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile translation:flags.6?Vector<InputSecureFile> files:flags.4?Vector<InputSecureFile> plain_data:flags.5?SecurePlainData = InputSecureValue;
+
+secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash;
+
+secureValueErrorData#e8a40bd9 type:SecureValueType data_hash:bytes field:string text:string = SecureValueError;
+secureValueErrorFrontSide#be3dfa type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorReverseSide#868a2aa5 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorFile#7a700873 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
+secureValueError#869d758f type:SecureValueType hash:bytes text:string = SecureValueError;
+secureValueErrorTranslationFile#a1144770 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorTranslationFiles#34636dd8 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
+
+secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted;
+
+account.authorizationForm#ad2e1cd8 flags:# required_types:Vector<SecureRequiredType> values:Vector<SecureValue> errors:Vector<SecureValueError> users:Vector<User> privacy_policy_url:flags.0?string = account.AuthorizationForm;
+
+account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEmailCode;
+
+help.deepLinkInfoEmpty#66afa166 = help.DeepLinkInfo;
+help.deepLinkInfo#6a4ee832 flags:# update_app:flags.0?true message:string entities:flags.1?Vector<MessageEntity> = help.DeepLinkInfo;
+
+savedPhoneContact#1142bd56 phone:string first_name:string last_name:string date:int = SavedContact;
+
+account.takeout#4dba4501 id:long = account.Takeout;
+
+passwordKdfAlgoUnknown#d45ab096 = PasswordKdfAlgo;
+passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow#3a912d4a salt1:bytes salt2:bytes g:int p:bytes = PasswordKdfAlgo;
+
+securePasswordKdfAlgoUnknown#4a8537 = SecurePasswordKdfAlgo;
+securePasswordKdfAlgoPBKDF2HMACSHA512iter100000#bbf2dda0 salt:bytes = SecurePasswordKdfAlgo;
+securePasswordKdfAlgoSHA512#86471d92 salt:bytes = SecurePasswordKdfAlgo;
+
+secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:bytes secure_secret_id:long = SecureSecretSettings;
+
+inputCheckPasswordEmpty#9880f658 = InputCheckPasswordSRP;
+inputCheckPasswordSRP#d27ff082 srp_id:long A:bytes M1:bytes = InputCheckPasswordSRP;
+
+secureRequiredType#829d99da flags:# native_names:flags.0?true selfie_required:flags.1?true translation_required:flags.2?true type:SecureValueType = SecureRequiredType;
+secureRequiredTypeOneOf#27477b4 types:Vector<SecureRequiredType> = SecureRequiredType;
+
+help.passportConfigNotModified#bfb9f457 = help.PassportConfig;
+help.passportConfig#a098d6af hash:int countries_langs:DataJSON = help.PassportConfig;
+
+inputAppEvent#1d1b1245 time:double type:string peer:long data:JSONValue = InputAppEvent;
+
+jsonObjectValue#c0de1bd9 key:string value:JSONValue = JSONObjectValue;
+
+jsonNull#3f6d7b68 = JSONValue;
+jsonBool#c7345e6a value:Bool = JSONValue;
+jsonNumber#2be0dfa4 value:double = JSONValue;
+jsonString#b71e767a value:string = JSONValue;
+jsonArray#f7444763 value:Vector<JSONValue> = JSONValue;
+jsonObject#99c1d49d value:Vector<JSONObjectValue> = JSONValue;
+
+pageTableCell#34566b6a flags:# header:flags.0?true align_center:flags.3?true align_right:flags.4?true valign_middle:flags.5?true valign_bottom:flags.6?true text:flags.7?RichText colspan:flags.1?int rowspan:flags.2?int = PageTableCell;
+
+pageTableRow#e0c0c5e5 cells:Vector<PageTableCell> = PageTableRow;
+
+pageCaption#6f747657 text:RichText credit:RichText = PageCaption;
+
+pageListItemText#b92fb6cd text:RichText = PageListItem;
+pageListItemBlocks#25e073fc blocks:Vector<PageBlock> = PageListItem;
+
+pageListOrderedItemText#5e068047 num:string text:RichText = PageListOrderedItem;
+pageListOrderedItemBlocks#98dd8936 num:string blocks:Vector<PageBlock> = PageListOrderedItem;
+
+pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long title:flags.0?string description:flags.1?string photo_id:flags.2?long author:flags.3?string published_date:flags.4?int = PageRelatedArticle;
+
+page#98657f0d flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> views:flags.3?int = Page;
+
+help.supportName#8c05f1c9 name:string = help.SupportName;
+
+help.userInfoEmpty#f3ae2eed = help.UserInfo;
+help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:string date:int = help.UserInfo;
+
+pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
+
+poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> close_period:flags.4?int close_date:flags.5?int = Poll;
+
+pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
+
+pollResults#badcc1a3 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<int> solution:flags.4?string solution_entities:flags.4?Vector<MessageEntity> = PollResults;
+
+chatOnlines#f041e250 onlines:int = ChatOnlines;
+
+statsURL#47a971e0 url:string = StatsURL;
+
+chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true = ChatAdminRights;
+
+chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights;
+
+inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper;
+inputWallPaperSlug#72091c80 slug:string = InputWallPaper;
+inputWallPaperNoFile#8427bbac = InputWallPaper;
+
+account.wallPapersNotModified#1c199183 = account.WallPapers;
+account.wallPapers#702b65a9 hash:int wallpapers:Vector<WallPaper> = account.WallPapers;
+
+codeSettings#debebe83 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true = CodeSettings;
+
+wallPaperSettings#5086cf8 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings;
+
+autoDownloadSettings#e04232f3 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:int file_size_max:int video_upload_maxbitrate:int = AutoDownloadSettings;
+
+account.autoDownloadSettings#63cacf26 low:AutoDownloadSettings medium:AutoDownloadSettings high:AutoDownloadSettings = account.AutoDownloadSettings;
+
+emojiKeyword#d5b3b9f9 keyword:string emoticons:Vector<string> = EmojiKeyword;
+emojiKeywordDeleted#236df622 keyword:string emoticons:Vector<string> = EmojiKeyword;
+
+emojiKeywordsDifference#5cc761bd lang_code:string from_version:int version:int keywords:Vector<EmojiKeyword> = EmojiKeywordsDifference;
+
+emojiURL#a575739d url:string = EmojiURL;
+
+emojiLanguage#b3fb5361 lang_code:string = EmojiLanguage;
+
+fileLocationToBeDeprecated#bc7fc6cd volume_id:long local_id:int = FileLocation;
+
+folder#ff544e65 flags:# autofill_new_broadcasts:flags.0?true autofill_public_groups:flags.1?true autofill_new_correspondents:flags.2?true id:int title:string photo:flags.3?ChatPhoto = Folder;
+
+inputFolderPeer#fbd2c296 peer:InputPeer folder_id:int = InputFolderPeer;
+
+folderPeer#e9baa668 peer:Peer folder_id:int = FolderPeer;
+
+messages.searchCounter#e844ebff flags:# inexact:flags.1?true filter:MessagesFilter count:int = messages.SearchCounter;
+
+urlAuthResultRequest#92d33a0e flags:# request_write_access:flags.0?true bot:User domain:string = UrlAuthResult;
+urlAuthResultAccepted#8f8c0e4e url:string = UrlAuthResult;
+urlAuthResultDefault#a9d6db1f = UrlAuthResult;
+
+channelLocationEmpty#bfb5ad8b = ChannelLocation;
+channelLocation#209b82db geo_point:GeoPoint address:string = ChannelLocation;
+
+peerLocated#ca461b5d peer:Peer expires:int distance:int = PeerLocated;
+peerSelfLocated#f8ec284b expires:int = PeerLocated;
+
+restrictionReason#d072acb4 platform:string reason:string text:string = RestrictionReason;
+
+inputTheme#3c5693e9 id:long access_hash:long = InputTheme;
+inputThemeSlug#f5890df1 slug:string = InputTheme;
+
+theme#28f1114 flags:# creator:flags.0?true default:flags.1?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:int = Theme;
+
+account.themesNotModified#f41eb622 = account.Themes;
+account.themes#7f676421 hash:int themes:Vector<Theme> = account.Themes;
+
+auth.loginToken#629f1980 expires:int token:bytes = auth.LoginToken;
+auth.loginTokenMigrateTo#68e9916 dc_id:int token:bytes = auth.LoginToken;
+auth.loginTokenSuccess#390d5c5e authorization:auth.Authorization = auth.LoginToken;
+
+account.contentSettings#57e28221 flags:# sensitive_enabled:flags.0?true sensitive_can_change:flags.1?true = account.ContentSettings;
+
+messages.inactiveChats#a927fec5 dates:Vector<int> chats:Vector<Chat> users:Vector<User> = messages.InactiveChats;
+
+baseThemeClassic#c3a12462 = BaseTheme;
+baseThemeDay#fbd81688 = BaseTheme;
+baseThemeNight#b7b31ea8 = BaseTheme;
+baseThemeTinted#6d5f77ee = BaseTheme;
+baseThemeArctic#5b11125a = BaseTheme;
+
+inputThemeSettings#bd507cd1 flags:# base_theme:BaseTheme accent_color:int message_top_color:flags.0?int message_bottom_color:flags.0?int wallpaper:flags.1?InputWallPaper wallpaper_settings:flags.1?WallPaperSettings = InputThemeSettings;
+
+themeSettings#9c14984a flags:# base_theme:BaseTheme accent_color:int message_top_color:flags.0?int message_bottom_color:flags.0?int wallpaper:flags.1?WallPaper = ThemeSettings;
+
+webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector<Document> settings:flags.1?ThemeSettings = WebPageAttribute;
+
+messageUserVote#a28e5559 user_id:int option:bytes date:int = MessageUserVote;
+messageUserVoteInputOption#36377430 user_id:int date:int = MessageUserVote;
+messageUserVoteMultiple#e8fe0de user_id:int options:Vector<bytes> date:int = MessageUserVote;
+
+messages.votesList#823f649 flags:# count:int votes:Vector<MessageUserVote> users:Vector<User> next_offset:flags.0?string = messages.VotesList;
+
+bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl;
+
+payments.bankCardData#3e24e573 title:string open_urls:Vector<BankCardOpenUrl> = payments.BankCardData;
+
+dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector<InputPeer> include_peers:Vector<InputPeer> exclude_peers:Vector<InputPeer> = DialogFilter;
+
+dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested;
+
+statsDateRangeDays#b637edaf min_date:int max_date:int = StatsDateRangeDays;
+
+statsAbsValueAndPrev#cb43acde current:double previous:double = StatsAbsValueAndPrev;
+
+statsPercentValue#cbce2fe0 part:double total:double = StatsPercentValue;
+
+statsGraphAsync#4a27eb2d token:string = StatsGraph;
+statsGraphError#bedc9822 error:string = StatsGraph;
+statsGraph#8ea464b6 flags:# json:DataJSON zoom_token:flags.0?string = StatsGraph;
+
+messageInteractionCounters#ad4fc9bd msg_id:int views:int forwards:int = MessageInteractionCounters;
+
+stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueAndPrev views_per_post:StatsAbsValueAndPrev shares_per_post:StatsAbsValueAndPrev enabled_notifications:StatsPercentValue growth_graph:StatsGraph followers_graph:StatsGraph mute_graph:StatsGraph top_hours_graph:StatsGraph interactions_graph:StatsGraph iv_interactions_graph:StatsGraph views_by_source_graph:StatsGraph new_followers_by_source_graph:StatsGraph languages_graph:StatsGraph recent_message_interactions:Vector<MessageInteractionCounters> = stats.BroadcastStats;
+
+---functions---
+
+invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
+invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
+initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X;
+invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
+invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
+invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X;
+invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X;
+
+auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode;
+auth.signUp#80eee427 phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization;
+auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization;
+auth.logOut#5717da40 = Bool;
+auth.resetAuthorizations#9fab0d1a = Bool;
+auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization;
+auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization;
+auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool;
+auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization;
+auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization;
+auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery;
+auth.recoverPassword#4ea56e92 code:string = auth.Authorization;
+auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode;
+auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool;
+auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector<long> = Bool;
+auth.exportLoginToken#b1b41517 api_id:int api_hash:string except_ids:Vector<int> = auth.LoginToken;
+auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken;
+auth.acceptLoginToken#e894ad4d token:bytes = Authorization;
+
+account.registerDevice#68976c6f flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector<int> = Bool;
+account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector<int> = Bool;
+account.updateNotifySettings#84be5b93 peer:InputNotifyPeer settings:InputPeerNotifySettings = Bool;
+account.getNotifySettings#12b3ad31 peer:InputNotifyPeer = PeerNotifySettings;
+account.resetNotifySettings#db7e1747 = Bool;
+account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User;
+account.updateStatus#6628562c offline:Bool = Bool;
+account.getWallPapers#aabb1763 hash:int = account.WallPapers;
+account.reportPeer#ae189d5f peer:InputPeer reason:ReportReason = Bool;
+account.checkUsername#2714d86c username:string = Bool;
+account.updateUsername#3e0bdd7c username:string = User;
+account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules;
+account.setPrivacy#c9f81ce8 key:InputPrivacyKey rules:Vector<InputPrivacyRule> = account.PrivacyRules;
+account.deleteAccount#418d4e0b reason:string = Bool;
+account.getAccountTTL#8fc711d = AccountDaysTTL;
+account.setAccountTTL#2442485e ttl:AccountDaysTTL = Bool;
+account.sendChangePhoneCode#82574ae5 phone_number:string settings:CodeSettings = auth.SentCode;
+account.changePhone#70c32edb phone_number:string phone_code_hash:string phone_code:string = User;
+account.updateDeviceLocked#38df3532 period:int = Bool;
+account.getAuthorizations#e320c158 = account.Authorizations;
+account.resetAuthorization#df77f3bc hash:long = Bool;
+account.getPassword#548a30f5 = account.Password;
+account.getPasswordSettings#9cd4eaf9 password:InputCheckPasswordSRP = account.PasswordSettings;
+account.updatePasswordSettings#a59b102f password:InputCheckPasswordSRP new_settings:account.PasswordInputSettings = Bool;
+account.sendConfirmPhoneCode#1b3faa88 hash:string settings:CodeSettings = auth.SentCode;
+account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool;
+account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword;
+account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
+account.resetWebAuthorization#2d01b9ef hash:long = Bool;
+account.resetWebAuthorizations#682d2594 = Bool;
+account.getAllSecureValues#b288bc7d = Vector<SecureValue>;
+account.getSecureValue#73665bc2 types:Vector<SecureValueType> = Vector<SecureValue>;
+account.saveSecureValue#899fe31d value:InputSecureValue secure_secret_id:long = SecureValue;
+account.deleteSecureValue#b880bc4b types:Vector<SecureValueType> = Bool;
+account.getAuthorizationForm#b86ba8e1 bot_id:int scope:string public_key:string = account.AuthorizationForm;
+account.acceptAuthorization#e7027c94 bot_id:int scope:string public_key:string value_hashes:Vector<SecureValueHash> credentials:SecureCredentialsEncrypted = Bool;
+account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode;
+account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool;
+account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode;
+account.verifyEmail#ecba39db email:string code:string = Bool;
+account.initTakeoutSession#f05b4804 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?int = account.Takeout;
+account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool;
+account.confirmPasswordEmail#8fdf1920 code:string = Bool;
+account.resendPasswordEmail#7a7f2a15 = Bool;
+account.cancelPasswordEmail#c1cbd5b6 = Bool;
+account.getContactSignUpNotification#9f07c728 = Bool;
+account.setContactSignUpNotification#cff43f61 silent:Bool = Bool;
+account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates;
+account.getWallPaper#fc8ddbea wallpaper:InputWallPaper = WallPaper;
+account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper;
+account.saveWallPaper#6c5a5b37 wallpaper:InputWallPaper unsave:Bool settings:WallPaperSettings = Bool;
+account.installWallPaper#feed5769 wallpaper:InputWallPaper settings:WallPaperSettings = Bool;
+account.resetWallPapers#bb3b9804 = Bool;
+account.getAutoDownloadSettings#56da0b3f = account.AutoDownloadSettings;
+account.saveAutoDownloadSettings#76f36233 flags:# low:flags.0?true high:flags.1?true settings:AutoDownloadSettings = Bool;
+account.uploadTheme#1c3db333 flags:# file:InputFile thumb:flags.0?InputFile file_name:string mime_type:string = Document;
+account.createTheme#8432c21f flags:# slug:string title:string document:flags.2?InputDocument settings:flags.3?InputThemeSettings = Theme;
+account.updateTheme#5cb367d5 flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?InputThemeSettings = Theme;
+account.saveTheme#f257106c theme:InputTheme unsave:Bool = Bool;
+account.installTheme#7ae43737 flags:# dark:flags.0?true format:flags.1?string theme:flags.1?InputTheme = Bool;
+account.getTheme#8d9d742b format:string theme:InputTheme document_id:long = Theme;
+account.getThemes#285946f8 format:string hash:int = account.Themes;
+account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
+account.getContentSettings#8b9b4dae = account.ContentSettings;
+account.getMultiWallPapers#65ad71dc wallpapers:Vector<InputWallPaper> = Vector<WallPaper>;
+
+users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
+users.getFullUser#ca30a5b1 id:InputUser = UserFull;
+users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector<SecureValueError> = Bool;
+
+contacts.getContactIDs#2caa4a42 hash:int = Vector<int>;
+contacts.getStatuses#c4a353ee = Vector<ContactStatus>;
+contacts.getContacts#c023849f hash:int = contacts.Contacts;
+contacts.importContacts#2c800be5 contacts:Vector<InputContact> = contacts.ImportedContacts;
+contacts.deleteContacts#96a0e00 id:Vector<InputUser> = Updates;
+contacts.deleteByPhones#1013fd9e phones:Vector<string> = Bool;
+contacts.block#332b49fc id:InputUser = Bool;
+contacts.unblock#e54100bd id:InputUser = Bool;
+contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked;
+contacts.search#11f812d8 q:string limit:int = contacts.Found;
+contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer;
+contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers;
+contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool;
+contacts.resetSaved#879537f1 = Bool;
+contacts.getSaved#82f1e39f = Vector<SavedContact>;
+contacts.toggleTopPeers#8514bdda enabled:Bool = Bool;
+contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates;
+contacts.acceptContact#f831a20f id:InputUser = Updates;
+contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoPoint self_expires:flags.0?int = Updates;
+
+messages.getMessages#63c66506 id:Vector<InputMessage> = messages.Messages;
+messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs;
+messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
+messages.search#8614ef68 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
+messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages;
+messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory;
+messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector<int> = messages.AffectedMessages;
+messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
+messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool;
+messages.sendMessage#520c3870 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int = Updates;
+messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int = Updates;
+messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true grouped:flags.9?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer schedule_date:flags.10?int = Updates;
+messages.reportSpam#cf1592db peer:InputPeer = Bool;
+messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings;
+messages.report#bd82b658 peer:InputPeer id:Vector<int> reason:ReportReason = Bool;
+messages.getChats#3c6aa187 id:Vector<int> = messages.Chats;
+messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull;
+messages.editChatTitle#dc452855 chat_id:int title:string = Updates;
+messages.editChatPhoto#ca4c79d8 chat_id:int photo:InputChatPhoto = Updates;
+messages.addChatUser#f9a0aa09 chat_id:int user_id:InputUser fwd_limit:int = Updates;
+messages.deleteChatUser#e0611f16 chat_id:int user_id:InputUser = Updates;
+messages.createChat#9cb126e users:Vector<InputUser> title:string = Updates;
+messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig;
+messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat;
+messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat;
+messages.discardEncryption#edd923c5 chat_id:int = Bool;
+messages.setEncryptedTyping#791451ed peer:InputEncryptedChat typing:Bool = Bool;
+messages.readEncryptedHistory#7f4b690a peer:InputEncryptedChat max_date:int = Bool;
+messages.sendEncrypted#a9776773 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage;
+messages.sendEncryptedFile#9a901b66 peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage;
+messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage;
+messages.receivedQueue#55a5bb66 max_qts:int = Vector<long>;
+messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool;
+messages.readMessageContents#36a73f77 id:Vector<int> = messages.AffectedMessages;
+messages.getStickers#43d4f2c emoticon:string hash:int = messages.Stickers;
+messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers;
+messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector<MessageEntity> = MessageMedia;
+messages.exportChatInvite#df7534c peer:InputPeer = ExportedChatInvite;
+messages.checkChatInvite#3eadb1bb hash:string = ChatInvite;
+messages.importChatInvite#6c50051c hash:string = Updates;
+messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet;
+messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult;
+messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool;
+messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates;
+messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector<int> increment:Bool = Vector<int>;
+messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool;
+messages.migrateChat#15a3b8e3 chat_id:int = Updates;
+messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
+messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector<long> = Bool;
+messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document;
+messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs;
+messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs;
+messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool;
+messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults;
+messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector<InputBotInlineResult> cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool;
+messages.sendInlineBotResult#220815b0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string schedule_date:flags.10?int = Updates;
+messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData;
+messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.15?int = Updates;
+messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Bool;
+messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer;
+messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;
+messages.getPeerDialogs#e470bcfd peers:Vector<InputDialogPeer> = messages.PeerDialogs;
+messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector<MessageEntity> = Bool;
+messages.getAllDrafts#6a3f8d65 = Updates;
+messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers;
+messages.readFeaturedStickers#5b118126 id:Vector<long> = Bool;
+messages.getRecentStickers#5ea192c9 flags:# attached:flags.0?true hash:int = messages.RecentStickers;
+messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool;
+messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool;
+messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true offset_id:long limit:int = messages.ArchivedStickers;
+messages.getMaskStickers#65b8c79f hash:int = messages.AllStickers;
+messages.getAttachedStickers#cc5b67cc media:InputStickeredMedia = Vector<StickerSetCovered>;
+messages.setGameScore#8ef8ecc0 flags:# edit_message:flags.0?true force:flags.1?true peer:InputPeer id:int user_id:InputUser score:int = Updates;
+messages.setInlineGameScore#15ad9f64 flags:# edit_message:flags.0?true force:flags.1?true id:InputBotInlineMessageID user_id:InputUser score:int = Bool;
+messages.getGameHighScores#e822649d peer:InputPeer id:int user_id:InputUser = messages.HighScores;
+messages.getInlineGameHighScores#f635e1b id:InputBotInlineMessageID user_id:InputUser = messages.HighScores;
+messages.getCommonChats#d0a48c4 user_id:InputUser max_id:int limit:int = messages.Chats;
+messages.getAllChats#eba80ff0 except_ids:Vector<int> = messages.Chats;
+messages.getWebPage#32ca8f91 url:string hash:int = WebPage;
+messages.toggleDialogPin#a731e257 flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
+messages.reorderPinnedDialogs#3b1adf37 flags:# force:flags.0?true folder_id:int order:Vector<InputDialogPeer> = Bool;
+messages.getPinnedDialogs#d6b94df2 folder_id:int = messages.PeerDialogs;
+messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = Bool;
+messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool;
+messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia;
+messages.sendScreenshotNotification#c97df020 peer:InputPeer reply_to_msg_id:int random_id:long = Updates;
+messages.getFavedStickers#21ce0b0e hash:int = messages.FavedStickers;
+messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool;
+messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
+messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory;
+messages.getRecentLocations#bbc45b09 peer:InputPeer limit:int hash:int = messages.Messages;
+messages.sendMultiMedia#cc0110cb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector<InputSingleMedia> schedule_date:flags.10?int = Updates;
+messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
+messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:string hash:int = messages.FoundStickerSets;
+messages.getSplitRanges#1cff7e08 = Vector<MessageRange>;
+messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool;
+messages.getDialogUnreadMarks#22e24e22 = Vector<DialogPeer>;
+messages.clearAllDrafts#7e58ee9c = Bool;
+messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true peer:InputPeer id:int = Updates;
+messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector<bytes> = Updates;
+messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates;
+messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines;
+messages.getStatsURL#812c2ae6 flags:# dark:flags.0?true peer:InputPeer params:string = StatsURL;
+messages.editChatAbout#def60797 peer:InputPeer about:string = Bool;
+messages.editChatDefaultBannedRights#a5866b41 peer:InputPeer banned_rights:ChatBannedRights = Updates;
+messages.getEmojiKeywords#35a0e062 lang_code:string = EmojiKeywordsDifference;
+messages.getEmojiKeywordsDifference#1508b6af lang_code:string from_version:int = EmojiKeywordsDifference;
+messages.getEmojiKeywordsLanguages#4e9963b2 lang_codes:Vector<string> = Vector<EmojiLanguage>;
+messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL;
+messages.getSearchCounters#732eef00 peer:InputPeer filters:Vector<MessagesFilter> = Vector<messages.SearchCounter>;
+messages.requestUrlAuth#e33f5613 peer:InputPeer msg_id:int button_id:int = UrlAuthResult;
+messages.acceptUrlAuth#f729ea98 flags:# write_allowed:flags.0?true peer:InputPeer msg_id:int button_id:int = UrlAuthResult;
+messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool;
+messages.getScheduledHistory#e2c2685b peer:InputPeer hash:int = messages.Messages;
+messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector<int> = messages.Messages;
+messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
+messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector<int> = Updates;
+messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList;
+messages.toggleStickerSets#b5052fea flags:# uninstall:flags.0?true archive:flags.1?true unarchive:flags.2?true stickersets:Vector<InputStickerSet> = Bool;
+messages.getDialogFilters#f19ed96d = Vector<DialogFilter>;
+messages.getSuggestedDialogFilters#a29cd42c = Vector<DialogFilterSuggested>;
+messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool;
+messages.updateDialogFiltersOrder#c563c1e4 order:Vector<int> = Bool;
+messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = messages.FeaturedStickers;
+
+updates.getState#edd4882a = updates.State;
+updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
+updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
+
+photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto;
+photos.uploadProfilePhoto#4f32c098 file:InputFile = photos.Photo;
+photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
+photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
+
+upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool;
+upload.getFile#b15a9afc flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:int limit:int = upload.File;
+upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool;
+upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile;
+upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile;
+upload.reuploadCdnFile#9b2754a8 file_token:bytes request_token:bytes = Vector<FileHash>;
+upload.getCdnFileHashes#4da54231 file_token:bytes offset:int = Vector<FileHash>;
+upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector<FileHash>;
+
+help.getConfig#c4f9186b = Config;
+help.getNearestDc#1fb33026 = NearestDc;
+help.getAppUpdate#522d5a7d source:string = help.AppUpdate;
+help.getInviteText#4d392343 = help.InviteText;
+help.getSupport#9cdf08cd = help.Support;
+help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
+help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
+help.getCdnConfig#52029342 = CdnConfig;
+help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls;
+help.getProxyData#3d7758e1 = help.ProxyData;
+help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate;
+help.acceptTermsOfService#ee72f79a id:DataJSON = Bool;
+help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo;
+help.getAppConfig#98914110 = JSONValue;
+help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
+help.getPassportConfig#c661ad08 hash:int = help.PassportConfig;
+help.getSupportName#d360e72c = help.SupportName;
+help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo;
+help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector<MessageEntity> = help.UserInfo;
+
+channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
+channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
+channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory;
+channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector<int> = Bool;
+channels.getMessages#ad8c9a23 channel:InputChannel id:Vector<InputMessage> = messages.Messages;
+channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants;
+channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant;
+channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
+channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull;
+channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates;
+channels.editAdmin#d33c8902 channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights rank:string = Updates;
+channels.editTitle#566decd0 channel:InputChannel title:string = Updates;
+channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates;
+channels.checkUsername#10e6bd2c channel:InputChannel username:string = Bool;
+channels.updateUsername#3514b3de channel:InputChannel username:string = Bool;
+channels.joinChannel#24b524c5 channel:InputChannel = Updates;
+channels.leaveChannel#f836aa95 channel:InputChannel = Updates;
+channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector<InputUser> = Updates;
+channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
+channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink;
+channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
+channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats;
+channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates;
+channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
+channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
+channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
+channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool;
+channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
+channels.getLeftChannels#8341ecc0 offset:int = messages.Chats;
+channels.getGroupsForDiscussion#f5dad378 = messages.Chats;
+channels.setDiscussionGroup#40582bb2 broadcast:InputChannel group:InputChannel = Bool;
+channels.editCreator#8f38cd1f channel:InputChannel user_id:InputUser password:InputCheckPasswordSRP = Updates;
+channels.editLocation#58e63f6d channel:InputChannel geo_point:InputGeoPoint address:string = Bool;
+channels.toggleSlowMode#edd49ef0 channel:InputChannel seconds:int = Updates;
+channels.getInactiveChannels#11e831ee = messages.InactiveChats;
+
+bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
+bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
+bots.setBotCommands#805d46f6 commands:Vector<BotCommand> = Bool;
+
+payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm;
+payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt;
+payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
+payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult;
+payments.getSavedInfo#227d824b = payments.SavedInfo;
+payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
+payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
+
+stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> = messages.StickerSet;
+stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
+stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet;
+stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;
+stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet;
+
+phone.getCallConfig#55451fa9 = DataJSON;
+phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
+phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
+phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
+phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool;
+phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates;
+phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
+phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
+
+langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
+langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
+langpack.getDifference#cd984aa5 lang_pack:string lang_code:string from_version:int = LangPackDifference;
+langpack.getLanguages#42c6978f lang_pack:string = Vector<LangPackLanguage>;
+langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage;
+
+folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
+folders.deleteFolder#1c295881 folder_id:int = Updates;
+
+stats.getBroadcastStats#e6300dba flags:# dark:flags.0?true channel:InputChannel tz_offset:int = stats.BroadcastStats;
+stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
+
+// LAYER 112

+ 113 - 0
static/schema.tl

@@ -0,0 +1,113 @@
+// Core types (no need to gen)
+
+//vector#1cb5c415 {t:Type} # [ t ] = Vector t;
+
+///////////////////////////////
+/// Authorization key creation
+///////////////////////////////
+
+resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector<long> = ResPQ;
+
+p_q_inner_data#83c95aec pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data;
+p_q_inner_data_dc#a9f55f95 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data;
+p_q_inner_data_temp#3c6a84d4 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data;
+p_q_inner_data_temp_dc#56fddf88 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data;
+
+server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params;
+server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params;
+
+server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:string g_a:string server_time:int = Server_DH_inner_data;
+
+client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:string = Client_DH_Inner_Data;
+
+dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set_client_DH_params_answer;
+dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer;
+dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = Set_client_DH_params_answer;
+
+destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes;
+destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes;
+destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
+
+---functions---
+
+req_pq#60469778 nonce:int128 = ResPQ;
+req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
+
+req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_data:string = Server_DH_Params;
+
+set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:string = Set_client_DH_params_answer;
+
+destroy_auth_key#d1435160 = DestroyAuthKeyRes;
+
+///////////////////////////////
+////////////// System messages
+///////////////////////////////
+
+---types---
+
+msgs_ack#62d6b459 msg_ids:Vector<long> = MsgsAck;
+
+bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int error_code:int = BadMsgNotification;
+bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long = BadMsgNotification;
+
+msgs_state_req#da69fb52 msg_ids:Vector<long> = MsgsStateReq;
+msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
+msgs_all_info#8cc0d131 msg_ids:Vector<long> info:string = MsgsAllInfo;
+
+msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
+msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
+
+msg_resend_req#7d861a08 msg_ids:Vector<long> = MsgResendReq;
+
+//rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult; // parsed manually
+
+rpc_error#2144ca19 error_code:int error_message:string = RpcError;
+
+rpc_answer_unknown#5e2ad36e = RpcDropAnswer;
+rpc_answer_dropped_running#cd78e586 = RpcDropAnswer;
+rpc_answer_dropped#a43ad8b7 msg_id:long seq_no:int bytes:int = RpcDropAnswer;
+
+future_salt#0949d9dc valid_since:int valid_until:int salt:long = FutureSalt;
+future_salts#ae500895 req_msg_id:long now:int salts:vector<future_salt> = FutureSalts;
+
+pong#347773c5 msg_id:long ping_id:long = Pong;
+
+destroy_session_ok#e22045fc session_id:long = DestroySessionRes;
+destroy_session_none#62d350c9 session_id:long = DestroySessionRes;
+
+new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = NewSession;
+
+//message msg_id:long seqno:int bytes:int body:Object = Message; // parsed manually
+//msg_container#73f1f8dc messages:vector<message> = MessageContainer; // parsed manually
+//msg_copy#e06046b2 orig_message:Message = MessageCopy; // parsed manually, not used - use msg_container
+//gzip_packed#3072cfa1 packed_data:string = Object; // parsed manually
+
+http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait;
+
+//ipPort ipv4:int port:int = IpPort;
+//help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector<ipPort> = help.ConfigSimple;
+
+ipPort#d433ad73 ipv4:int port:int = IpPort;
+ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort;
+accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector<IpPort> = AccessPointRule;
+help.configSimple#5a592a6c date:int expires:int rules:vector<AccessPointRule> = help.ConfigSimple;
+
+tlsClientHello blocks:vector<TlsBlock> = TlsClientHello;
+
+tlsBlockString data:string = TlsBlock;
+tlsBlockRandom length:int = TlsBlock;
+tlsBlockZero length:int = TlsBlock;
+tlsBlockDomain = TlsBlock;
+tlsBlockGrease seed:int = TlsBlock;
+tlsBlockScope entries:Vector<TlsBlock> = TlsBlock;
+
+---functions---
+
+rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;
+
+get_future_salts#b921bd04 num:int = FutureSalts;
+
+ping#7abe77ec ping_id:long = Pong;
+ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;
+
+destroy_session#e7512126 session_id:long = DestroySessionRes;

+ 28 - 0
tsconfig.json

@@ -0,0 +1,28 @@
+{
+    "compilerOptions": {
+        // We don't care about this since Parcel runs Babel after TypeScript
+        "module": "commonjs",
+        "target": "es5",
+        "lib": [
+            "dom",
+            "es2015",
+            "es5",
+            "es6",
+            "es7"
+        ],
+
+        "allowJs": false,
+        "skipLibCheck": true,
+        "esModuleInterop": true,
+        "allowSyntheticDefaultImports": true,
+        "strict": true,
+        "forceConsistentCasingInFileNames": true,
+        "moduleResolution": "node",
+        "resolveJsonModule": true,
+        "isolatedModules": true,
+        "jsx": "react"
+    },
+    "include": [
+        "gramjs"
+    ]
+}

+ 51 - 0
webpack.config.js

@@ -0,0 +1,51 @@
+module.exports = {
+    entry: './gramjs/index.js',
+    mode: 'development',
+    resolve: {
+        extensions: ['.js', '.ts', '.tsx'],
+    },
+    node: {
+        fs: 'empty',
+        net: 'empty',
+    },
+    module: {
+        rules: [
+            {   test: /\.tsx?$/,
+                use: 'ts-loader',
+                exclude: [
+                    /node_modules/,
+                ]
+            },
+            {
+                test: /\.js$/,
+                loader: 'babel-loader',
+                exclude: /node_modules/,
+            },
+            {
+                test: /\.(woff(2)?|ttf|eot|svg|png|jpg|tgs)(\?v=\d+\.\d+\.\d+)?$/,
+                loader: 'file-loader',
+                options: {
+                    name: '[name].[contenthash].[ext]',
+                },
+            },
+            {
+                test: /\.wasm$/,
+                type: 'javascript/auto',
+                loader: 'file-loader',
+                options: {
+                    name: '[name].[contenthash].[ext]',
+                },
+            },
+            {
+                test: /\.tl$/i,
+                loader: 'raw-loader',
+            },
+        ],
+    },
+    output: {
+        path: __dirname + '/browser',
+        filename: 'gramjs.js',
+        libraryTarget: 'var',
+        library: 'gramjs',
+    },
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно