Ver código fonte

Merge pull request #44 from gram-js/dev

merge contest version into main
painor 4 anos atrás
pai
commit
68f4836776
100 arquivos alterados com 14033 adições e 8206 exclusões
  1. 16 3
      .bablerc
  2. 1 3
      .gitignore
  3. 0 2
      .idea/.gitignore
  4. 5 3
      README.md
  5. 0 1
      browser/LAYER
  6. 0 96
      browser/gramjs.js
  7. 1 0
      empty.txt
  8. 2 2
      examples/simpleLogin.html
  9. 1 3
      examples/simpleLogin.js
  10. 12 0
      gramjs/.editorconfig
  11. 152 124
      gramjs/Helpers.js
  12. 76 62
      gramjs/Password.js
  13. 171 637
      gramjs/Utils.js
  14. 22 0
      gramjs/client/TelegramClient.d.ts
  15. 423 372
      gramjs/client/TelegramClient.js
  16. 332 0
      gramjs/client/auth.ts
  17. 192 0
      gramjs/client/downloadFile.ts
  18. 129 0
      gramjs/client/uploadFile.ts
  19. 0 80
      gramjs/crypto/AES.js
  20. 0 23
      gramjs/crypto/AESCTR.js
  21. 33 15
      gramjs/crypto/AuthKey.js
  22. 17 0
      gramjs/crypto/CTR.js
  23. 59 71
      gramjs/crypto/Factorizator.js
  24. 37 0
      gramjs/crypto/IGE.js
  25. 44 76
      gramjs/crypto/RSA.js
  26. 55 0
      gramjs/crypto/converters.ts
  27. 123 0
      gramjs/crypto/crypto.js
  28. 0 12
      gramjs/crypto/index.js
  29. 51 0
      gramjs/crypto/words.ts
  30. 13 17
      gramjs/errors/Common.js
  31. 8 4
      gramjs/errors/RPCBaseErrors.js
  32. 87 0
      gramjs/errors/RPCErrorList.js
  33. 4 7
      gramjs/errors/index.js
  34. 31 73
      gramjs/events/NewMessage.js
  35. 10 12
      gramjs/events/Raw.js
  36. 5 5
      gramjs/extensions/BinaryReader.js
  37. 0 273
      gramjs/extensions/HTML.js
  38. 16 17
      gramjs/extensions/Logger.js
  39. 0 166
      gramjs/extensions/Markdown.js
  40. 14 11
      gramjs/extensions/MessagePacker.js
  41. 31 8
      gramjs/extensions/PromisedNetSockets.js
  42. 44 21
      gramjs/extensions/PromisedWebSockets.js
  43. 0 58
      gramjs/extensions/Scanner.js
  44. 0 15
      gramjs/extensions/index.js
  45. 10 0
      gramjs/index.d.ts
  46. 6 6
      gramjs/index.js
  47. 43 60
      gramjs/network/Authenticator.js
  48. 12 22
      gramjs/network/MTProtoPlainSender.js
  49. 129 52
      gramjs/network/MTProtoSender.js
  50. 38 28
      gramjs/network/MTProtoState.js
  51. 18 12
      gramjs/network/connection/Connection.js
  52. 9 7
      gramjs/network/connection/TCPAbridged.js
  53. 45 44
      gramjs/network/connection/TCPFull.js
  54. 7 6
      gramjs/network/connection/TCPObfuscated.js
  55. 14 0
      gramjs/network/index.js
  56. 25 3
      gramjs/sessions/Abstract.js
  57. 126 0
      gramjs/sessions/CacheApiSession.js
  58. 0 320
      gramjs/sessions/JSONSession.js
  59. 20 19
      gramjs/sessions/Memory.js
  60. 0 258
      gramjs/sessions/SQLiteSession.js
  61. 39 20
      gramjs/sessions/StringSession.js
  62. 3 2
      gramjs/sessions/index.js
  63. 19 0
      gramjs/tl/AllTLObjects.js
  64. 10113 0
      gramjs/tl/api.d.ts
  65. 391 0
      gramjs/tl/api.js
  66. 24 12
      gramjs/tl/core/GZIPPacked.js
  67. 4 4
      gramjs/tl/core/MessageContainer.js
  68. 4 4
      gramjs/tl/core/RPCResult.js
  69. 3 3
      gramjs/tl/core/TLMessage.js
  70. 0 0
      gramjs/tl/custom/index.js
  71. 326 0
      gramjs/tl/generationHelpers.js
  72. 8 9
      gramjs/tl/index.js
  73. 104 22
      gramjs/tl/static/api.tl
  74. 0 0
      gramjs/tl/static/schema.tl
  75. 0 81
      gramjs/tl/tlobject.js
  76. 57 0
      gramjs/tl/types-generator/generate.js
  77. 219 0
      gramjs/tl/types-generator/template.js
  78. 0 290
      gramjs_generator/data/errors.csv
  79. 0 27
      gramjs_generator/data/friendly.csv
  80. 0 50
      gramjs_generator/data/html/404.html
  81. 0 236
      gramjs_generator/data/html/core.html
  82. 0 185
      gramjs_generator/data/html/css/docs.dark.css
  83. 0 229
      gramjs_generator/data/html/css/docs.h4x0r.css
  84. 0 182
      gramjs_generator/data/html/css/docs.light.css
  85. 0 35
      gramjs_generator/data/html/img/arrow.svg
  86. 0 244
      gramjs_generator/data/html/js/search.js
  87. 0 300
      gramjs_generator/data/methods.csv
  88. 0 390
      gramjs_generator/docswriter.js
  89. 0 827
      gramjs_generator/generators/docs.js
  90. 0 95
      gramjs_generator/generators/errors.js
  91. 0 10
      gramjs_generator/generators/index.js
  92. 0 916
      gramjs_generator/generators/tlobject.js
  93. 0 92
      gramjs_generator/parsers/errors.js
  94. 0 14
      gramjs_generator/parsers/index.js
  95. 0 65
      gramjs_generator/parsers/methods.js
  96. 0 10
      gramjs_generator/parsers/tlobject/index.js
  97. 0 188
      gramjs_generator/parsers/tlobject/parser.js
  98. 0 287
      gramjs_generator/parsers/tlobject/tlarg.js
  99. 0 191
      gramjs_generator/parsers/tlobject/tlobject.js
  100. 0 77
      gramjs_generator/sourcebuilder.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/

+ 0 - 2
.idea/.gitignore

@@ -1,2 +0,0 @@
-# Default ignored files
-/workspace.xml

+ 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))` .

+ 0 - 1
browser/LAYER

@@ -1 +0,0 @@
-105

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 96
browser/gramjs.js


+ 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,

Diferenças do arquivo suprimidas por serem muito extensas
+ 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;

Diferenças do arquivo suprimidas por serem muito extensas
+ 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();
+}

+ 0 - 80
gramjs/crypto/AES.js

@@ -1,80 +0,0 @@
-const aesjs = require('aes-js')
-const { generateRandomBytes } = require('../Helpers')
-
-class AES {
-    /**
-     * Decrypts the given text in 16-bytes blocks by using the given key and 32-bytes initialization vector
-     * @param cipherText {Buffer}
-     * @param key {Buffer}
-     * @param iv {Buffer}
-     * @returns {Buffer}
-     */
-    static decryptIge(cipherText, key, iv) {
-        let iv1 = iv.slice(0, Math.floor(iv.length / 2))
-        let iv2 = iv.slice(Math.floor(iv.length / 2))
-        let plainText = new Array(cipherText.length).fill(0)
-        const aes = new aesjs.AES(key)
-        const blocksCount = Math.floor(plainText.length / 16)
-        const cipherTextBlock = new Array(16).fill(0)
-
-        for (let blockIndex = 0; blockIndex < blocksCount; blockIndex++) {
-            for (let i = 0; i < 16; i++) {
-                cipherTextBlock[i] = cipherText[blockIndex * 16 + i] ^ iv2[i]
-            }
-            const plainTextBlock = aes.decrypt(cipherTextBlock)
-            for (let i = 0; i < 16; i++) {
-                plainTextBlock[i] ^= iv1[i]
-            }
-
-            iv1 = cipherText.slice(blockIndex * 16, blockIndex * 16 + 16)
-            iv2 = plainTextBlock.slice(0, 16)
-            plainText = new Uint8Array([
-                ...plainText.slice(0, blockIndex * 16),
-                ...plainTextBlock.slice(0, 16),
-                ...plainText.slice(blockIndex * 16 + 16),
-            ])
-        }
-        return Buffer.from(plainText)
-    }
-
-    /**
-     * Encrypts the given text in 16-bytes blocks by using the given key and 32-bytes initialization vector
-     * @param plainText {Buffer}
-     * @param key {Buffer}
-     * @param iv {Buffer}
-     * @returns {Buffer}
-     */
-    static encryptIge(plainText, key, iv) {
-        const padding = plainText.length % 16
-        if (padding) {
-            plainText = Buffer.concat([plainText, generateRandomBytes(16 - padding)])
-        }
-
-        let iv1 = iv.slice(0, Math.floor(iv.length / 2))
-        let iv2 = iv.slice(Math.floor(iv.length / 2))
-
-        const aes = new aesjs.AES(key)
-        let cipherText = Buffer.alloc(0)
-        const blockCount = Math.floor(plainText.length / 16)
-
-        for (let blockIndex = 0; blockIndex < blockCount; blockIndex++) {
-            const plainTextBlock = Buffer.from(plainText.slice(blockIndex * 16, blockIndex * 16 + 16))
-
-            for (let i = 0; i < 16; i++) {
-                plainTextBlock[i] ^= iv1[i]
-            }
-            const cipherTextBlock = Buffer.from(aes.encrypt(plainTextBlock))
-
-            for (let i = 0; i < 16; i++) {
-                cipherTextBlock[i] ^= iv2[i]
-            }
-
-            iv1 = cipherTextBlock
-            iv2 = plainText.slice(blockIndex * 16, blockIndex * 16 + 16)
-            cipherText = Buffer.concat([cipherText, cipherTextBlock])
-        }
-        return cipherText
-    }
-}
-
-module.exports = AES

+ 0 - 23
gramjs/crypto/AESCTR.js

@@ -1,23 +0,0 @@
-const aesjs = require('aes-js')
-const stackTrace = require('stack-trace')
-
-class AESModeCTR {
-    constructor(key, iv) {
-        if (!(key instanceof Buffer) || !(iv instanceof Buffer) || iv.length !== 16) {
-            throw new Error('Key and iv need to be a buffer')
-        }
-        this.cipher = new aesjs.ModeOfOperation.ctr(Buffer.from(key), Buffer.from(iv))
-    }
-
-    encrypt(data) {
-        const res = this.cipher.encrypt(data)
-        return Buffer.from(res)
-    }
-
-    decrypt(data) {
-        return Buffer.from(this.cipher.decrypt(data))
-    }
-
-}
-
-module.exports = AESModeCTR

+ 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
+}

+ 0 - 12
gramjs/crypto/index.js

@@ -1,12 +0,0 @@
-const AES = require('./AES')
-const AESCTR = require('./AESCTR')
-const AuthKey = require('./AuthKey')
-const Factorizator = require('./Factorizator')
-const RSA = require('./RSA')
-module.exports = {
-    AES,
-    AESCTR,
-    AuthKey,
-    Factorizator,
-    RSA,
-}

+ 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)
     }
 
     /**

+ 0 - 273
gramjs/extensions/HTML.js

@@ -1,273 +0,0 @@
-/* eslint-disable no-extend-native */
-/* eslint-disable no-case-declarations, no-fallthrough */
-const Scanner = require('./Scanner')
-const {
-    MessageEntityBold, MessageEntityItalic, MessageEntityCode,
-    MessageEntityPre, MessageEntityEmail, MessageEntityTextUrl,
-    MessageEntityUnderline, MessageEntityStrike, MessageEntityBlockquote,
-} = require('../tl/types')
-
-class HTMLParser extends Scanner {
-    constructor(str) {
-        super(str)
-        this.text = ''
-        this.entities = []
-        this._buildingEntities = {}
-        this._openTags = []
-        this._openTagsMeta = []
-    }
-
-    parse() {
-        while (!this.eof()) {
-            switch (this.peek(1)) {
-            case '<':
-                this.consume(1)
-                if (this.peek(1) === '/') {
-                    // Closing tag
-                    this.consume(1)
-                    const tag = this.scanUntil('>').trim()
-
-                    // Consume the closing bracket
-                    this.consume(1)
-
-                    this.handleEndTag(tag)
-                } else {
-                    // Opening tag
-                    let tag = this.scanUntil('>').trim()
-                    let attrs
-
-                    // Consume the closing bracket
-                    this.consume(1);
-
-                    [tag, ...attrs] = tag.split(/\s+/)
-                    attrs = attrs
-                        // Split on `=`
-                        .map((a) => a.split('='))
-                        // Take non key/value items and make them `true`
-                        .map((a) => a.length === 1 ? a.concat([true]) : a)
-                        // Remove quotes if they exist
-                        .map((a) => {
-                            const attr = a[1].replace(/^('|")|('|")$/g, '')
-                            return [a[0], attr]
-                        })
-                        .reduce((p, c) => {
-                            p[c[0]] = c[1]
-                            return p
-                        }, {})
-
-                    this.handleStartTag(tag, attrs)
-                }
-                break
-            default:
-                if (this.eof()) break
-                this.handleData(this.chr)
-                this.pos += 1
-            }
-        }
-
-        return [this.text, this.entities]
-    }
-
-    static unparse(text, entities, _offset = 0, _length = null) {
-        if (!_length) {
-            _length = text.length
-        }
-
-        const html = []
-        let lastOffset = 0
-
-        for (const [i, entity] of entities.entries()) {
-            if (entity.offset > _offset + _length) {
-                break
-            }
-
-            const relativeOffset = entity.offset - _offset
-            if (relativeOffset > lastOffset) {
-                html.push(text.substring(lastOffset, relativeOffset))
-            } else if (relativeOffset < lastOffset) {
-                continue
-            }
-
-            let skipEntity = false
-            let length = entity.length
-
-            while ((relativeOffset < _length) &&
-                   ('\ud800' <= text.substring(relativeOffset, length)) &&
-                   (text.substring(relativeOffset, length) <= '\udfff')) {
-                length += 1
-            }
-
-            const entityText = this.unparse(
-                text.substring(relativeOffset, relativeOffset + length),
-                entities.slice(i + 1, entities.length),
-                entity.offset,
-                length,
-            )
-
-            const entityType = entity.constructor.name
-
-            switch (entityType) {
-            case 'MessageEntityBold':
-                html.push(`<strong>${entityText}</strong>`)
-                break
-            case 'MessageEntityItalic':
-                html.push(`<em>${entityText}</em>`)
-                break
-            case 'MessageEntityCode':
-                html.push(`<code>${entityText}</code>`)
-                break
-            case 'MessageEntityUnderline':
-                html.push(`<u>${entityText}</u>`)
-                break
-            case 'MessageEntityStrike':
-                html.push(`<del>${entityText}</del>`)
-                break
-            case 'MessageEntityBlockquote':
-                html.push(`<blockquote>${entityText}</blockquote>`)
-                break
-            case 'MessageEntityPre':
-                if (entity.language) {
-                    html.push(`<pre>
-                      <code class="language-${entity.language}">
-                        ${entityText}
-                      </code>
-                    </pre>`)
-                } else {
-                    html.push(`<pre>${entityText}</pre>`)
-                }
-                break
-            case 'MessageEntityEmail':
-                html.push(`<a href="mailto:${entityText}">${entityText}</a>`)
-                break
-            case 'MessageEntityUrl':
-                html.push(`<a href="${entityText}">${entityText}</a>`)
-                break
-            case 'MessageEntityTextUrl':
-                html.push(`<a href="${entity.url}">${entityText}</a>`)
-                break
-            case 'MessageEntityMentionName':
-                html.push(`<a href="tg://user?id=${entity.userId}">${entityText}</a>`)
-                break
-            default:
-                skipEntity = true
-            }
-
-            lastOffset = relativeOffset + (skipEntity ? 0 : length)
-        }
-
-        while ((lastOffset < _length) &&
-               ('\ud800' <= text.substring(lastOffset)) &&
-               (text.substring(lastOffset) <= '\udfff')) {
-            lastOffset += 1
-        }
-
-        html.push(text.substring(lastOffset, text.length))
-        return html.join('')
-    }
-
-    handleStartTag(tag, attrs = {}) {
-        this._openTags.unshift(tag)
-        this._openTagsMeta.unshift(null)
-
-        let EntityType
-        const args = {}
-
-        switch (tag) {
-        case 'b':
-        case 'strong':
-            EntityType = MessageEntityBold
-            break
-        case 'i':
-        case 'em':
-            EntityType = MessageEntityItalic
-            break
-        case 'u':
-            EntityType = MessageEntityUnderline
-            break
-        case 's':
-        case 'del':
-            EntityType = MessageEntityStrike
-            break
-        case 'blockquote':
-            EntityType = MessageEntityBlockquote
-            break
-        case 'code':
-            // If we're in the middle of a <pre> tag, this <code> tag is
-            // probably intended for syntax highlighting.
-            //
-            // Syntax highlighting is set with
-            //     <code class='language-...'>codeblock</code>
-            // inside <pre> tags
-            const pre = this._buildingEntities['pre']
-            const language = attrs['class'] ? attrs['class'].match(/language-(\S+)/)[1] : null
-            if (pre && language) {
-                pre.language = language
-            } else {
-                EntityType = MessageEntityCode
-            }
-            break
-        case 'pre':
-            EntityType = MessageEntityPre
-            args['language'] = ''
-            break
-        case 'a':
-            let url = attrs['href']
-            if (!url) return
-
-            if (url.indexOf('mailto:') === 0) {
-                EntityType = MessageEntityEmail
-            } else {
-                EntityType = MessageEntityTextUrl
-                args['url'] = url
-                url = null
-            }
-
-            this._openTagsMeta.shift()
-            this._openTagsMeta.unshift(url)
-            break
-        default:
-            // Do nothing
-        }
-
-        if (EntityType && !(tag in this._buildingEntities)) {
-            this._buildingEntities[tag] = new EntityType({
-                offset: this.text.length,
-                // The length will be determined when closing the tag.
-                length: 0,
-                ...args,
-            })
-        }
-    }
-
-    handleData(text) {
-        for (const [, entity] of Object.entries(this._buildingEntities)) {
-            entity.length += text.length
-        }
-
-        this.text += text
-    }
-
-    handleEndTag(tag) {
-        this._openTags.shift()
-        this._openTagsMeta.shift()
-
-        const entity = this._buildingEntities[tag]
-        if (entity) {
-            delete this._buildingEntities[tag]
-            this.entities.push(entity)
-        }
-    }
-}
-
-const parse = (str) => {
-    const parser = new HTMLParser(str)
-    return parser.parse()
-}
-
-const unparse = HTMLParser.unparse
-
-module.exports = {
-    HTMLParser,
-    parse,
-    unparse,
-}

+ 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')
+
         }
     }
 }

+ 0 - 166
gramjs/extensions/Markdown.js

@@ -1,166 +0,0 @@
-/* eslint-disable no-fallthrough */
-const Scanner = require('./Scanner')
-const {
-    MessageEntityBold, MessageEntityItalic, MessageEntityCode,
-    MessageEntityPre, MessageEntityTextUrl, MessageEntityMentionName,
-    MessageEntityStrike,
-} = require('../tl/types')
-const { regExpEscape } = require('../Helpers')
-
-const URL_RE = /\[([\S\s]+?)\]\((.+?)\)/
-const DELIMITERS = {
-    'MessageEntityBold': '**',
-    'MessageEntityItalic': '__',
-    'MessageEntityCode': '`',
-    'MessageEntityPre': '```',
-    'MessageEntityStrike': '~~',
-}
-
-class MarkdownParser extends Scanner {
-    constructor(str) {
-        super(str)
-        this.text = ''
-        this.entities = []
-    }
-
-    parse() {
-        // Do a little reset
-        this.text = ''
-        this.entities = []
-
-        while (!this.eof()) {
-            switch (this.chr) {
-            case '*':
-                if (this.peek(2) == '**') {
-                    if (this.parseEntity(MessageEntityBold, '**')) break
-                }
-            case '_':
-                if (this.peek(2) == '__') {
-                    if (this.parseEntity(MessageEntityItalic, '__')) break
-                }
-            case '~':
-                if (this.peek(2) == '~~') {
-                    if (this.parseEntity(MessageEntityStrike, '~~')) break
-                }
-            case '`':
-                if (this.peek(3) == '```') {
-                    if (this.parseEntity(MessageEntityPre, '```')) break
-                } else if (this.peek(1) == '`') {
-                    if (this.parseEntity(MessageEntityCode, '`')) break
-                }
-            case '[':
-                if (this.parseURL()) break
-            default:
-                this.text += this.chr
-                this.pos += 1
-            }
-        }
-
-        return [this.text, this.entities]
-    }
-
-    static unparse(text, entities) {
-        if (!text || !entities) return text
-        entities = Array.isArray(entities) ? entities : [entities]
-
-        let insertAt = []
-        for (const entity of entities) {
-            const s = entity.offset
-            const e = entity.offset + entity.length
-            const delimiter = DELIMITERS[entity.constructor.name]
-            if (delimiter) {
-                insertAt.push([s, delimiter])
-                insertAt.push([e, delimiter])
-            } else {
-                let url = null
-                if (entity instanceof MessageEntityTextUrl) {
-                    url = entity.url
-                } else if (entity instanceof MessageEntityMentionName) {
-                    url = `tg://user?id=${entity.userId}`
-                }
-
-                if (url) {
-                    insertAt.push([s, '['])
-                    insertAt.push([e, `](${url})`])
-                }
-            }
-        }
-
-        insertAt = insertAt.sort((a, b) => a[0] - b[0])
-        while (insertAt.length > 0) {
-            let [at, what] = insertAt.pop()
-
-            while ((at < text.length) && '\ud800' <= text[at] && text[at] <= '\udfff') {
-                at += 1
-            }
-
-            text = text.slice(0, at) + what + text.slice(at, text.size)
-        }
-
-        return text
-    }
-
-    parseEntity(EntityType, delimiter) {
-        // The offset for this entity should be the end of the
-        // text string
-        const offset = this.text.length
-
-        // Consume the delimiter
-        this.consume(delimiter.length)
-
-        // Scan until the delimiter is reached again. This is the
-        // entity's content.
-        const content = this.scanUntil(new RegExp(regExpEscape(delimiter)))
-
-        if (content) {
-            // Consume the delimiter again
-            this.consume(delimiter.length)
-
-            // Add the entire content to the text
-            this.text += content
-
-            // Create and return a new Entity
-            const entity = new EntityType({
-                offset,
-                length: content.length,
-            })
-            this.entities.push(entity)
-            return entity
-        }
-    }
-
-    parseURL() {
-        const match = this.rest.match(URL_RE)
-        if (match.index !== 0) return
-
-        const [full, txt, url] = match
-        const len = full.length
-        const offset = this.text.length
-
-        this.text += txt
-
-        const entity = new MessageEntityTextUrl({
-            offset: offset,
-            length: txt.length,
-            url: url,
-        })
-
-        this.consume(len)
-        this.entities.push(entity)
-
-        return entity
-    }
-}
-
-const parse = (str) => {
-    const parser = new MarkdownParser(str)
-    return parser.parse()
-}
-
-const unparse = MarkdownParser.unparse
-
-module.exports = {
-    MarkdownParser,
-    parse,
-    unparse,
-}

+ 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 - 58
gramjs/extensions/Scanner.js

@@ -1,58 +0,0 @@
-class Scanner {
-    constructor(str) {
-        this.str = str
-        this.pos = 0
-        this.lastMatch = null
-    }
-
-    get chr() {
-        return this.str[this.pos]
-    }
-
-    peek(n = 1) {
-        return this.str.slice(this.pos, this.pos + n)
-    }
-
-    reverse(n = 1) {
-        const pos = this.pos - n
-        this.pos = pos < 0 ? 0 : pos
-    }
-
-    consume(n = 1) {
-        return this.str.slice(this.pos, this.pos += n)
-    }
-
-    scanUntil(re, consumeMatch = false) {
-        let match
-        try {
-            match = this.lastMatch = this.rest.match(re)
-        } catch {
-            match = null
-        }
-
-        if (!match) return null
-
-        let len = match.index
-        if (consumeMatch) len += match[0].size
-
-        return this.consume(len)
-    }
-
-    get rest() {
-        return this.str.slice(this.pos, this.str.length) || null
-    }
-
-    reset() {
-        this.pos = 0
-    }
-
-    bof() {
-        return this.pos <= 0
-    }
-
-    eof() {
-        return this.pos >= this.str.length
-    }
-}
-
-module.exports = Scanner

+ 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

+ 0 - 320
gramjs/sessions/JSONSession.js

@@ -1,320 +0,0 @@
-const { generateRandomLong, getRandomInt } = require('../Helpers')
-const fs = require('fs').promises
-const { existsSync, readFileSync } = require('fs')
-const AuthKey = require('../crypto/AuthKey')
-const { TLObject } = require('../tl/tlobject')
-const utils = require('../Utils')
-const types = require('../tl/types')
-
-BigInt.toJSON = function() {
-    return { fool: this.fool.toString('hex') }
-}
-BigInt.parseJson = function() {
-    return { fool: BigInt('0x' + this.fool) }
-}
-
-class Session {
-    constructor(sessionUserId) {
-        this.sessionUserId = sessionUserId
-        this._serverAddress = null
-        this._dcId = 0
-        this._port = null
-        // this.serverAddress = "localhost";
-        // this.port = 21;
-        this.authKey = undefined
-        this.id = generateRandomLong(false)
-        this.sequence = 0
-        this.salt = BigInt(0) // Unsigned long
-        this.timeOffset = BigInt(0)
-        this.lastMessageId = BigInt(0)
-        this.user = undefined
-        this._files = {}
-        this._entities = new Set()
-        this._updateStates = {}
-    }
-
-    /**
-     * Saves the current session object as session_user_id.session
-     */
-    async save() {
-        if (this.sessionUserId) {
-            const str = JSON.stringify(this, function(key, value) {
-                if (typeof value === 'bigint') {
-                    return value.toString() + 'n'
-                } else {
-                    return value
-                }
-            })
-            await fs.writeFile(`${this.sessionUserId}.session`, str)
-        }
-    }
-
-    setDC(dcId, serverAddress, port) {
-        this._dcId = dcId | 0
-        this._serverAddress = serverAddress
-        this._port = port
-    }
-
-    get serverAddress() {
-        return this._serverAddress
-    }
-
-    get port() {
-        return this._port
-    }
-
-    get dcId() {
-        return this._dcId
-    }
-
-    getUpdateState(entityId) {
-        return this._updateStates[entityId]
-    }
-
-    setUpdateState(entityId, state) {
-        return this._updateStates[entityId] = state
-    }
-
-    close() {
-    }
-
-    delete() {
-    }
-
-    _entityValuesToRow(id, hash, username, phone, name) {
-        // While this is a simple implementation it might be overrode by,
-        // other classes so they don't need to implement the plural form
-        // of the method. Don't remove.
-        return [id, hash, username, phone, name]
-    }
-
-    _entityToRow(e) {
-        if (!(e instanceof TLObject)) {
-            return
-        }
-        let p
-        let markedId
-        try {
-            p = utils.getInputPeer(e, false)
-            markedId = utils.getPeerId(p)
-        } catch (e) {
-            // Note: `get_input_peer` already checks for non-zero `access_hash`.
-            // See issues #354 and #392. It also checks that the entity
-            // is not `min`, because its `access_hash` cannot be used
-            // anywhere (since layer 102, there are two access hashes).
-            return
-        }
-        let pHash
-        if (p instanceof types.InputPeerUser || p instanceof types.InputPeerChannel) {
-            pHash = p.accessHash
-        } else if (p instanceof types.InputPeerChat) {
-            pHash = 0
-        } else {
-            return
-        }
-
-        let username = e.username
-        if (username) {
-            username = username.toLowerCase()
-        }
-        const phone = e.phone
-        const name = utils.getDisplayName(e)
-        return this._entityValuesToRow(markedId, pHash, username, phone, name)
-    }
-
-    _entitiesToRows(tlo) {
-        let entities = []
-        if (tlo instanceof TLObject && 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)
-            }
-        }
-        const rows = [] // Rows to add (id, hash, username, phone, name)
-        for (const e of entities) {
-            const row = this._entityToRow(e)
-            if (row) {
-                rows.push(row)
-            }
-        }
-        return rows
-    }
-
-
-    static tryLoadOrCreateNew(sessionUserId) {
-        if (sessionUserId === undefined) {
-            return new Session()
-        }
-        const filepath = `${sessionUserId}.session`
-
-
-        if (existsSync(filepath)) {
-            try {
-                const ob = JSON.parse(readFileSync(filepath, 'utf-8'), function(key, value) {
-                    if (typeof value == 'string' && value.match(/(\d+)n/)) {
-                        return BigInt(value.slice(0, -1))
-                    } else {
-                        return value
-                    }
-                })
-
-                const authKey = new AuthKey(Buffer.from(ob.authKey._key.data))
-                const session = new Session(ob.sessionUserId)
-                session._serverAddress = ob._serverAddress
-                session._port = ob._port
-                session._dcId = ob._dcId
-
-                // this.serverAddress = "localhost";
-                // this.port = 21;
-                session.authKey = authKey
-                session.id = ob.id
-                session.sequence = ob.sequence
-                session.salt = ob.salt // Unsigned long
-                session.timeOffset = ob.timeOffset
-                session.lastMessageId = ob.lastMessageId
-                session.user = ob.user
-                return session
-            } catch (e) {
-                return new Session(sessionUserId)
-            }
-        } else {
-            return new Session(sessionUserId)
-        }
-    }
-
-    getNewMsgId() {
-        const msTime = new Date().getTime()
-        let newMessageId =
-            (BigInt(BigInt(Math.floor(msTime / 1000)) + this.timeOffset) << BigInt(32)) |
-            (BigInt(msTime % 1000) << BigInt(22)) |
-            (BigInt(getRandomInt(0, 524288)) << BigInt(2)) // 2^19
-
-        if (this.lastMessageId >= newMessageId) {
-            newMessageId = this.lastMessageId + BigInt(4)
-        }
-        this.lastMessageId = newMessageId
-        return newMessageId
-    }
-
-    processEntities(tlo) {
-        const entitiesSet = this._entitiesToRows(tlo)
-        for (const e of entitiesSet) {
-            this._entities.add(e)
-        }
-    }
-
-    getEntityRowsByPhone(phone) {
-        for (const e of this._entities) { // id, hash, username, phone, name
-            if (e[3] === phone) {
-                return [e[0], e[1]]
-            }
-        }
-    }
-
-    getEntityRowsByName(name) {
-        for (const e of this._entities) { // id, hash, username, phone, name
-            if (e[4] === name) {
-                return [e[0], e[1]]
-            }
-        }
-    }
-
-    getEntityRowsByUsername(username) {
-        for (const e of this._entities) { // id, hash, username, phone, name
-            if (e[2] === username) {
-                return [e[0], e[1]]
-            }
-        }
-    }
-
-    getEntityRowsById(id, exact = true) {
-        if (exact) {
-            for (const e of this._entities) { // id, hash, username, phone, name
-                if (e[0] === id) {
-                    return [e[0], e[1]]
-                }
-            }
-        } else {
-            const ids = [utils.getPeerId(new types.PeerUser({ userId: id })),
-                utils.getPeerId(new types.PeerChat({ chatId: id })),
-                utils.getPeerId(new types.PeerChannel({ channelId: id })),
-            ]
-            for (const e of this._entities) { // id, hash, username, phone, name
-                if (ids.includes(e[0])) {
-                    return [e[0], e[1]]
-                }
-            }
-        }
-    }
-
-    getInputEntity(key) {
-        let exact
-        if (key.SUBCLASS_OF_ID !== undefined) {
-            if ([0xc91c90b6, 0xe669bf46, 0x40f202fd].includes(key.SUBCLASS_OF_ID)) {
-                // hex(crc32(b'InputPeer', b'InputUser' and b'InputChannel'))
-                // We already have an Input version, so nothing else required
-                return key
-            }
-            // Try to early return if this key can be casted as input peer
-            return utils.getInputPeer(key)
-        } else {
-            // Not a TLObject or can't be cast into InputPeer
-            if (key instanceof TLObject) {
-                key = utils.getPeerId(key)
-                exact = true
-            } else {
-                exact = !(typeof key == 'number') || key < 0
-            }
-        }
-        let result = null
-        if (typeof key === 'string') {
-            const phone = utils.parsePhone(key)
-            if (phone) {
-                result = this.getEntityRowsByPhone(phone)
-            } else {
-                const { username, isInvite } = utils.parseUsername(key)
-                if (username && !isInvite) {
-                    result = this.getEntityRowsByUsername(username)
-                } else {
-                    const tup = utils.resolveInviteLink(key)[1]
-                    if (tup) {
-                        result = this.getEntityRowsById(tup, false)
-                    }
-                }
-            }
-        } else if (typeof key === 'number') {
-            result = this.getEntityRowsById(key, exact)
-        }
-        if (!result && typeof key === 'string') {
-            result = this.getEntityRowsByName(key)
-        }
-
-        if (result) {
-            let entityId = result[0] // unpack resulting tuple
-            const entityHash = result[1]
-            const resolved = utils.resolveId(entityId)
-            entityId = resolved[0]
-            const kind = resolved[1]
-            // removes the mark and returns type of entity
-            if (kind === types.PeerUser) {
-                return new types.InputPeerUser({ userId: entityId, accessHash: entityHash })
-            } else if (kind === types.PeerChat) {
-                return new types.InputPeerChat({ chatId: entityId })
-            } else if (kind === types.PeerChannel) {
-                return new types.InputPeerChannel({ channelId: entityId, accessHash: entityHash })
-            }
-        } else {
-            throw new Error('Could not find input entity with key ' + key)
-        }
-    }
-}
-
-module.exports = Session

+ 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

+ 0 - 258
gramjs/sessions/SQLiteSession.js

@@ -1,258 +0,0 @@
-const EXTENSION = '.session'
-const CURRENT_VERSION = 1
-const AuthKey = require('../crypto/AuthKey')
-const Database = require('better-sqlite3')
-const utils = require('../Utils')
-const { PeerUser, PeerChannel, PeerChat } = require('../tl/types')
-const types = require('../tl/types')
-const fs = require('fs')
-const MemorySession = require('./Memory')
-
-class SQLiteSession extends MemorySession {
-    /**
-     * This session contains the required information to login into your
-     * Telegram account. NEVER give the saved session file to anyone, since
-     * they would gain instant access to all your messages and contacts.
-
-     * If you think the session has been compromised, close all the sessions
-     * through an official Telegram client to revoke the authorization.
-     */
-    constructor(sessionId = null) {
-        super()
-        this.filename = ':memory:'
-        this.saveEntities = true
-
-        if (sessionId) {
-            this.filename = sessionId
-            if (!this.filename.endsWith(EXTENSION)) {
-                this.filename += EXTENSION
-            }
-        }
-
-        this.db = new Database(this.filename)
-        let stmt = this.db.prepare('SELECT name FROM sqlite_master where type=\'table\' and name=\'version\'')
-        if (stmt.get()) {
-            // Tables already exist, check for the version
-            stmt = this.db.prepare('select version from version')
-            const version = stmt.get().version
-            if (version < CURRENT_VERSION) {
-                this._upgradeDatabase(version)
-                this.db.exec('delete from version')
-                stmt = this.db.prepare('insert into version values (?)')
-                stmt.run(CURRENT_VERSION)
-                this.save()
-            }
-
-            // These values will be saved
-            stmt = this.db.prepare('select * from sessions')
-            const res = stmt.get()
-
-
-            if (res) {
-
-                this._dcId = res['dcId']
-                this._serverAddress = res['serverAddress']
-                this._port = res['port']
-                this._authKey = new AuthKey(res['authKey'])
-                this._takeoutId = res['takeoutId']
-            }
-        } else {
-            // Tables don't exist, create new ones
-            this._createTable(
-                'version (version integer primary key)'
-                ,
-                `sessions (
-                dcId integer primary key,
-                    serverAddress text,
-                    port integer,
-                    authKey blob,
-                    takeoutId integer
-                )`,
-                `entities (
-                id integer primary key,
-                    hash integer not null,
-                    username text,
-                    phone integer,
-                    name text
-                )`,
-                `sent_files (
-                md5Digest blob,
-                    fileSize integer,
-                    type integer,
-                    id integer,
-                    hash integer,
-                    primary key(md5Digest, fileSize, type)
-                )`,
-                `updateState (
-                id integer primary key,
-                    pts integer,
-                    qts integer,
-                    date integer,
-                    seq integer
-                )`,
-            )
-            stmt = this.db.prepare('insert into version values (?)')
-            stmt.run(CURRENT_VERSION)
-            this._updateSessionTable()
-            this.save()
-        }
-
-    }
-
-    load() {
-
-    }
-
-    get authKey() {
-        return super.authKey
-    }
-
-    _upgradeDatabase(old) {
-        // nothing so far
-    }
-
-    _createTable(...definitions) {
-        for (const definition of definitions) {
-            this.db.exec(`create table ${definition}`)
-        }
-    }
-
-    // Data from sessions should be kept as properties
-    // not to fetch the database every time we need it
-    setDC(dcId, serverAddress, port) {
-        super.setDC(dcId, serverAddress, port)
-        this._updateSessionTable()
-
-        // Fetch the authKey corresponding to this data center
-        const row = this.db.prepare('select authKey from sessions').get()
-        if (row && row.authKey) {
-            this._authKey = new AuthKey(row.authKey)
-        } else {
-            this._authKey = null
-        }
-    }
-
-    set authKey(value) {
-        this._authKey = value
-        this._updateSessionTable()
-    }
-
-    set takeoutId(value) {
-        this._takeoutId = value
-        this._updateSessionTable()
-    }
-
-    _updateSessionTable() {
-        // While we can save multiple rows into the sessions table
-        // currently we only want to keep ONE as the tables don't
-        // tell us which auth_key's are usable and will work. Needs
-        // some more work before being able to save auth_key's for
-        // multiple DCs. Probably done differently.
-        this.db.exec('delete from sessions')
-        const stmt = this.db.prepare('insert or replace into sessions values (?,?,?,?,?)')
-        stmt.run(this._dcId, this._serverAddress,
-            this._port, this._authKey ? this._authKey.key : Buffer.alloc(0), this._takeoutId)
-    }
-
-    getUpdateState(entityId) {
-        const row = this.db.prepare('select pts, qts, date, seq from updateState where id=?').get(entityId)
-        if (row) {
-            return new types.update.State({
-                pts: row.pts,
-                qts: row.qts, date: new Date(row.date), seq: row.seq, unreadCount: 0,
-            })
-        }
-    }
-
-    setUpdateState(entityId, state) {
-        const stmt = this.db.prepare('insert or replace into updateState values (?,?,?,?,?)')
-        stmt.run(entityId, state.pts, state.qts,
-            state.date.getTime(), state.seq)
-    }
-
-    save() {
-        // currently nothing needs to be done
-    }
-
-    /**
-     * Deletes the current session file
-     */
-    delete() {
-        if (this.db.name === ':memory:') {
-            return true
-        }
-        try {
-            fs.unlinkSync(this.db.name)
-            return true
-        } catch (e) {
-            return false
-        }
-    }
-
-    /**
-     * Lists all the sessions of the users who have ever connected
-     * using this client and never logged out
-     */
-    listSessions() {
-        // ???
-    }
-
-    // Entity processing
-    /**
-     * Processes all the found entities on the given TLObject,
-     * unless .enabled is False.
-     *
-     * Returns True if new input entities were added.
-     * @param tlo
-     */
-    processEntities(tlo) {
-        if (!this.saveEntities) {
-            return
-        }
-        const rows = this._entitiesToRows(tlo)
-        if (!rows) {
-            return
-        }
-        for (const row of rows) {
-            row[1] = Database.Integer(row[1].toString())
-            const stmt = this.db.prepare('insert or replace into entities values (?,?,?,?,?)')
-            stmt.run(...row)
-        }
-    }
-
-    getEntityRowsByPhone(phone) {
-        return this.db.prepare('select id, hash from entities where phone=?').get(phone)
-    }
-
-    getEntityRowsByUsername(username) {
-        return this.db.prepare('select id, hash from entities where username=?').get(username)
-    }
-
-    getEntityRowsByName(name) {
-        return this.db.prepare('select id, hash from entities where name=?').get(name)
-    }
-
-    getEntityRowsById(id, exact = true) {
-        if (exact) {
-            return this.db.prepare('select id, hash from entities where id=?').get(id)
-        } else {
-            return this.db.prepare('select id, hash from entities where id in (?,?,?)').get(
-                utils.getPeerId(new PeerUser(id)),
-                utils.getPeerId(new PeerChat(id)),
-                utils.getPeerId(new PeerChannel(id)),
-            )
-        }
-    }
-
-    // File processing
-
-    getFile(md5Digest, fileSize, cls) {
-        // nope
-    }
-
-    cacheFile(md5Digest, fileSize, instance) {
-        // nope
-    }
-}
-
-module.exports = SQLiteSession

+ 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
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 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"
     }
 }
 

+ 0 - 0
gramjs/tl/custom/index.js


+ 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
 }

+ 104 - 22
gramjs_generator/data/api.tl → gramjs/tl/static/api.tl

@@ -71,7 +71,8 @@ inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int =
 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#6b3765b poll:Poll = 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;
@@ -89,6 +90,7 @@ inputDocumentFileLocation#bad07584 id:long access_hash:long file_reference:bytes
 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;
 
@@ -127,7 +129,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.
 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#2d895c74 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 pts: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;
@@ -155,6 +157,7 @@ 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;
@@ -213,6 +216,7 @@ peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bo
 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;
@@ -347,6 +351,12 @@ 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;
 
@@ -496,8 +506,8 @@ messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMess
 
 webPageEmpty#eb1477e8 id:long = WebPage;
 webPagePending#c586da1c id:long date:int = WebPage;
-webPage#fa64e172 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 documents:flags.11?Vector<Document> cached_page:flags.10?Page = WebPage;
-webPageNotModified#85849473 = 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;
 
@@ -523,6 +533,7 @@ 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;
 
@@ -542,6 +553,7 @@ 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;
 
@@ -568,6 +580,7 @@ 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;
@@ -679,8 +692,8 @@ 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#4ede3cf = messages.FeaturedStickers;
-messages.featuredStickers#f89d88e5 hash:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
+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;
@@ -816,7 +829,7 @@ phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?
 
 phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
 
-phoneCallProtocol#a2bb35cb flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int = PhoneCallProtocol;
+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;
 
@@ -1002,7 +1015,7 @@ pageListOrderedItemBlocks#98dd8936 num:string blocks:Vector<PageBlock> = PageLis
 
 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#ae891bec flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> = Page;
+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;
 
@@ -1011,11 +1024,11 @@ help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:strin
 
 pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
 
-poll#d5529d06 id:long flags:# closed:flags.0?true question:string answers:Vector<PollAnswer> = Poll;
+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 option:bytes voters:int = PollAnswerVoters;
+pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
 
-pollResults#5755785a flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int = PollResults;
+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;
 
@@ -1027,15 +1040,16 @@ chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags
 
 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#a12f40b8 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int intensity:flags.3?int = WallPaperSettings;
+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#d246fd47 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 = AutoDownloadSettings;
+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;
 
@@ -1066,23 +1080,71 @@ 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;
 
-themeDocumentNotModified#483d270c = Theme;
-theme#f7d90ce0 flags:# creator:flags.0?true default:flags.1?true id:long access_hash:long slug:string title:string document:flags.2?Document installs_count:int = Theme;
+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#785188b8 {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 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;
@@ -1103,6 +1165,9 @@ 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;
@@ -1160,12 +1225,15 @@ 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#2b7ffd7f slug:string title:string document:InputDocument = Theme;
-account.updateTheme#3b8ea202 flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument = Theme;
+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;
@@ -1189,7 +1257,7 @@ 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#a356056 geo_point:InputGeoPoint = 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;
@@ -1308,6 +1376,13 @@ messages.getScheduledHistory#e2c2685b peer:InputPeer hash:int = messages.Message
 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;
@@ -1319,7 +1394,7 @@ 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 location:InputFileLocation offset:int limit:int = upload.File;
+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;
@@ -1381,9 +1456,11 @@ channels.setDiscussionGroup#40582bb2 broadcast:InputChannel group:InputChannel =
 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;
@@ -1391,11 +1468,13 @@ payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int inf
 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#9bd86e6a flags:# masks:flags.0?true user_id:InputUser title:string short_name:string stickers:Vector<InputStickerSetItem> = messages.StickerSet;
+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;
@@ -1415,4 +1494,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua
 folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
 folders.deleteFolder#1c295881 folder_id:int = Updates;
 
-// LAYER 105
+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

+ 0 - 0
gramjs_generator/data/mtproto.tl → gramjs/tl/static/schema.tl


+ 0 - 81
gramjs/tl/tlobject.js

@@ -1,81 +0,0 @@
-const struct = require('python-struct')
-
-class TLObject {
-    CONSTRUCTOR_ID = null;
-    SUBCLASS_OF_ID = null;
-
-    static prettyFormat() {
-        // TODO
-    }
-
-    /**
-     * Write bytes by using Telegram guidelines
-     * @param data {Buffer|string}
-     */
-    static 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)
-    }
-
-    static 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') {
-            return struct.pack('<i', dt)
-        }
-        throw Error(`Cannot interpret "${dt}" as a date`)
-    }
-
-    fromReader(reader) {
-        throw Error('not implemented')
-    }
-}
-
-/**
- * Represents a content-related `TLObject` (a request that can be sent).
- */
-class TLRequest extends TLObject {
-    /**
-     *
-     * @param reader {BinaryReader}
-     * @returns {boolean}
-     */
-    readResult(reader) {
-        return reader.tgReadObject()
-    }
-
-    async resolve(client, utils) {}
-}
-
-module.exports = {
-    TLObject,
-    TLRequest,
-}

+ 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    | ')};
+
+}
+`
+}

+ 0 - 290
gramjs_generator/data/errors.csv

@@ -1,290 +0,0 @@
-name,codes,description
-ABOUT_TOO_LONG,400,The provided bio is too long
-ACCESS_TOKEN_EXPIRED,400,Bot token expired
-ACCESS_TOKEN_INVALID,400,The provided token is not valid
-ACTIVE_USER_REQUIRED,401,The method is only available to already activated users
-ADMINS_TOO_MUCH,400,Too many admins
-ADMIN_RANK_EMOJI_NOT_ALLOWED,400,Emoji are not allowed in admin titles or ranks
-ADMIN_RANK_INVALID,400,The given admin title or rank was invalid (possibly larger than 16 characters)
-API_ID_INVALID,400,The api_id/api_hash combination is invalid
-API_ID_PUBLISHED_FLOOD,400,"This API id was published somewhere, you can't use it now"
-ARTICLE_TITLE_EMPTY,400,The title of the article is empty
-AUTH_BYTES_INVALID,400,The provided authorization is invalid
-AUTH_KEY_DUPLICATED,406,"The authorization key (session file) was used under two different IP addresses simultaneously, and can no longer be used. Use the same session exclusively, or use different sessions"
-AUTH_KEY_INVALID,401,The key is invalid
-AUTH_KEY_PERM_EMPTY,401,"The method is unavailable for temporary authorization key, not bound to permanent"
-AUTH_KEY_UNREGISTERED,401,The key is not registered in the system
-AUTH_RESTART,500,Restart the authorization process
-BANNED_RIGHTS_INVALID,400,"You cannot use that set of permissions in this request, i.e. restricting view_messages as a default"
-BOTS_TOO_MUCH,400,There are too many bots in this chat/channel
-BOT_CHANNELS_NA,400,Bots can't edit admin privileges
-BOT_GROUPS_BLOCKED,400,This bot can't be added to groups
-BOT_INLINE_DISABLED,400,This bot can't be used in inline mode
-BOT_INVALID,400,This is not a valid bot
-BOT_METHOD_INVALID,400,The API access for bot users is restricted. The method you tried to invoke cannot be executed as a bot
-BOT_MISSING,400,This method can only be run by a bot
-BOT_PAYMENTS_DISABLED,400,This method can only be run by a bot
-BOT_POLLS_DISABLED,400,You cannot create polls under a bot account
-BROADCAST_ID_INVALID,400,The channel is invalid
-BUTTON_DATA_INVALID,400,The provided button data is invalid
-BUTTON_TYPE_INVALID,400,The type of one of the buttons you provided is invalid
-BUTTON_URL_INVALID,400,Button URL invalid
-CALL_ALREADY_ACCEPTED,400,The call was already accepted
-CALL_ALREADY_DECLINED,400,The call was already declined
-CALL_OCCUPY_FAILED,500,The call failed because the user is already making another call
-CALL_PEER_INVALID,400,The provided call peer object is invalid
-CALL_PROTOCOL_FLAGS_INVALID,400,Call protocol flags invalid
-CDN_METHOD_INVALID,400,This method cannot be invoked on a CDN server. Refer to https://core.telegram.org/cdn#schema for available methods
-CHANNELS_ADMIN_PUBLIC_TOO_MUCH,400,"You're admin of too many public channels, make some channels private to change the username of this channel"
-CHANNELS_TOO_MUCH,400,You have joined too many channels/supergroups
-CHANNEL_INVALID,400,"Invalid channel object. Make sure to pass the right types, for instance making sure that the request is designed for channels or otherwise look for a different one more suited"
-CHANNEL_PRIVATE,400,The channel specified is private and you lack permission to access it. Another reason may be that you were banned from it
-CHANNEL_PUBLIC_GROUP_NA,403,channel/supergroup not available
-CHAT_ABOUT_NOT_MODIFIED,400,About text has not changed
-CHAT_ABOUT_TOO_LONG,400,Chat about too long
-CHAT_ADMIN_INVITE_REQUIRED,403,You do not have the rights to do this
-CHAT_ADMIN_REQUIRED,400 403,"Chat admin privileges are required to do that in the specified chat (for example, to send a message in a channel which is not yours), or invalid permissions used for the channel or group"
-CHAT_FORBIDDEN,,You cannot write in this chat
-CHAT_ID_EMPTY,400,The provided chat ID is empty
-CHAT_ID_INVALID,400,"Invalid object ID for a chat. Make sure to pass the right types, for instance making sure that the request is designed for chats (not channels/megagroups) or otherwise look for a different one more suited\nAn example working with a megagroup and AddChatUserRequest, it will fail because megagroups are channels. Use InviteToChannelRequest instead"
-CHAT_INVALID,400,The chat is invalid for this request
-CHAT_LINK_EXISTS,400,The chat is linked to a channel and cannot be used in that request
-CHAT_NOT_MODIFIED,400,"The chat or channel wasn't modified (title, invites, username, admins, etc. are the same)"
-CHAT_RESTRICTED,400,The chat is restricted and cannot be used in that request
-CHAT_SEND_GIFS_FORBIDDEN,403,You can't send gifs in this chat
-CHAT_SEND_INLINE_FORBIDDEN,400,You cannot send inline results in this chat
-CHAT_SEND_MEDIA_FORBIDDEN,403,You can't send media in this chat
-CHAT_SEND_STICKERS_FORBIDDEN,403,You can't send stickers in this chat
-CHAT_TITLE_EMPTY,400,No chat title provided
-CHAT_WRITE_FORBIDDEN,403,You can't write in this chat
-CODE_EMPTY,400,The provided code is empty
-CODE_HASH_INVALID,400,Code hash invalid
-CODE_INVALID,400,Code invalid (i.e. from email)
-CONNECTION_API_ID_INVALID,400,The provided API id is invalid
-CONNECTION_DEVICE_MODEL_EMPTY,400,Device model empty
-CONNECTION_LANG_PACK_INVALID,400,"The specified language pack is not valid. This is meant to be used by official applications only so far, leave it empty"
-CONNECTION_LAYER_INVALID,400,The very first request must always be InvokeWithLayerRequest
-CONNECTION_NOT_INITED,400,Connection not initialized
-CONNECTION_SYSTEM_EMPTY,400,Connection system empty
-CONTACT_ID_INVALID,400,The provided contact ID is invalid
-DATA_INVALID,400,Encrypted data invalid
-DATA_JSON_INVALID,400,The provided JSON data is invalid
-DATE_EMPTY,400,Date empty
-DC_ID_INVALID,400,This occurs when an authorization is tried to be exported for the same data center one is currently connected to
-DH_G_A_INVALID,400,g_a invalid
-EMAIL_HASH_EXPIRED,400,The email hash expired and cannot be used to verify it
-EMAIL_INVALID,400,The given email is invalid
-EMAIL_UNCONFIRMED_X,400,"Email unconfirmed, the length of the code must be {code_length}"
-EMOTICON_EMPTY,400,The emoticon field cannot be empty
-ENCRYPTED_MESSAGE_INVALID,400,Encrypted message invalid
-ENCRYPTION_ALREADY_ACCEPTED,400,Secret chat already accepted
-ENCRYPTION_ALREADY_DECLINED,400,The secret chat was already declined
-ENCRYPTION_DECLINED,400,The secret chat was declined
-ENCRYPTION_ID_INVALID,400,The provided secret chat ID is invalid
-ENCRYPTION_OCCUPY_FAILED,500,TDLib developer claimed it is not an error while accepting secret chats and 500 is used instead of 420
-ENTITIES_TOO_LONG,400,It is no longer possible to send such long data inside entity tags (for example inline text URLs)
-ENTITY_MENTION_USER_INVALID,400,You can't use this entity
-ERROR_TEXT_EMPTY,400,The provided error message is empty
-EXPORT_CARD_INVALID,400,Provided card is invalid
-EXTERNAL_URL_INVALID,400,External URL invalid
-FIELD_NAME_EMPTY,,The field with the name FIELD_NAME is missing
-FIELD_NAME_INVALID,,The field with the name FIELD_NAME is invalid
-FILE_ID_INVALID,400,The provided file id is invalid
-FILE_MIGRATE_X,303,The file to be accessed is currently stored in DC {new_dc}
-FILE_PARTS_INVALID,400,The number of file parts is invalid
-FILE_PART_0_MISSING,,File part 0 missing
-FILE_PART_EMPTY,400,The provided file part is empty
-FILE_PART_INVALID,400,The file part number is invalid
-FILE_PART_LENGTH_INVALID,400,The length of a file part is invalid
-FILE_PART_SIZE_INVALID,400,The provided file part size is invalid
-FILE_PART_X_MISSING,400,Part {which} of the file is missing from storage
-FILEREF_UPGRADE_NEEDED,406,The file reference needs to be refreshed before being used again
-FIRSTNAME_INVALID,400,The first name is invalid
-FLOOD_TEST_PHONE_WAIT_X,420,A wait of {seconds} seconds is required in the test servers
-FLOOD_WAIT_X,420,A wait of {seconds} seconds is required
-FOLDER_ID_EMPTY,400,The folder you tried to delete was already empty
-FOLDER_ID_INVALID,400,The folder you tried to use was not valid
-FRESH_RESET_AUTHORISATION_FORBIDDEN,406,The current session is too new and cannot be used to reset other authorisations yet
-GIF_ID_INVALID,400,The provided GIF ID is invalid
-GROUPED_MEDIA_INVALID,400,Invalid grouped media
-HASH_INVALID,400,The provided hash is invalid
-HISTORY_GET_FAILED,500,Fetching of history failed
-IMAGE_PROCESS_FAILED,400,Failure while processing image
-INLINE_RESULT_EXPIRED,400,The inline query expired
-INPUT_CONSTRUCTOR_INVALID,400,The provided constructor is invalid
-INPUT_FETCH_ERROR,,An error occurred while deserializing TL parameters
-INPUT_FETCH_FAIL,400,Failed deserializing TL payload
-INPUT_LAYER_INVALID,400,The provided layer is invalid
-INPUT_METHOD_INVALID,,The invoked method does not exist anymore or has never existed
-INPUT_REQUEST_TOO_LONG,400,The input request was too long. This may be a bug in the library as it can occur when serializing more bytes than it should (like appending the vector constructor code at the end of a message)
-INPUT_USER_DEACTIVATED,400,The specified user was deleted
-INTERDC_X_CALL_ERROR,,An error occurred while communicating with DC {dc}
-INTERDC_X_CALL_RICH_ERROR,,A rich error occurred while communicating with DC {dc}
-INVITE_HASH_EMPTY,400,The invite hash is empty
-INVITE_HASH_EXPIRED,400,The chat the user tried to join has expired and is not valid anymore
-INVITE_HASH_INVALID,400,The invite hash is invalid
-LANG_PACK_INVALID,400,The provided language pack is invalid
-LASTNAME_INVALID,,The last name is invalid
-LIMIT_INVALID,400,An invalid limit was provided. See https://core.telegram.org/api/files#downloading-files
-LINK_NOT_MODIFIED,400,The channel is already linked to this group
-LOCATION_INVALID,400,The location given for a file was invalid. See https://core.telegram.org/api/files#downloading-files
-MAX_ID_INVALID,400,The provided max ID is invalid
-MAX_QTS_INVALID,400,The provided QTS were invalid
-MD5_CHECKSUM_INVALID,,The MD5 check-sums do not match
-MEDIA_CAPTION_TOO_LONG,400,The caption is too long
-MEDIA_EMPTY,400,The provided media object is invalid
-MEDIA_INVALID,400,Media invalid
-MEDIA_NEW_INVALID,400,The new media to edit the message with is invalid (such as stickers or voice notes)
-MEDIA_PREV_INVALID,400,The old media cannot be edited with anything else (such as stickers or voice notes)
-MEGAGROUP_ID_INVALID,400,The group is invalid
-MEGAGROUP_PREHISTORY_HIDDEN,400,You can't set this discussion group because it's history is hidden
-MEMBER_NO_LOCATION,500,An internal failure occurred while fetching user info (couldn't find location)
-MEMBER_OCCUPY_PRIMARY_LOC_FAILED,500,Occupation of primary member location failed
-MESSAGE_AUTHOR_REQUIRED,403,Message author required
-MESSAGE_DELETE_FORBIDDEN,403,"You can't delete one of the messages you tried to delete, most likely because it is a service message."
-MESSAGE_EDIT_TIME_EXPIRED,400,"You can't edit this message anymore, too much time has passed since its creation."
-MESSAGE_EMPTY,400,Empty or invalid UTF-8 message was sent
-MESSAGE_IDS_EMPTY,400,No message ids were provided
-MESSAGE_ID_INVALID,400,"The specified message ID is invalid or you can't do that operation on such message"
-MESSAGE_NOT_MODIFIED,400,Content of the message was not modified
-MESSAGE_TOO_LONG,400,Message was too long. Current maximum length is 4096 UTF-8 characters
-MSG_WAIT_FAILED,400,A waiting call returned an error
-MT_SEND_QUEUE_TOO_LONG,500,
-NEED_CHAT_INVALID,500,The provided chat is invalid
-NEED_MEMBER_INVALID,500,The provided member is invalid or does not exist (for example a thumb size)
-NETWORK_MIGRATE_X,303,The source IP address is associated with DC {new_dc}
-NEW_SALT_INVALID,400,The new salt is invalid
-NEW_SETTINGS_INVALID,400,The new settings are invalid
-OFFSET_INVALID,400,"The given offset was invalid, it must be divisible by 1KB. See https://core.telegram.org/api/files#downloading-files"
-OFFSET_PEER_ID_INVALID,400,The provided offset peer is invalid
-OPTIONS_TOO_MUCH,400,You defined too many options for the poll
-PACK_SHORT_NAME_INVALID,400,"Invalid sticker pack name. It must begin with a letter, can't contain consecutive underscores and must end in ""_by_<bot username>""."
-PACK_SHORT_NAME_OCCUPIED,400,A stickerpack with this name already exists
-PARTICIPANTS_TOO_FEW,400,Not enough participants
-PARTICIPANT_CALL_FAILED,500,Failure while making call
-PARTICIPANT_VERSION_OUTDATED,400,The other participant does not use an up to date telegram client with support for calls
-PASSWORD_EMPTY,400,The provided password is empty
-PASSWORD_HASH_INVALID,400,The password (and thus its hash value) you entered is invalid
-PASSWORD_REQUIRED,400,The account must have 2-factor authentication enabled (a password) before this method can be used
-PAYMENT_PROVIDER_INVALID,400,The payment provider was not recognised or its token was invalid
-PEER_FLOOD,,Too many requests
-PEER_ID_INVALID,400,An invalid Peer was used. Make sure to pass the right peer type
-PEER_ID_NOT_SUPPORTED,400,The provided peer ID is not supported
-PERSISTENT_TIMESTAMP_EMPTY,400,Persistent timestamp empty
-PERSISTENT_TIMESTAMP_INVALID,400,Persistent timestamp invalid
-PERSISTENT_TIMESTAMP_OUTDATED,500,Persistent timestamp outdated
-PHONE_CODE_EMPTY,400,The phone code is missing
-PHONE_CODE_EXPIRED,400,The confirmation code has expired
-PHONE_CODE_HASH_EMPTY,,The phone code hash is missing
-PHONE_CODE_INVALID,400,The phone code entered was invalid
-PHONE_MIGRATE_X,303,The phone number a user is trying to use for authorization is associated with DC {new_dc}
-PHONE_NUMBER_APP_SIGNUP_FORBIDDEN,400,
-PHONE_NUMBER_BANNED,400,The used phone number has been banned from Telegram and cannot be used anymore. Maybe check https://www.telegram.org/faq_spam
-PHONE_NUMBER_FLOOD,400,You asked for the code too many times.
-PHONE_NUMBER_INVALID,400 406,The phone number is invalid
-PHONE_NUMBER_OCCUPIED,400,The phone number is already in use
-PHONE_NUMBER_UNOCCUPIED,400,The phone number is not yet being used
-PHONE_PASSWORD_FLOOD,406,You have tried logging in too many times
-PHONE_PASSWORD_PROTECTED,400,This phone is password protected
-PHOTO_CONTENT_URL_EMPTY,400,The content from the URL used as a photo appears to be empty or has caused another HTTP error
-PHOTO_CROP_SIZE_SMALL,400,Photo is too small
-PHOTO_EXT_INVALID,400,The extension of the photo is invalid
-PHOTO_INVALID,400,Photo invalid
-PHOTO_INVALID_DIMENSIONS,400,The photo dimensions are invalid (hint: `pip install pillow` for `send_file` to resize images)
-PHOTO_SAVE_FILE_INVALID,400,The photo you tried to send cannot be saved by Telegram. A reason may be that it exceeds 10MB. Try resizing it locally
-PHOTO_THUMB_URL_EMPTY,400,The URL used as a thumbnail appears to be empty or has caused another HTTP error
-PIN_RESTRICTED,400,You can't pin messages in private chats with other people
-POLL_OPTION_DUPLICATE,400,A duplicate option was sent in the same poll
-POLL_UNSUPPORTED,400,This layer does not support polls in the issued method
-PRIVACY_KEY_INVALID,400,The privacy key is invalid
-PTS_CHANGE_EMPTY,500,No PTS change
-QUERY_ID_EMPTY,400,The query ID is empty
-QUERY_ID_INVALID,400,The query ID is invalid
-QUERY_TOO_SHORT,400,The query string is too short
-RANDOM_ID_DUPLICATE,500,You provided a random ID that was already used
-RANDOM_ID_INVALID,400,A provided random ID is invalid
-RANDOM_LENGTH_INVALID,400,Random length invalid
-RANGES_INVALID,400,Invalid range provided
-REACTION_EMPTY,400,No reaction provided
-REACTION_INVALID,400,Invalid reaction provided (only emoji are allowed)
-REG_ID_GENERATE_FAILED,500,Failure while generating registration ID
-REPLY_MARKUP_INVALID,400,The provided reply markup is invalid
-REPLY_MARKUP_TOO_LONG,400,The data embedded in the reply markup buttons was too much
-RESULT_ID_DUPLICATE,400,Duplicated IDs on the sent results. Make sure to use unique IDs.
-RESULT_TYPE_INVALID,400,Result type invalid
-RESULTS_TOO_MUCH,400,You sent too many results. See https://core.telegram.org/bots/api#answerinlinequery for the current limit.
-RIGHT_FORBIDDEN,403,Either your admin rights do not allow you to do this or you passed the wrong rights combination (some rights only apply to channels and vice versa)
-RPC_CALL_FAIL,,"Telegram is having internal issues, please try again later."
-RPC_MCGET_FAIL,,"Telegram is having internal issues, please try again later."
-RSA_DECRYPT_FAILED,400,Internal RSA decryption failed
-SCHEDULE_DATE_TOO_LATE,400,The date you tried to schedule is too far in the future (last known limit of 1 year and a few hours)
-SCHEDULE_TOO_MUCH,400,You cannot schedule more messages in this chat (last known limit of 100 per chat)
-SEARCH_QUERY_EMPTY,400,The search query is empty
-SECONDS_INVALID,400,"Slow mode only supports certain values (e.g. 0, 10s, 30s, 1m, 5m, 15m and 1h)"
-SEND_MESSAGE_MEDIA_INVALID,400,The message media was invalid or not specified
-SEND_MESSAGE_TYPE_INVALID,400,The message type is invalid
-SESSION_EXPIRED,401,The authorization has expired
-SESSION_PASSWORD_NEEDED,401,Two-steps verification is enabled and a password is required
-SESSION_REVOKED,401,"The authorization has been invalidated, because of the user terminating all sessions"
-SHA256_HASH_INVALID,400,The provided SHA256 hash is invalid
-SHORTNAME_OCCUPY_FAILED,400,An error occurred when trying to register the short-name used for the sticker pack. Try a different name
-SLOWMODE_WAIT_X,420,A wait of {seconds} seconds is required before sending another message in this chat
-START_PARAM_EMPTY,400,The start parameter is empty
-START_PARAM_INVALID,400,Start parameter invalid
-STICKERSET_INVALID,400,The provided sticker set is invalid
-STICKERS_EMPTY,400,No sticker provided
-STICKER_EMOJI_INVALID,400,Sticker emoji invalid
-STICKER_FILE_INVALID,400,Sticker file invalid
-STICKER_ID_INVALID,400,The provided sticker ID is invalid
-STICKER_INVALID,400,The provided sticker is invalid
-STICKER_PNG_DIMENSIONS,400,Sticker png dimensions invalid
-STORAGE_CHECK_FAILED,500,Server storage check failed
-STORE_INVALID_SCALAR_TYPE,500,
-TAKEOUT_INIT_DELAY_X,420,A wait of {seconds} seconds is required before being able to initiate the takeout
-TAKEOUT_INVALID,400,The takeout session has been invalidated by another data export session
-TAKEOUT_REQUIRED,400,You must initialize a takeout request first
-TEMP_AUTH_KEY_EMPTY,400,No temporary auth key provided
-Timeout,-503,A timeout occurred while fetching data from the worker
-TMP_PASSWORD_DISABLED,400,The temporary password is disabled
-TOKEN_INVALID,400,The provided token is invalid
-TTL_DAYS_INVALID,400,The provided TTL is invalid
-TYPES_EMPTY,400,The types field is empty
-TYPE_CONSTRUCTOR_INVALID,,The type constructor is invalid
-UNKNOWN_METHOD,500,The method you tried to call cannot be called on non-CDN DCs
-UNTIL_DATE_INVALID,400,That date cannot be specified in this request (try using None)
-URL_INVALID,400,The URL used was invalid (e.g. when answering a callback with an URL that's not t.me/yourbot or your game's URL)
-USERNAME_INVALID,400,"Nobody is using this username, or the username is unacceptable. If the latter, it must match r""[a-zA-Z][\w\d]{3,30}[a-zA-Z\d]"""
-USERNAME_NOT_MODIFIED,400,The username is not different from the current username
-USERNAME_NOT_OCCUPIED,400,The username is not in use by anyone else yet
-USERNAME_OCCUPIED,400,The username is already taken
-USERS_TOO_FEW,400,"Not enough users (to create a chat, for example)"
-USERS_TOO_MUCH,400,"The maximum number of users has been exceeded (to create a chat, for example)"
-USER_ADMIN_INVALID,400,Either you're not an admin or you tried to ban an admin that you didn't promote
-USER_ALREADY_PARTICIPANT,400,The authenticated user is already a participant of the chat
-USER_BANNED_IN_CHANNEL,400,You're banned from sending messages in supergroups/channels
-USER_BLOCKED,400,User blocked
-USER_BOT,400,Bots can only be admins in channels.
-USER_BOT_INVALID,400 403,This method can only be called by a bot
-USER_BOT_REQUIRED,400,This method can only be called by a bot
-USER_CHANNELS_TOO_MUCH,403,One of the users you tried to add is already in too many channels/supergroups
-USER_CREATOR,400,"You can't leave this channel, because you're its creator"
-USER_DEACTIVATED,401,The user has been deleted/deactivated
-USER_DEACTIVATED_BAN,401,The user has been deleted/deactivated
-USER_ID_INVALID,400,"Invalid object ID for a user. Make sure to pass the right types, for instance making sure that the request is designed for users or otherwise look for a different one more suited"
-USER_INVALID,400,The given user was invalid
-USER_IS_BLOCKED,400 403,User is blocked
-USER_IS_BOT,400,Bots can't send messages to other bots
-USER_KICKED,400,This user was kicked from this supergroup/channel
-USER_MIGRATE_X,303,The user whose identity is being used to execute queries is associated with DC {new_dc}
-USER_NOT_MUTUAL_CONTACT,400 403,The provided user is not a mutual contact
-USER_NOT_PARTICIPANT,400,The target user is not a member of the specified megagroup or channel
-USER_PRIVACY_RESTRICTED,403,The user's privacy settings do not allow you to do this
-USER_RESTRICTED,403,"You're spamreported, you can't create channels or chats."
-VIDEO_CONTENT_TYPE_INVALID,400,The video content type is not supported with the given parameters (i.e. supports_streaming)
-WALLPAPER_FILE_INVALID,400,The given file cannot be used as a wallpaper
-WALLPAPER_INVALID,400,The input wallpaper was not valid
-WC_CONVERT_URL_INVALID,400,WC convert URL invalid
-WEBPAGE_CURL_FAILED,400,Failure while fetching the webpage with cURL
-WEBPAGE_MEDIA_EMPTY,400,Webpage media empty
-WORKER_BUSY_TOO_LONG_RETRY,500,Telegram workers are too busy to respond immediately
-YOU_BLOCKED_USER,400,You blocked this user

+ 0 - 27
gramjs_generator/data/friendly.csv

@@ -1,27 +0,0 @@
-ns,friendly,raw
-account.AccountMethods,takeout,invokeWithTakeout account.initTakeoutSession account.finishTakeoutSession
-auth.AuthMethods,sign_in,auth.signIn auth.importBotAuthorization
-auth.AuthMethods,sign_up,auth.signUp
-auth.AuthMethods,send_code_request,auth.sendCode auth.resendCode
-auth.AuthMethods,log_out,auth.logOut
-auth.AuthMethods,edit_2fa,account.updatePasswordSettings
-bots.BotMethods,inline_query,messages.getInlineBotResults
-chats.ChatMethods,action,messages.setTyping
-chats.ChatMethods,edit_admin,channels.editAdmin messages.editChatAdmin
-chats.ChatMethods,edit_permissions,channels.editBanned messages.editChatDefaultBannedRights
-chats.ChatMethods,iter_participants,channels.getParticipants
-chats.ChatMethods,iter_admin_log,channels.getAdminLog
-dialogs.DialogMethods,iter_dialogs,messages.getDialogs
-dialogs.DialogMethods,iter_drafts,messages.getAllDrafts
-dialogs.DialogMethods,edit_folder,folders.deleteFolder folders.editPeerFolders
-downloads.DownloadMethods,download_media,upload.getFile
-messages.MessageMethods,iter_messages,messages.searchGlobal messages.search messages.getHistory channels.getMessages messages.getMessages
-messages.MessageMethods,send_message,messages.sendMessage
-messages.MessageMethods,forward_messages,messages.forwardMessages
-messages.MessageMethods,edit_message,messages.editInlineBotMessage messages.editMessage
-messages.MessageMethods,delete_messages,channels.deleteMessages messages.deleteMessages
-messages.MessageMethods,send_read_acknowledge,messages.readMentions channels.readHistory messages.readHistory
-updates.UpdateMethods,catch_up,updates.getDifference updates.getChannelDifference
-uploads.UploadMethods,send_file,messages.sendMedia messages.sendMultiMedia messages.uploadMedia
-uploads.UploadMethods,upload_file,upload.saveFilePart upload.saveBigFilePart
-users.UserMethods,get_entity,users.getUsers messages.getChats channels.getChannels contacts.resolveUsername

+ 0 - 50
gramjs_generator/data/html/404.html

@@ -1,50 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <title>Oopsie! | GramJS</title>
-
-        <meta charset="utf-8" />
-        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
-        <meta name="viewport" content="width=device-width, initial-scale=1" />
-        <style type="text/css">
-            body {
-                background-color: #f0f4f8;
-                font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial,
-                    sans-serif;
-            }
-            div {
-                width: 560px;
-                margin: 5em auto;
-                padding: 50px;
-                background-color: #fff;
-                border-radius: 1em;
-            }
-            a:link,
-            a:visited {
-                color: #38488f;
-                text-decoration: none;
-            }
-            @media (max-width: 700px) {
-                body {
-                    background-color: #fff;
-                }
-                div {
-                    width: auto;
-                    margin: 0 auto;
-                    border-radius: 0;
-                    padding: 1em;
-                }
-            }
-        </style>
-    </head>
-    <body>
-        <div>
-            <h1>You seem a bit lost…</h1>
-            <p>
-                You seem to be lost! Don't worry, that's just Telegram's API
-                being itself. Shall we go back to the
-                <a href="index.html">Main Page</a>?
-            </p>
-        </div>
-    </body>
-</html>

+ 0 - 236
gramjs_generator/data/html/core.html

@@ -1,236 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-        <title>GramJS API</title>
-        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-        <link id="style" href="css/docs.dark.css" rel="stylesheet" />
-        <script>
-            (function() {
-                var style = document.getElementById('style');
-
-                // setTheme(<link />, 'light' / 'dark')
-                function setTheme(theme) {
-                    localStorage.setItem('theme', theme);
-                    return (style.href = 'css/docs.' + theme + '.css');
-                }
-
-                // setThemeOnClick(<link />, 'light' / 'dark', <a />)
-                function setThemeOnClick(theme, button) {
-                    return button.addEventListener('click', function(e) {
-                        setTheme(theme);
-                        e.preventDefault();
-                        return false;
-                    });
-                }
-
-                setTheme(localStorage.getItem('theme') || 'light');
-
-                document.addEventListener('DOMContentLoaded', function() {
-                    setThemeOnClick(
-                        'light',
-                        document.getElementById('themeLight')
-                    );
-                    setThemeOnClick(
-                        'dark',
-                        document.getElementById('themeDark')
-                    );
-                });
-            })();
-        </script>
-        <link
-            href="https://fonts.googleapis.com/css?family=Nunito|Source+Code+Pro"
-            rel="stylesheet"
-        />
-    </head>
-    <body>
-        <div id="main_div">
-            <noscript
-                >Please enable JavaScript if you would like to use
-                search.</noscript
-            >
-            <h1>GramJS API</h1>
-            <p>
-                This documentation was generated straight from the
-                <code>scheme.tl</code> provided by Telegram. However, there is
-                no official documentation per se on what the methods,
-                constructors and types mean. Nevertheless, this page aims to
-                provide easy access to all the available methods, their
-                definition and parameters.
-            </p>
-            <p id="themeSelect">
-                <a href="#" id="themeLight">light</a> /
-                <a href="#" id="themeDark">dark</a> theme.
-            </p>
-            <p>Please note that when you see this:</p>
-            <pre>
----functions---
-users.getUsers#0d91a548 id:Vector&lt;InputUser&gt; = Vector&lt;User&gt;</pre
-            >
-
-            <p>
-                This is <b>not</b> Python code. It's the "TL definition". It's
-                an easy-to-read line that gives a quick overview on the
-                parameters and its result. You don't need to worry about this.
-                See
-                <a
-                    href="https://docs.telethon.dev/en/latest/developing/understanding-the-type-language.html"
-                    >Understanding the Type Language</a
-                >
-                for more details on it.
-            </p>
-
-            <h3>Index</h3>
-            <ul>
-                <li>
-                    <a href="#methods">Methods</a>
-                    (<a href="methods/index.html">full list</a>)
-                </li>
-                <li>
-                    <a href="#types">Types</a>
-                    (<a href="types/index.html">full list</a>)
-                </li>
-                <li>
-                    <a href="#constructors">Constructors</a>
-                    (<a href="constructors/index.html">full list</a>)
-                </li>
-                <li><a href="#core">Core types</a></li>
-                <li><a href="#example">Full example</a></li>
-            </ul>
-
-            <h3 id="methods">Methods</h3>
-            <p>
-                Currently there are <b>{methodCount} methods</b> available for
-                the layer {layer}.
-                <a href="methods/index.html">See the complete method list</a>.
-                <br /><br />
-                Methods, also known as <i>requests</i>, are used to interact
-                with the Telegram API itself and are invoked through
-                <code>client(Request(...))</code>. <b>Only these</b> can be used
-                like that! You cannot invoke types or constructors, only
-                requests. After this, Telegram will return a
-                <code>result</code>, which may be, for instance, a bunch of
-                messages, some dialogs, users, etc.
-            </p>
-
-            <h3 id="types">Types</h3>
-            <p>
-                Currently there are <b>{typeCount} types</b>.
-                <a href="types/index.html">See the complete list of types</a>.
-            </p>
-
-            <p>
-                The Telegram types are the <i>abstract</i> results that you
-                receive after invoking a request. They are "abstract" because
-                they can have multiple constructors. For instance, the abstract
-                type <code>User</code> can be either <code>UserEmpty</code> or
-                <code>User</code>. You should, most of the time, make sure you
-                received the desired type by using the
-                <code>isinstance(result, Constructor)</code> Python function.
-                When a request needs a Telegram type as argument, you should
-                create an instance of it by using one of its, possibly multiple,
-                constructors.
-            </p>
-
-            <h3 id="constructors">Constructors</h3>
-            <p>
-                Currently there are <b>{constructorCount} constructors</b>.
-                <a href="constructors/index.html"
-                    >See the list of all constructors</a
-                >.
-            </p>
-
-            <p>
-                Constructors are the way you can create instances of the
-                abstract types described above, and also the instances which are
-                actually returned from the functions although they all share a
-                common abstract type.
-            </p>
-
-            <h3 id="core">Core types</h3>
-            <p>
-                Core types are types from which the rest of Telegram types build
-                upon:
-            </p>
-            <ul>
-                <li id="int">
-                    <b>int</b>: The value should be an integer type, like
-                    <span class="sh1">42</span>. It should have 32 bits or less.
-                    You can check the bit length by calling
-                    <code>a.bit_length()</code>, where <code>a</code> is an
-                    integer variable.
-                </li>
-                <li id="long">
-                    <b>long</b>: Different name for an integer type. The numbers
-                    given should have 64 bits or less.
-                </li>
-                <li id="int128">
-                    <b>int128</b>: Another integer type, should have 128 bits or
-                    less.
-                </li>
-                <li id="int256">
-                    <b>int256</b>: The largest integer type, allowing 256 bits
-                    or less.
-                </li>
-                <li id="double">
-                    <b>double</b>: The value should be a floating point value,
-                    such as <span class="sh1">123.456</span>.
-                </li>
-                <li id="vector">
-                    <b>Vector&lt;T&gt;</b>: If a type <code>T</code> is wrapped
-                    around <code>Vector&lt;T&gt;</code>, then it means that the
-                    argument should be a <i>list</i> of it. For instance, a
-                    valid value for <code>Vector&lt;int&gt;</code> would be
-                    <code>[1, 2, 3]</code>.
-                </li>
-                <li id="string">
-                    <b>string</b>: A valid UTF-8 string should be supplied. This
-                    is right how Python strings work, no further encoding is
-                    required.
-                </li>
-                <li id="bool">
-                    <b>Bool</b>: Either <code>True</code> or <code>False</code>.
-                </li>
-                <li id="true">
-                    <b>flag</b>: These arguments aren't actually sent but rather
-                    encoded as flags. Any truthy value (<code>True</code>,
-                    <code>7</code>) will enable this flag, although it's
-                    recommended to use <code>True</code> or <code>None</code> to
-                    symbolize that it's not present.
-                </li>
-                <li id="bytes">
-                    <b>bytes</b>: A sequence of bytes, like
-                    <code>b'hello'</code>, should be supplied.
-                </li>
-                <li id="date">
-                    <b>date</b>: Although this type is internally used as an
-                    <code>int</code>, you can pass a <code>datetime</code> or
-                    <code>date</code> object instead to work with date
-                    parameters.<br />
-                    Note that the library uses the date in <b>UTC+0</b>, since
-                    timezone conversion is not responsibility of the library.
-                    Furthermore, this eases converting into any other timezone
-                    without the need for a middle step.
-                </li>
-            </ul>
-
-            <h3 id="example">Full example</h3>
-            <p>
-                All methods shown here have dummy examples on how to write them,
-                so you don't get confused with their TL definition. However,
-                this may not always run. They are just there to show the right
-                syntax.
-            </p>
-
-            <p>
-                You should check out
-                <a
-                    href="https://docs.telethon.dev/en/latest/concepts/full-api.html"
-                    >how to access the full API</a
-                >
-                in ReadTheDocs.
-            </p>
-        </div>
-        <script src="js/search.js"></script>
-    </body>
-</html>

+ 0 - 185
gramjs_generator/data/html/css/docs.dark.css

@@ -1,185 +0,0 @@
-body {
-    font-family: 'Nunito', sans-serif;
-    color: #bbb;
-    background-color:#000;
-    font-size: 16px;
-}
-
-a {
-    color: #42aaed;
-    text-decoration: none;
-}
-
-pre {
-    font-family: 'Source Code Pro', monospace;
-    padding: 8px;
-    color: #567;
-    background: #080a0c;
-    border-radius: 0;
-    overflow-x: auto;
-}
-
-a:hover {
-    color: #64bbdd;
-    text-decoration: underline;
-}
-
-table {
-    width: 100%;
-    max-width: 100%;
-}
-
-table td {
-    border-top: 1px solid #111;
-    padding: 8px;
-}
-
-.horizontal {
-    margin-bottom: 16px;
-    list-style: none;
-    background: #080a0c;
-    border-radius: 4px;
-    padding: 8px 16px;
-}
-
-.horizontal li {
-    display: inline-block;
-    margin: 0 8px 0 0;
-}
-
-.horizontal img {
-    display: inline-block;
-    margin: 0 8px -2px 0;
-}
-
-h1, summary.title {
-    font-size: 24px;
-}
-
-h3 {
-    font-size: 20px;
-}
-
-#main_div {
-  padding: 20px 0;
-  max-width: 800px;
-  margin: 0 auto;
-}
-
-pre::-webkit-scrollbar {
-    visibility: visible;
-    display: block;
-    height: 12px;
-}
-
-pre::-webkit-scrollbar-track:horizontal {
-    background: #222;
-    border-radius: 0;
-    height: 12px;
-}
-
-pre::-webkit-scrollbar-thumb:horizontal {
-    background: #444;
-    border-radius: 0;
-    height: 12px;
-}
-
-:target {
-    border: 2px solid #149;
-    background: #246;
-    padding: 4px;
-}
-
-/* 'sh' stands for Syntax Highlight */
-span.sh1 {
-    color: #f93;
-}
-
-span.tooltip {
-    border-bottom: 1px dashed #ddd;
-}
-
-#searchBox {
-    width: 100%;
-    border: none;
-    height: 20px;
-    padding: 8px;
-    font-size: 16px;
-    border-radius: 2px;
-    border: 2px solid #222;
-    background: #000;
-    color: #eee;
-}
-
-#searchBox:placeholder-shown {
-    color: #bbb;
-    font-style: italic;
-}
-
-button {
-    border-radius: 2px;
-    font-size: 16px;
-    padding: 8px;
-    color: #bbb;
-    background-color: #111;
-    border: 2px solid #146;
-    transition-duration: 300ms;
-}
-
-button:hover {
-    background-color: #146;
-    color: #fff;
-}
-
-/* https://www.w3schools.com/css/css_navbar.asp */
-ul.together {
-    list-style-type: none;
-    margin: 0;
-    padding: 0;
-    overflow: hidden;
-}
-
-ul.together li {
-    float: left;
-}
-
-ul.together li a {
-    display: block;
-    border-radius: 8px;
-    background: #111;
-    padding: 4px 8px;
-    margin: 8px;
-}
-
-/* https://stackoverflow.com/a/30810322 */
-.invisible {
-    left: 0;
-    top: -99px;
-    padding: 0;
-    width: 2em;
-    height: 2em;
-    border: none;
-    outline: none;
-    position: fixed;
-    box-shadow: none;
-    color: transparent;
-    background: transparent;
-}
-
-@media (max-width: 640px) {
-    h1, summary.title {
-        font-size: 18px;
-    }
-    h3 {
-        font-size: 16px;
-    }
-
-    #dev_page_content_wrap {
-        padding-top: 12px;
-    }
-
-    #dev_page_title {
-        margin-top: 10px;
-        margin-bottom: 20px;
-    }
-}

+ 0 - 229
gramjs_generator/data/html/css/docs.h4x0r.css

@@ -1,229 +0,0 @@
-/* Begin of https://cdn.jsdelivr.net/npm/hack-font@3/build/web/hack.css
- *
- *  Hack typeface https://github.com/source-foundry/Hack
- *  License: https://github.com/source-foundry/Hack/blob/master/LICENSE.md
- */
-@font-face {
-  font-family: 'Hack';
-  src: url('fonts/hack-regular.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-regular.woff?sha=3114f1256') format('woff');
-  font-weight: 400;
-  font-style: normal;
-}
-
-@font-face {
-  font-family: 'Hack';
-  src: url('fonts/hack-bold.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bold.woff?sha=3114f1256') format('woff');
-  font-weight: 700;
-  font-style: normal;
-}
-
-@font-face {
-  font-family: 'Hack';
-  src: url('fonts/hack-italic.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-italic.woff?sha=3114f1256') format('woff');
-  font-weight: 400;
-  font-style: italic;
-}
-
-@font-face {
-  font-family: 'Hack';
-  src: url('fonts/hack-bolditalic.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bolditalic.woff?sha=3114f1256') format('woff');
-  font-weight: 700;
-  font-style: italic;
-}
-
-/* End of https://cdn.jsdelivr.net/npm/hack-font@3/build/web/hack.css */
-
-body {
-    font-family: 'Hack', monospace;
-    color: #0a0;
-    background-color: #000;
-    font-size: 16px;
-}
-
-::-moz-selection {
-    color: #000;
-    background: #0a0;
-}
-
-::selection {
-    color: #000;
-    background: #0a0;
-}
-
-a {
-    color: #0a0;
-}
-
-pre {
-    padding: 8px;
-    color: #0c0;
-    background: #010;
-    border-radius: 0;
-    overflow-x: auto;
-}
-
-a:hover {
-    color: #0f0;
-    text-decoration: underline;
-}
-
-table {
-    width: 100%;
-    max-width: 100%;
-}
-
-table td {
-    border-top: 1px solid #111;
-    padding: 8px;
-}
-
-.horizontal {
-    margin-bottom: 16px;
-    list-style: none;
-    background: #010;
-    border-radius: 4px;
-    padding: 8px 16px;
-}
-
-.horizontal li {
-    display: inline-block;
-    margin: 0 8px 0 0;
-}
-
-.horizontal img {
-    opacity: 0;
-    display: inline-block;
-    margin: 0 8px -2px 0;
-}
-
-h1, summary.title {
-    font-size: 24px;
-}
-
-h3 {
-    font-size: 20px;
-}
-
-#main_div {
-  padding: 20px 0;
-  max-width: 800px;
-  margin: 0 auto;
-}
-
-pre::-webkit-scrollbar {
-    visibility: visible;
-    display: block;
-    height: 12px;
-}
-
-pre::-webkit-scrollbar-track:horizontal {
-    background: #222;
-    border-radius: 0;
-    height: 12px;
-}
-
-pre::-webkit-scrollbar-thumb:horizontal {
-    background: #444;
-    border-radius: 0;
-    height: 12px;
-}
-
-:target {
-    border: 2px solid #0f0;
-    background: #010;
-    padding: 4px;
-}
-
-/* 'sh' stands for Syntax Highlight */
-span.sh1 {
-    color: #0f0;
-}
-
-span.tooltip {
-    border-bottom: 1px dashed #ddd;
-}
-
-#searchBox {
-    width: 100%;
-    border: none;
-    height: 20px;
-    padding: 8px;
-    font-size: 16px;
-    border-radius: 2px;
-    border: 2px solid #222;
-    background: #000;
-    color: #0e0;
-    font-family: 'Hack', monospace;
-}
-
-#searchBox:placeholder-shown {
-    color: #0b0;
-    font-style: italic;
-}
-
-button {
-    font-size: 16px;
-    padding: 8px;
-    color: #0f0;
-    background-color: #071007;
-    border: 2px solid #131;
-    transition-duration: 300ms;
-    font-family: 'Hack', monospace;
-}
-
-button:hover {
-    background-color: #131;
-}
-
-/* https://www.w3schools.com/css/css_navbar.asp */
-ul.together {
-    list-style-type: none;
-    margin: 0;
-    padding: 0;
-    overflow: hidden;
-}
-
-ul.together li {
-    float: left;
-}
-
-ul.together li a {
-    display: block;
-    border-radius: 8px;
-    background: #121;
-    padding: 4px 8px;
-    margin: 8px;
-}
-
-/* https://stackoverflow.com/a/30810322 */
-.invisible {
-    left: 0;
-    top: -99px;
-    padding: 0;
-    width: 2em;
-    height: 2em;
-    border: none;
-    outline: none;
-    position: fixed;
-    box-shadow: none;
-    color: transparent;
-    background: transparent;
-}
-
-@media (max-width: 640px) {
-    h1, summary.title {
-        font-size: 18px;
-    }
-    h3 {
-        font-size: 16px;
-    }
-
-    #dev_page_content_wrap {
-        padding-top: 12px;
-    }
-
-    #dev_page_title {
-        margin-top: 10px;
-        margin-bottom: 20px;
-    }
-}

+ 0 - 182
gramjs_generator/data/html/css/docs.light.css

@@ -1,182 +0,0 @@
-body {
-    font-family: 'Nunito', sans-serif;
-    color: #333;
-    background-color:#eee;
-    font-size: 16px;
-}
-
-a {
-    color: #329add;
-    text-decoration: none;
-}
-
-pre {
-    font-family: 'Source Code Pro', monospace;
-    padding: 8px;
-    color: #567;
-    background: #e0e4e8;
-    border-radius: 0;
-    overflow-x: auto;
-}
-
-a:hover {
-    color: #64bbdd;
-    text-decoration: underline;
-}
-
-table {
-    width: 100%;
-    max-width: 100%;
-}
-
-table td {
-    border-top: 1px solid #ddd;
-    padding: 8px;
-}
-
-.horizontal {
-    margin-bottom: 16px;
-    list-style: none;
-    background: #e0e4e8;
-    border-radius: 4px;
-    padding: 8px 16px;
-}
-
-.horizontal li {
-    display: inline-block;
-    margin: 0 8px 0 0;
-}
-
-.horizontal img {
-    display: inline-block;
-    margin: 0 8px -2px 0;
-}
-
-h1, summary.title {
-    font-size: 24px;
-}
-
-h3 {
-    font-size: 20px;
-}
-
-#main_div {
-  padding: 20px 0;
-  max-width: 800px;
-  margin: 0 auto;
-}
-
-pre::-webkit-scrollbar {
-    visibility: visible;
-    display: block;
-    height: 12px;
-}
-
-pre::-webkit-scrollbar-track:horizontal {
-    background: #def;
-    border-radius: 0;
-    height: 12px;
-}
-
-pre::-webkit-scrollbar-thumb:horizontal {
-    background: #bdd;
-    border-radius: 0;
-    height: 12px;
-}
-
-:target {
-    border: 2px solid #f8f800;
-    background: #f8f8f8;
-    padding: 4px;
-}
-
-/* 'sh' stands for Syntax Highlight */
-span.sh1 {
-    color: #f70;
-}
-
-span.tooltip {
-    border-bottom: 1px dashed #444;
-}
-
-#searchBox {
-    width: 100%;
-    border: none;
-    height: 20px;
-    padding: 8px;
-    font-size: 16px;
-    border-radius: 2px;
-    border: 2px solid #ddd;
-}
-
-#searchBox:placeholder-shown {
-    font-style: italic;
-}
-
-button {
-    border-radius: 2px;
-    font-size: 16px;
-    padding: 8px;
-    color: #000;
-    background-color: #f7f7f7;
-    border: 2px solid #329add;
-    transition-duration: 300ms;
-}
-
-button:hover {
-    background-color: #329add;
-    color: #f7f7f7;
-}
-
-/* https://www.w3schools.com/css/css_navbar.asp */
-ul.together {
-    list-style-type: none;
-    margin: 0;
-    padding: 0;
-    overflow: hidden;
-}
-
-ul.together li {
-    float: left;
-}
-
-ul.together li a {
-    display: block;
-    border-radius: 8px;
-    background: #e0e4e8;
-    padding: 4px 8px;
-    margin: 8px;
-}
-
-/* https://stackoverflow.com/a/30810322 */
-.invisible {
-    left: 0;
-    top: -99px;
-    padding: 0;
-    width: 2em;
-    height: 2em;
-    border: none;
-    outline: none;
-    position: fixed;
-    box-shadow: none;
-    color: transparent;
-    background: transparent;
-}
-
-@media (max-width: 640px) {
-    h1, summary.title {
-        font-size: 18px;
-    }
-    h3 {
-        font-size: 16px;
-    }
-
-    #dev_page_content_wrap {
-        padding-top: 12px;
-    }
-
-    #dev_page_title {
-        margin-top: 10px;
-        margin-bottom: 20px;
-    }
-}

+ 0 - 35
gramjs_generator/data/html/img/arrow.svg

@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   version="1.1"
-   id="svg2"
-   viewBox="0 0 9 15"
-   height="15"
-   width="9">
-  <defs
-     id="defs4" />
-  <metadata
-     id="metadata7">
-    <rdf:RDF>
-      <cc:Work
-         rdf:about="">
-        <dc:format>image/svg+xml</dc:format>
-        <dc:type
-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <dc:title></dc:title>
-      </cc:Work>
-    </rdf:RDF>
-  </metadata>
-  <g
-     transform="translate(0,-1037.3622)"
-     id="layer1">
-    <path
-       id="path4163"
-       d="m 0.1049,1039.6602 5.34527,5.0641 -5.43588,5.4358 1.81196,1.812 7.15946,-7.1594 -7.0291,-7.0604 z"
-       style="fill:#42aaed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
-  </g>
-</svg>

+ 0 - 244
gramjs_generator/data/html/js/search.js

@@ -1,244 +0,0 @@
-root = document.getElementById("main_div");
-root.innerHTML = `
-<!-- You can append '?q=query' to the URL to default to a search -->
-<input id="searchBox" type="text" onkeyup="updateSearch(event)"
-       placeholder="Search for requests and types…" />
-
-<div id="searchDiv">
-    <div id="exactMatch" style="display:none;">
-        <b>Exact match:</b>
-        <ul id="exactList" class="together">
-        </ul>
-    </div>
-
-    <details id="methods" open><summary class="title">Methods (<span id="methodsCount">0</span>)</summary>
-        <ul id="methodsList" class="together">
-        </ul>
-    </details>
-
-    <details id="types" open><summary class="title">Types (<span id="typesCount">0</span>)</summary>
-        <ul id="typesList" class="together">
-        </ul>
-    </details>
-
-    <details id="constructors"><summary class="title">Constructors (<span id="constructorsCount">0</span>)</summary>
-        <ul id="constructorsList" class="together">
-        </ul>
-    </details>
-</div>
-<div id="contentDiv">
-` + root.innerHTML + "</div>";
-
-// HTML modified, now load documents
-contentDiv = document.getElementById("contentDiv");
-searchDiv = document.getElementById("searchDiv");
-searchBox = document.getElementById("searchBox");
-
-// Search lists
-methodsDetails = document.getElementById("methods");
-methodsList = document.getElementById("methodsList");
-methodsCount = document.getElementById("methodsCount");
-
-typesDetails = document.getElementById("types");
-typesList = document.getElementById("typesList");
-typesCount = document.getElementById("typesCount");
-
-constructorsDetails = document.getElementById("constructors");
-constructorsList = document.getElementById("constructorsList");
-constructorsCount = document.getElementById("constructorsCount");
-
-// Exact match
-exactMatch = document.getElementById("exactMatch");
-exactList = document.getElementById("exactList");
-
-try {
-    requests = [{requestNames}];
-    types = [{typeNames}];
-    constructors = [{constructorNames}];
-
-    requestsu = [{requestUrls}];
-    typesu = [{typeUrls}];
-    constructorsu = [{constructorUrls}];
-} catch (e) {
-    requests = [];
-    types = [];
-    constructors = [];
-    requestsu = [];
-    typesu = [];
-    constructorsu = [];
-}
-
-if (typeof prependPath !== 'undefined') {
-    for (var i = 0; i != requestsu.length; ++i) {
-        requestsu[i] = prependPath + requestsu[i];
-    }
-    for (var i = 0; i != typesu.length; ++i) {
-        typesu[i] = prependPath + typesu[i];
-    }
-    for (var i = 0; i != constructorsu.length; ++i) {
-        constructorsu[i] = prependPath + constructorsu[i];
-    }
-}
-
-// Assumes haystack has no whitespace and both are lowercase.
-//
-// Returns the penalty for finding the needle in the haystack
-// or -1 if the needle wasn't found at all.
-function find(haystack, needle) {
-    if (haystack.indexOf(needle) != -1) {
-        return 0;
-    }
-    var hi = 0;
-    var ni = 0;
-    var penalty = 0;
-    var started = false;
-    while (true) {
-        while (needle[ni] < 'a' || needle[ni] > 'z') {
-            ++ni;
-            if (ni == needle.length) {
-                return penalty;
-            }
-        }
-        while (haystack[hi] != needle[ni]) {
-            ++hi;
-            if (started) {
-                ++penalty;
-            }
-            if (hi == haystack.length) {
-                return -1;
-            }
-        }
-        ++hi;
-        ++ni;
-        started = true;
-        if (ni == needle.length) {
-            return penalty;
-        }
-        if (hi == haystack.length) {
-            return -1;
-        }
-    }
-}
-
-// Given two input arrays "original" and "original urls" and a query,
-// return a pair of arrays with matching "query" elements from "original".
-//
-// TODO Perhaps return an array of pairs instead a pair of arrays (for cache).
-function getSearchArray(original, originalu, query) {
-    var destination = [];
-    var destinationu = [];
-
-    for (var i = 0; i < original.length; ++i) {
-        var penalty = find(original[i].toLowerCase(), query);
-        if (penalty > -1 && penalty < original[i].length / 3) {
-            destination.push(original[i]);
-            destinationu.push(originalu[i]);
-        }
-    }
-
-    return [destination, destinationu];
-}
-
-// Modify "countSpan" and "resultList" accordingly based on the elements
-// given as [[elements], [element urls]] (both with the same length)
-function buildList(countSpan, resultList, foundElements) {
-    var result = "";
-    for (var i = 0; i < foundElements[0].length; ++i) {
-        result += '<li>';
-        result += '<a href="' + foundElements[1][i] + '">';
-        result += foundElements[0][i];
-        result += '</a></li>';
-    }
-
-    if (countSpan) {
-        countSpan.innerHTML = "" + foundElements[0].length;
-    }
-    resultList.innerHTML = result;
-}
-
-function updateSearch(event) {
-    var query = searchBox.value.toLowerCase();
-    if (!query) {
-        contentDiv.style.display = "";
-        searchDiv.style.display = "none";
-        return;
-    }
-
-    contentDiv.style.display = "none";
-    searchDiv.style.display = "";
-
-    var foundRequests = getSearchArray(requests, requestsu, query);
-    var foundTypes = getSearchArray(types, typesu, query);
-    var foundConstructors = getSearchArray(constructors, constructorsu, query);
-
-    var original = requests.concat(constructors);
-    var originalu = requestsu.concat(constructorsu);
-    var destination = [];
-    var destinationu = [];
-
-    for (var i = 0; i < original.length; ++i) {
-        if (original[i].toLowerCase().replace("request", "") == query) {
-            destination.push(original[i]);
-            destinationu.push(originalu[i]);
-        }
-    }
-
-    if (event && event.keyCode == 13) {
-        if (destination.length != 0) {
-            window.location = destinationu[0];
-        } else if (methodsDetails.open && foundRequests[1].length) {
-            window.location = foundRequests[1][0];
-        } else if (typesDetails.open && foundTypes[1].length) {
-            window.location = foundTypes[1][0];
-        } else if (constructorsDetails.open && foundConstructors[1].length) {
-            window.location = foundConstructors[1][0];
-        }
-        return;
-    }
-
-    buildList(methodsCount, methodsList, foundRequests);
-    buildList(typesCount, typesList, foundTypes);
-    buildList(constructorsCount, constructorsList, foundConstructors);
-
-    // Now look for exact matches
-    if (destination.length == 0) {
-        exactMatch.style.display = "none";
-    } else {
-        exactMatch.style.display = "";
-        buildList(null, exactList, [destination, destinationu]);
-        return destinationu[0];
-    }
-}
-
-function getQuery(name) {
-    var query = window.location.search.substring(1);
-    var vars = query.split("&");
-    for (var i = 0; i != vars.length; ++i) {
-        var pair = vars[i].split("=");
-        if (pair[0] == name)
-            return decodeURI(pair[1]);
-    }
-}
-
-document.onkeydown = function (e) {
-    if (e.key == '/' || e.key == 's' || e.key == 'S') {
-        if (document.activeElement != searchBox) {
-            searchBox.focus();
-            return false;
-        }
-    } else if (e.key == '?') {
-        alert('Pressing any of: /sS\nWill focus the search bar\n\n' +
-              'Pressing: enter\nWill navigate to the first match')
-    }
-}
-
-var query = getQuery('q');
-if (query) {
-    searchBox.value = query;
-}
-
-var exactUrl = updateSearch();
-var redirect = getQuery('redirect');
-if (exactUrl && redirect != 'no') {
-    window.location = exactUrl;
-}

+ 0 - 300
gramjs_generator/data/methods.csv

@@ -1,300 +0,0 @@
-method,usability,errors
-account.acceptAuthorization,user,
-account.cancelPasswordEmail,user,
-account.changePhone,user,PHONE_NUMBER_INVALID
-account.checkUsername,user,USERNAME_INVALID
-account.confirmPasswordEmail,user,
-account.confirmPhone,user,CODE_HASH_INVALID PHONE_CODE_EMPTY
-account.deleteSecureValue,user,
-account.finishTakeoutSession,user,
-account.getAccountTTL,user,
-account.getAllSecureValues,user,
-account.getAuthorizationForm,user,
-account.getAuthorizations,user,
-account.getContactSignUpNotification,user,
-account.getNotifyExceptions,user,
-account.getNotifySettings,user,PEER_ID_INVALID
-account.getPassword,user,
-account.getPasswordSettings,user,PASSWORD_HASH_INVALID
-account.getPrivacy,user,PRIVACY_KEY_INVALID
-account.getSecureValue,user,
-account.getTmpPassword,user,PASSWORD_HASH_INVALID TMP_PASSWORD_DISABLED
-account.getWallPaper,user,WALLPAPER_INVALID
-account.getWallPapers,user,
-account.getWebAuthorizations,user,
-account.initTakeoutSession,user,
-account.installWallPaper,user,WALLPAPER_INVALID
-account.registerDevice,user,TOKEN_INVALID
-account.reportPeer,user,PEER_ID_INVALID
-account.resendPasswordEmail,user,
-account.resetAuthorization,user,HASH_INVALID
-account.resetNotifySettings,user,
-account.resetWallPapers,user,
-account.resetWebAuthorization,user,
-account.resetWebAuthorizations,user,
-account.saveSecureValue,user,PASSWORD_REQUIRED
-account.saveWallPaper,user,WALLPAPER_INVALID
-account.sendChangePhoneCode,user,PHONE_NUMBER_INVALID
-account.sendConfirmPhoneCode,user,HASH_INVALID
-account.sendVerifyEmailCode,user,EMAIL_INVALID
-account.sendVerifyPhoneCode,user,
-account.setAccountTTL,user,TTL_DAYS_INVALID
-account.setContactSignUpNotification,user,
-account.setPrivacy,user,PRIVACY_KEY_INVALID
-account.unregisterDevice,user,TOKEN_INVALID
-account.updateDeviceLocked,user,
-account.updateNotifySettings,user,PEER_ID_INVALID
-account.updatePasswordSettings,user,EMAIL_UNCONFIRMED_X NEW_SALT_INVALID NEW_SETTINGS_INVALID PASSWORD_HASH_INVALID
-account.updateProfile,user,ABOUT_TOO_LONG FIRSTNAME_INVALID
-account.updateStatus,user,SESSION_PASSWORD_NEEDED
-account.updateUsername,user,USERNAME_INVALID USERNAME_NOT_MODIFIED USERNAME_OCCUPIED
-account.uploadWallPaper,user,WALLPAPER_FILE_INVALID
-account.verifyEmail,user,EMAIL_INVALID
-account.verifyPhone,user,
-auth.bindTempAuthKey,both,ENCRYPTED_MESSAGE_INVALID INPUT_REQUEST_TOO_LONG TEMP_AUTH_KEY_EMPTY Timeout
-auth.cancelCode,user,PHONE_NUMBER_INVALID
-auth.checkPassword,user,PASSWORD_HASH_INVALID
-auth.dropTempAuthKeys,both,
-auth.exportAuthorization,both,DC_ID_INVALID
-auth.importAuthorization,both,AUTH_BYTES_INVALID USER_ID_INVALID
-auth.importBotAuthorization,both,ACCESS_TOKEN_EXPIRED ACCESS_TOKEN_INVALID API_ID_INVALID
-auth.logOut,both,
-auth.recoverPassword,user,CODE_EMPTY
-auth.requestPasswordRecovery,user,PASSWORD_EMPTY
-auth.resendCode,user,PHONE_NUMBER_INVALID
-auth.resetAuthorizations,user,Timeout
-auth.sendCode,user,API_ID_INVALID API_ID_PUBLISHED_FLOOD AUTH_RESTART INPUT_REQUEST_TOO_LONG PHONE_NUMBER_APP_SIGNUP_FORBIDDEN PHONE_NUMBER_BANNED PHONE_NUMBER_FLOOD PHONE_NUMBER_INVALID PHONE_PASSWORD_FLOOD PHONE_PASSWORD_PROTECTED
-auth.signIn,user,PHONE_CODE_EMPTY PHONE_CODE_EXPIRED PHONE_CODE_INVALID PHONE_NUMBER_INVALID PHONE_NUMBER_UNOCCUPIED SESSION_PASSWORD_NEEDED
-auth.signUp,user,FIRSTNAME_INVALID MEMBER_OCCUPY_PRIMARY_LOC_FAILED PHONE_CODE_EMPTY PHONE_CODE_EXPIRED PHONE_CODE_INVALID PHONE_NUMBER_FLOOD PHONE_NUMBER_INVALID PHONE_NUMBER_OCCUPIED REG_ID_GENERATE_FAILED
-bots.answerWebhookJSONQuery,bot,QUERY_ID_INVALID USER_BOT_INVALID
-bots.sendCustomRequest,bot,USER_BOT_INVALID
-channels.checkUsername,user,CHANNEL_INVALID CHAT_ID_INVALID USERNAME_INVALID
-channels.createChannel,user,CHAT_TITLE_EMPTY USER_RESTRICTED
-channels.deleteChannel,user,CHANNEL_INVALID CHANNEL_PRIVATE
-channels.deleteHistory,user,
-channels.deleteMessages,both,CHANNEL_INVALID CHANNEL_PRIVATE MESSAGE_DELETE_FORBIDDEN
-channels.deleteUserHistory,user,CHANNEL_INVALID CHAT_ADMIN_REQUIRED
-channels.editAdmin,both,ADMINS_TOO_MUCH ADMIN_RANK_EMOJI_NOT_ALLOWED ADMIN_RANK_INVALID BOT_CHANNELS_NA CHANNEL_INVALID CHAT_ADMIN_INVITE_REQUIRED CHAT_ADMIN_REQUIRED RIGHT_FORBIDDEN USER_CREATOR USER_ID_INVALID USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED
-channels.editBanned,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ADMIN_INVALID USER_ID_INVALID
-channels.editPhoto,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED PHOTO_INVALID
-channels.editTitle,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED CHAT_NOT_MODIFIED
-channels.exportMessageLink,user,CHANNEL_INVALID
-channels.getAdminLog,user,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED
-channels.getAdminedPublicChannels,user,
-channels.getChannels,both,CHANNEL_INVALID CHANNEL_PRIVATE NEED_CHAT_INVALID
-channels.getFullChannel,both,CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA Timeout
-channels.getLeftChannels,user,
-channels.getMessages,both,CHANNEL_INVALID CHANNEL_PRIVATE MESSAGE_IDS_EMPTY
-channels.getParticipant,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ID_INVALID USER_NOT_PARTICIPANT
-channels.getParticipants,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED INPUT_CONSTRUCTOR_INVALID Timeout
-channels.inviteToChannel,user,BOTS_TOO_MUCH BOT_GROUPS_BLOCKED CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_INVALID CHAT_WRITE_FORBIDDEN INPUT_USER_DEACTIVATED USERS_TOO_MUCH USER_BANNED_IN_CHANNEL USER_BLOCKED USER_BOT USER_CHANNELS_TOO_MUCH USER_ID_INVALID USER_KICKED USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED
-channels.joinChannel,user,CHANNELS_TOO_MUCH CHANNEL_INVALID CHANNEL_PRIVATE
-channels.leaveChannel,both,CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA USER_CREATOR USER_NOT_PARTICIPANT
-channels.readHistory,user,CHANNEL_INVALID CHANNEL_PRIVATE
-channels.readMessageContents,user,CHANNEL_INVALID CHANNEL_PRIVATE
-channels.reportSpam,user,CHANNEL_INVALID INPUT_USER_DEACTIVATED
-channels.setDiscussionGroup,user,BROADCAST_ID_INVALID LINK_NOT_MODIFIED MEGAGROUP_ID_INVALID MEGAGROUP_PREHISTORY_HIDDEN
-channels.setStickers,both,CHANNEL_INVALID PARTICIPANTS_TOO_FEW
-channels.togglePreHistoryHidden,user,CHAT_LINK_EXISTS
-channels.toggleSignatures,user,CHANNEL_INVALID
-channels.toggleSlowMode,user,SECONDS_INVALID
-channels.updateUsername,user,CHANNELS_ADMIN_PUBLIC_TOO_MUCH CHANNEL_INVALID CHAT_ADMIN_REQUIRED USERNAME_INVALID USERNAME_OCCUPIED
-contacts.block,user,CONTACT_ID_INVALID
-contacts.deleteByPhones,user,
-contacts.deleteContact,user,CONTACT_ID_INVALID
-contacts.deleteContacts,user,NEED_MEMBER_INVALID Timeout
-contacts.getBlocked,user,
-contacts.getContactIDs,user,
-contacts.getContacts,user,
-contacts.getSaved,user,TAKEOUT_REQUIRED
-contacts.getStatuses,user,
-contacts.getTopPeers,user,TYPES_EMPTY
-contacts.importContacts,user,
-contacts.resetSaved,user,
-contacts.resetTopPeerRating,user,PEER_ID_INVALID
-contacts.resolveUsername,both,AUTH_KEY_PERM_EMPTY SESSION_PASSWORD_NEEDED USERNAME_INVALID USERNAME_NOT_OCCUPIED
-contacts.search,user,QUERY_TOO_SHORT SEARCH_QUERY_EMPTY Timeout
-contacts.toggleTopPeers,user,
-contacts.unblock,user,CONTACT_ID_INVALID
-contest.saveDeveloperInfo,both,
-folders.deleteFolder,user,FOLDER_ID_EMPTY
-folders.editPeerFolders,user,FOLDER_ID_INVALID
-help.acceptTermsOfService,user,
-help.editUserInfo,user,USER_INVALID
-help.getAppChangelog,user,
-help.getAppConfig,user,
-help.getAppUpdate,user,
-help.getCdnConfig,both,AUTH_KEY_PERM_EMPTY Timeout
-help.getConfig,both,AUTH_KEY_DUPLICATED Timeout
-help.getDeepLinkInfo,user,
-help.getInviteText,user,
-help.getNearestDc,user,
-help.getPassportConfig,user,
-help.getProxyData,user,
-help.getRecentMeUrls,user,
-help.getSupport,user,
-help.getSupportName,user,USER_INVALID
-help.getTermsOfServiceUpdate,user,
-help.getUserInfo,user,USER_INVALID
-help.saveAppLog,user,
-help.setBotUpdatesStatus,both,
-initConnection,both,CONNECTION_LAYER_INVALID INPUT_FETCH_FAIL
-invokeAfterMsg,both,
-invokeAfterMsgs,both,
-invokeWithLayer,both,AUTH_BYTES_INVALID AUTH_KEY_DUPLICATED CDN_METHOD_INVALID CHAT_WRITE_FORBIDDEN CONNECTION_API_ID_INVALID CONNECTION_DEVICE_MODEL_EMPTY CONNECTION_LANG_PACK_INVALID CONNECTION_NOT_INITED CONNECTION_SYSTEM_EMPTY INPUT_LAYER_INVALID INVITE_HASH_EXPIRED NEED_MEMBER_INVALID Timeout
-invokeWithMessagesRange,both,
-invokeWithTakeout,both,
-invokeWithoutUpdates,both,
-langpack.getDifference,user,LANG_PACK_INVALID
-langpack.getLangPack,user,LANG_PACK_INVALID
-langpack.getLanguage,user,
-langpack.getLanguages,user,LANG_PACK_INVALID
-langpack.getStrings,user,LANG_PACK_INVALID
-messages.acceptEncryption,user,CHAT_ID_INVALID ENCRYPTION_ALREADY_ACCEPTED ENCRYPTION_ALREADY_DECLINED ENCRYPTION_OCCUPY_FAILED
-messages.addChatUser,user,CHAT_ADMIN_REQUIRED CHAT_ID_INVALID INPUT_USER_DEACTIVATED PEER_ID_INVALID USERS_TOO_MUCH USER_ALREADY_PARTICIPANT USER_ID_INVALID USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED
-messages.checkChatInvite,user,INVITE_HASH_EMPTY INVITE_HASH_EXPIRED INVITE_HASH_INVALID
-messages.clearAllDrafts,user,
-messages.clearRecentStickers,user,
-messages.createChat,user,USERS_TOO_FEW USER_RESTRICTED
-messages.deleteChatUser,both,CHAT_ID_INVALID PEER_ID_INVALID USER_NOT_PARTICIPANT
-messages.deleteHistory,user,PEER_ID_INVALID
-messages.deleteMessages,both,MESSAGE_DELETE_FORBIDDEN
-messages.discardEncryption,user,CHAT_ID_EMPTY ENCRYPTION_ALREADY_DECLINED ENCRYPTION_ID_INVALID
-messages.editChatAbout,both,
-messages.editChatAdmin,user,CHAT_ID_INVALID
-messages.editChatDefaultBannedRights,both,BANNED_RIGHTS_INVALID
-messages.editChatPhoto,both,CHAT_ID_INVALID INPUT_CONSTRUCTOR_INVALID INPUT_FETCH_FAIL PEER_ID_INVALID PHOTO_EXT_INVALID
-messages.editChatTitle,both,CHAT_ID_INVALID NEED_CHAT_INVALID
-messages.editInlineBotMessage,both,MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED
-messages.editMessage,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_WRITE_FORBIDDEN INPUT_USER_DEACTIVATED MEDIA_NEW_INVALID MEDIA_PREV_INVALID MESSAGE_AUTHOR_REQUIRED MESSAGE_EDIT_TIME_EXPIRED MESSAGE_EMPTY MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED PEER_ID_INVALID
-messages.exportChatInvite,user,CHAT_ID_INVALID
-messages.faveSticker,user,STICKER_ID_INVALID
-messages.forwardMessages,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_SEND_GIFS_FORBIDDEN CHAT_SEND_MEDIA_FORBIDDEN CHAT_SEND_STICKERS_FORBIDDEN CHAT_WRITE_FORBIDDEN GROUPED_MEDIA_INVALID INPUT_USER_DEACTIVATED MEDIA_EMPTY MESSAGE_IDS_EMPTY MESSAGE_ID_INVALID PEER_ID_INVALID PTS_CHANGE_EMPTY RANDOM_ID_DUPLICATE RANDOM_ID_INVALID SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER
-messages.getAllChats,user,
-messages.getAllDrafts,user,
-messages.getAllStickers,user,
-messages.getArchivedStickers,user,
-messages.getAttachedStickers,user,
-messages.getBotCallbackAnswer,user,CHANNEL_INVALID DATA_INVALID MESSAGE_ID_INVALID PEER_ID_INVALID Timeout
-messages.getChats,both,CHAT_ID_INVALID PEER_ID_INVALID
-messages.getCommonChats,user,USER_ID_INVALID
-messages.getDhConfig,user,RANDOM_LENGTH_INVALID
-messages.getDialogUnreadMarks,user,
-messages.getDialogs,user,INPUT_CONSTRUCTOR_INVALID OFFSET_PEER_ID_INVALID SESSION_PASSWORD_NEEDED Timeout
-messages.getDocumentByHash,both,SHA256_HASH_INVALID
-messages.getFavedStickers,user,
-messages.getFeaturedStickers,user,
-messages.getFullChat,both,CHAT_ID_INVALID PEER_ID_INVALID
-messages.getGameHighScores,bot,PEER_ID_INVALID USER_BOT_REQUIRED
-messages.getHistory,user,AUTH_KEY_DUPLICATED AUTH_KEY_PERM_EMPTY CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ID_INVALID PEER_ID_INVALID Timeout
-messages.getInlineBotResults,user,BOT_INLINE_DISABLED BOT_INVALID CHANNEL_PRIVATE Timeout
-messages.getInlineGameHighScores,bot,MESSAGE_ID_INVALID USER_BOT_REQUIRED
-messages.getMaskStickers,user,
-messages.getMessageEditData,user,MESSAGE_AUTHOR_REQUIRED PEER_ID_INVALID
-messages.getMessages,both,
-messages.getMessagesViews,user,CHANNEL_PRIVATE CHAT_ID_INVALID PEER_ID_INVALID
-messages.getOnlines,user,
-messages.getPeerDialogs,user,CHANNEL_PRIVATE PEER_ID_INVALID
-messages.getPeerSettings,user,CHANNEL_INVALID PEER_ID_INVALID
-messages.getPinnedDialogs,user,
-messages.getPollResults,user,
-messages.getRecentLocations,user,
-messages.getRecentStickers,user,
-messages.getSavedGifs,user,
-messages.getSplitRanges,user,
-messages.getStatsURL,user,
-messages.getStickerSet,both,STICKERSET_INVALID
-messages.getStickers,user,EMOTICON_EMPTY
-messages.getUnreadMentions,user,PEER_ID_INVALID
-messages.getWebPage,user,WC_CONVERT_URL_INVALID
-messages.getWebPagePreview,user,
-messages.hideReportSpam,user,PEER_ID_INVALID
-messages.importChatInvite,user,CHANNELS_TOO_MUCH INVITE_HASH_EMPTY INVITE_HASH_EXPIRED INVITE_HASH_INVALID SESSION_PASSWORD_NEEDED USERS_TOO_MUCH USER_ALREADY_PARTICIPANT
-messages.installStickerSet,user,STICKERSET_INVALID
-messages.markDialogUnread,user,
-messages.migrateChat,user,CHAT_ADMIN_REQUIRED CHAT_ID_INVALID PEER_ID_INVALID
-messages.readEncryptedHistory,user,MSG_WAIT_FAILED
-messages.readFeaturedStickers,user,
-messages.readHistory,user,PEER_ID_INVALID Timeout
-messages.readMentions,user,
-messages.readMessageContents,user,
-messages.receivedMessages,user,
-messages.receivedQueue,user,MSG_WAIT_FAILED MAX_QTS_INVALID
-messages.reorderPinnedDialogs,user,PEER_ID_INVALID
-messages.reorderStickerSets,user,
-messages.report,user,
-messages.reportEncryptedSpam,user,CHAT_ID_INVALID
-messages.reportSpam,user,PEER_ID_INVALID
-messages.requestEncryption,user,DH_G_A_INVALID USER_ID_INVALID
-messages.saveDraft,user,PEER_ID_INVALID
-messages.saveGif,user,GIF_ID_INVALID
-messages.saveRecentSticker,user,STICKER_ID_INVALID
-messages.search,user,CHAT_ADMIN_REQUIRED INPUT_CONSTRUCTOR_INVALID INPUT_USER_DEACTIVATED PEER_ID_INVALID PEER_ID_NOT_SUPPORTED SEARCH_QUERY_EMPTY USER_ID_INVALID
-messages.searchGifs,user,SEARCH_QUERY_EMPTY
-messages.searchGlobal,user,SEARCH_QUERY_EMPTY
-messages.searchStickerSets,user,
-messages.sendEncrypted,user,CHAT_ID_INVALID DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED
-messages.sendEncryptedFile,user,MSG_WAIT_FAILED
-messages.sendEncryptedService,user,DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED USER_IS_BLOCKED
-messages.sendInlineBotResult,user,CHAT_SEND_INLINE_FORBIDDEN CHAT_WRITE_FORBIDDEN INLINE_RESULT_EXPIRED PEER_ID_INVALID QUERY_ID_EMPTY SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH WEBPAGE_CURL_FAILED WEBPAGE_MEDIA_EMPTY
-messages.sendMedia,both,BOT_PAYMENTS_DISABLED BOT_POLLS_DISABLED CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID INPUT_USER_DEACTIVATED MEDIA_CAPTION_TOO_LONG MEDIA_EMPTY PAYMENT_PROVIDER_INVALID PEER_ID_INVALID PHOTO_EXT_INVALID PHOTO_INVALID_DIMENSIONS PHOTO_SAVE_FILE_INVALID POLL_OPTION_DUPLICATE RANDOM_ID_DUPLICATE SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH STORAGE_CHECK_FAILED Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT VIDEO_CONTENT_TYPE_INVALID WEBPAGE_CURL_FAILED WEBPAGE_MEDIA_EMPTY
-messages.sendMessage,both,AUTH_KEY_DUPLICATED BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_RESTRICTED CHAT_WRITE_FORBIDDEN ENTITIES_TOO_LONG ENTITY_MENTION_USER_INVALID INPUT_USER_DEACTIVATED MESSAGE_EMPTY MESSAGE_TOO_LONG PEER_ID_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER
-messages.sendMultiMedia,both,SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH
-messages.sendReaction,User,REACTION_INVALID
-messages.sendVote,user,
-messages.setBotCallbackAnswer,both,QUERY_ID_INVALID URL_INVALID
-messages.setBotPrecheckoutResults,both,ERROR_TEXT_EMPTY
-messages.setBotShippingResults,both,QUERY_ID_INVALID
-messages.setEncryptedTyping,user,CHAT_ID_INVALID
-messages.setGameScore,bot,PEER_ID_INVALID USER_BOT_REQUIRED
-messages.setInlineBotResults,bot,ARTICLE_TITLE_EMPTY BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID MESSAGE_EMPTY PHOTO_CONTENT_URL_EMPTY PHOTO_THUMB_URL_EMPTY QUERY_ID_INVALID REPLY_MARKUP_INVALID RESULT_TYPE_INVALID SEND_MESSAGE_MEDIA_INVALID SEND_MESSAGE_TYPE_INVALID START_PARAM_INVALID USER_BOT_INVALID
-messages.setInlineGameScore,bot,MESSAGE_ID_INVALID USER_BOT_REQUIRED
-messages.setTyping,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ID_INVALID CHAT_WRITE_FORBIDDEN PEER_ID_INVALID USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT
-messages.startBot,user,BOT_INVALID PEER_ID_INVALID START_PARAM_EMPTY START_PARAM_INVALID
-messages.toggleDialogPin,user,PEER_ID_INVALID
-messages.uninstallStickerSet,user,STICKERSET_INVALID
-messages.updatePinnedMessage,both,
-messages.uploadEncryptedFile,user,
-messages.uploadMedia,both,BOT_MISSING MEDIA_INVALID PEER_ID_INVALID
-payments.clearSavedInfo,user,
-payments.getPaymentForm,user,MESSAGE_ID_INVALID
-payments.getPaymentReceipt,user,MESSAGE_ID_INVALID
-payments.getSavedInfo,user,
-payments.sendPaymentForm,user,MESSAGE_ID_INVALID
-payments.validateRequestedInfo,user,MESSAGE_ID_INVALID
-phone.acceptCall,user,CALL_ALREADY_ACCEPTED CALL_ALREADY_DECLINED CALL_OCCUPY_FAILED CALL_PEER_INVALID CALL_PROTOCOL_FLAGS_INVALID
-phone.confirmCall,user,CALL_ALREADY_DECLINED CALL_PEER_INVALID
-phone.discardCall,user,CALL_ALREADY_ACCEPTED CALL_PEER_INVALID
-phone.getCallConfig,user,
-phone.receivedCall,user,CALL_ALREADY_DECLINED CALL_PEER_INVALID
-phone.requestCall,user,CALL_PROTOCOL_FLAGS_INVALID PARTICIPANT_CALL_FAILED PARTICIPANT_VERSION_OUTDATED USER_ID_INVALID USER_IS_BLOCKED USER_PRIVACY_RESTRICTED
-phone.saveCallDebug,user,CALL_PEER_INVALID DATA_JSON_INVALID
-phone.setCallRating,user,CALL_PEER_INVALID
-photos.deletePhotos,user,
-photos.getUserPhotos,both,MAX_ID_INVALID USER_ID_INVALID
-photos.updateProfilePhoto,user,
-photos.uploadProfilePhoto,user,FILE_PARTS_INVALID IMAGE_PROCESS_FAILED PHOTO_CROP_SIZE_SMALL PHOTO_EXT_INVALID
-ping,both,
-reqDHParams,both,
-reqPq,both,
-reqPqMulti,both,
-rpcDropAnswer,both,
-setClientDHParams,both,
-stickers.addStickerToSet,bot,BOT_MISSING STICKERSET_INVALID
-stickers.changeStickerPosition,bot,BOT_MISSING STICKER_INVALID
-stickers.createStickerSet,bot,BOT_MISSING PACK_SHORT_NAME_INVALID PACK_SHORT_NAME_OCCUPIED PEER_ID_INVALID SHORTNAME_OCCUPY_FAILED STICKERS_EMPTY STICKER_EMOJI_INVALID STICKER_FILE_INVALID STICKER_PNG_DIMENSIONS USER_ID_INVALID
-stickers.removeStickerFromSet,bot,BOT_MISSING STICKER_INVALID
-updates.getChannelDifference,both,CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA HISTORY_GET_FAILED PERSISTENT_TIMESTAMP_EMPTY PERSISTENT_TIMESTAMP_INVALID PERSISTENT_TIMESTAMP_OUTDATED RANGES_INVALID Timeout
-updates.getDifference,both,AUTH_KEY_PERM_EMPTY CDN_METHOD_INVALID DATE_EMPTY NEED_MEMBER_INVALID PERSISTENT_TIMESTAMP_EMPTY PERSISTENT_TIMESTAMP_INVALID SESSION_PASSWORD_NEEDED STORE_INVALID_SCALAR_TYPE Timeout
-updates.getState,both,AUTH_KEY_DUPLICATED SESSION_PASSWORD_NEEDED Timeout
-upload.getCdnFile,user,UNKNOWN_METHOD
-upload.getCdnFileHashes,both,CDN_METHOD_INVALID RSA_DECRYPT_FAILED
-upload.getFile,both,AUTH_KEY_PERM_EMPTY FILE_ID_INVALID INPUT_FETCH_FAIL LIMIT_INVALID LOCATION_INVALID OFFSET_INVALID Timeout
-upload.getFileHashes,both,
-upload.getWebFile,user,LOCATION_INVALID
-upload.reuploadCdnFile,both,RSA_DECRYPT_FAILED
-upload.saveBigFilePart,both,FILE_PARTS_INVALID FILE_PART_EMPTY FILE_PART_INVALID FILE_PART_SIZE_INVALID Timeout
-upload.saveFilePart,both,FILE_PART_EMPTY FILE_PART_INVALID INPUT_FETCH_FAIL SESSION_PASSWORD_NEEDED
-users.getFullUser,both,Timeout USER_ID_INVALID
-users.getUsers,both,AUTH_KEY_PERM_EMPTY MEMBER_NO_LOCATION NEED_MEMBER_INVALID SESSION_PASSWORD_NEEDED Timeout
-users.setSecureValueErrors,bot,

+ 0 - 390
gramjs_generator/docswriter.js

@@ -1,390 +0,0 @@
-const fs = require('fs')
-const path = require('path')
-const util = require('util')
-
-class DocsWriter {
-    /**
-     * Utility class used to write the HTML files used on the documentation.
-     *
-     * Initializes the writer to the specified output file,
-     * creating the parent directories when used if required.
-     */
-    constructor(filename, typeToPath) {
-        this.filename = filename
-        this._parent = path.join(this.filename, '..')
-        this.handle = null
-        this.title = ''
-
-        // Should be set before calling adding items to the menu
-        this.menuSeparatorTag = null
-
-        // Utility functions
-        this.typeToPath = (t) => this._rel(typeToPath(t))
-
-        // Control signals
-        this.menuBegan = false
-        this.tableColumns = 0
-        this.tableColumnsLeft = null
-        this.writeCopyScript = false
-        this._script = ''
-    }
-
-    /**
-     * Get the relative path for the given path from the current
-     * file by working around https://bugs.python.org/issue20012.
-     */
-    _rel(path_) {
-        return path
-            .relative(this._parent, path_)
-            .replace(new RegExp(`\\${path.sep}`, 'g'), '/')
-    }
-
-    /**
-     * Writes the head part for the generated document,
-     * with the given title and CSS
-     */
-    // High level writing
-    writeHead(title, cssPath, defaultCss) {
-        this.title = title
-        this.write(
-            `<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-    <title>${title}</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <link id="style" href="${this._rel(
-        cssPath
-    )}/docs.dark.css" rel="stylesheet">
-    <script>
-    document.getElementById("style").href = "${this._rel(cssPath)}/docs."
-        + (localStorage.getItem("theme") || "${defaultCss}")
-        + ".css";
-    </script>
-    <link href="https://fonts.googleapis.com/css?family=Nunito|Source+Code+Pro"
-          rel="stylesheet">
-</head>
-<body>
-<div id="main_div">`
-        )
-    }
-
-    /**
-     * Sets the menu separator.
-     * Must be called before adding entries to the menu
-     */
-    setMenuSeparator(img) {
-        if (img) {
-            this.menuSeparatorTag = `<img src="${this._rel(img)}" alt="/" />`
-        } else {
-            this.menuSeparatorTag = null
-        }
-    }
-
-    /**
-     * Adds a menu entry, will create it if it doesn't exist yet
-     */
-    addMenu(name, link) {
-        if (this.menuBegan) {
-            if (this.menuSeparatorTag) {
-                this.write(this.menuSeparatorTag)
-            }
-        } else {
-            // First time, create the menu tag
-            this.write('<ul class="horizontal">')
-            this.menuBegan = true
-        }
-
-        this.write('<li>')
-
-        if (link) {
-            this.write(`<a href="${this._rel(link)}">`)
-        }
-
-        // Write the real menu entry text
-        this.write(name)
-
-        if (link) {
-            this.write('</a>')
-        }
-
-        this.write('</li>')
-    }
-
-    /**
-     * Ends an opened menu
-     */
-    endMenu() {
-        if (!this.menuBegan) {
-            throw new Error('No menu had been started in the first place.')
-        }
-
-        this.write('</ul>')
-    }
-
-    /**
-     * Writes a title header in the document body,
-     * with an optional depth level
-     */
-    writeTitle(title, level, id) {
-        level = level || 1
-
-        if (id) {
-            this.write(`<h${level} id="${id}">${title}</h${level}>`)
-        } else {
-            this.write(`<h${level}>${title}</h${level}>`)
-        }
-    }
-
-    /**
-     * Writes the code for the given 'tlobject' properly
-     * formatted with hyperlinks
-     */
-    writeCode(tlobject) {
-        this.write(
-            `<pre>---${tlobject.isFunction ? 'functions' : 'types'}---\n`
-        )
-
-        // Write the function or type and its ID
-        if (tlobject.namespace) {
-            this.write(tlobject.namespace)
-            this.write('.')
-        }
-
-        this.write(
-            `${tlobject.name}#${tlobject.id.toString(16).padStart(8, '0')}`
-        )
-
-        // Write all the arguments (or do nothing if there's none)
-        for (const arg of tlobject.args) {
-            this.write(' ')
-            const addLink = !arg.genericDefinition && !arg.isGeneric
-
-            // "Opening" modifiers
-            if (arg.genericDefinition) {
-                this.write('{')
-            }
-
-            // Argument name
-            this.write(arg.name)
-            this.write(':')
-
-            // "Opening" modifiers
-            if (arg.isFlag) {
-                this.write(`flags.${arg.flagIndex}?`)
-            }
-
-            if (arg.isGeneric) {
-                this.write('!')
-            }
-
-            if (arg.isVector) {
-                this.write(
-                    `<a href="${this.typeToPath('vector')}">Vector</a>&lt;`
-                )
-            }
-
-            // Argument type
-            if (arg.type) {
-                if (addLink) {
-                    this.write(`<a href="${this.typeToPath(arg.type)}">`)
-                }
-
-                this.write(arg.type)
-
-                if (addLink) {
-                    this.write('</a>')
-                }
-            } else {
-                this.write('#')
-            }
-
-            // "Closing" modifiers
-            if (arg.isVector) {
-                this.write('&gt;')
-            }
-
-            if (arg.genericDefinition) {
-                this.write('}')
-            }
-        }
-
-        // Now write the resulting type (result from a function/type)
-        this.write(' = ')
-        const [genericName] = tlobject.args
-            .filter((arg) => arg.genericDefinition)
-            .map((arg) => arg.name)
-
-        if (tlobject.result === genericName) {
-            // Generic results cannot have any link
-            this.write(tlobject.result)
-        } else {
-            if (/^vector</i.test(tlobject.result)) {
-                // Notice that we don't simply make up the "Vector" part,
-                // because some requests (as of now, only FutureSalts),
-                // use a lower type name for it (see #81)
-                let [vector, inner] = tlobject.result.split('<')
-                inner = inner.replace(/>+$/, '')
-
-                this.write(
-                    `<a href="${this.typeToPath(vector)}">${vector}</a>&lt;`
-                )
-                this.write(
-                    `<a href="${this.typeToPath(inner)}">${inner}</a>&gt;`
-                )
-            } else {
-                this.write(
-                    `<a href="${this.typeToPath(tlobject.result)}">${
-                        tlobject.result
-                    }</a>`
-                )
-            }
-        }
-
-        this.write('</pre>')
-    }
-
-    /**
-     * Begins a table with the given 'column_count', required to automatically
-     * create the right amount of columns when adding items to the rows
-     */
-    beginTable(columnCount) {
-        this.tableColumns = columnCount
-        this.tableColumnsLeft = 0
-        this.write('<table>')
-    }
-
-    /**
-     * This will create a new row, or add text to the next column
-     * of the previously created, incomplete row, closing it if complete
-     */
-    addRow(text, link, bold, align) {
-        if (!this.tableColumnsLeft) {
-            // Starting a new row
-            this.write('<tr>')
-            this.tableColumnsLeft = this.tableColumns
-        }
-
-        this.write('<td')
-
-        if (align) {
-            this.write(` style="text-align: ${align}"`)
-        }
-
-        this.write('>')
-
-        if (bold) {
-            this.write('<b>')
-        }
-
-        if (link) {
-            this.write(`<a href="${this._rel(link)}">`)
-        }
-
-        // Finally write the real table data, the given text
-        this.write(text)
-
-        if (link) {
-            this.write('</a>')
-        }
-
-        if (bold) {
-            this.write('</b>')
-        }
-
-        this.write('</td>')
-
-        this.tableColumnsLeft -= 1
-        if (!this.tableColumnsLeft) {
-            this.write('</tr>')
-        }
-    }
-
-    endTable() {
-        if (this.tableColumnsLeft) {
-            this.write('</tr>')
-        }
-
-        this.write('</table>')
-    }
-
-    /**
-     * Writes a paragraph of text
-     */
-    writeText(text) {
-        this.write(`<p>${text}</p>`)
-    }
-
-    /**
-     * Writes a button with 'text' which can be used
-     * to copy 'textToCopy' to clipboard when it's clicked.
-     */
-    writeCopyButton(text, textToCopy) {
-        this.writeCopyScript = true
-        this.write(
-            `<button onclick="cp('${textToCopy.replace(
-                /'/g,
-                '\\\''
-            )}');">${text}</button>`
-        )
-    }
-
-    addScript(src, path) {
-        if (path) {
-            this._script += `<script src="${this._rel(path)}"></script>`
-        } else if (src) {
-            this._script += `<script>${src}</script>`
-        }
-    }
-
-    /**
-     * Ends the whole document. This should be called the last
-     */
-    endBody() {
-        if (this.writeCopyScript) {
-            this.write(
-                '<textarea id="c" class="invisible"></textarea>' +
-                    '<script>' +
-                    'function cp(t){' +
-                    'var c=document.getElementById("c");' +
-                    'c.value=t;' +
-                    'c.select();' +
-                    'try{document.execCommand("copy")}' +
-                    'catch(e){}}' +
-                    '</script>'
-            )
-        }
-
-        this.write(`</div>${this._script}</body></html>`)
-    }
-
-    /**
-     * Wrapper around handle.write
-     */
-    // "Low" level writing
-    write(s, ...args) {
-        if (args.length) {
-            fs.appendFileSync(this.handle, util.format(s, ...args))
-        } else {
-            fs.appendFileSync(this.handle, s)
-        }
-    }
-
-    open() {
-        // Sanity check
-        const parent = path.join(this.filename, '..')
-        fs.mkdirSync(parent, { recursive: true })
-
-        this.handle = fs.openSync(this.filename, 'w')
-
-        return this
-    }
-
-    close() {
-        fs.closeSync(this.handle)
-    }
-}
-
-module.exports = {
-    DocsWriter,
-}

+ 0 - 827
gramjs_generator/generators/docs.js

@@ -1,827 +0,0 @@
-const fs = require('fs')
-const path = require('path')
-const format = require('string-format')
-const { DocsWriter } = require('../docswriter')
-const { TLObject, Usability } = require('../parsers')
-const { snakeToCamelCase } = require('../utils')
-
-const CORE_TYPES = new Set([
-    'int',
-    'long',
-    'int128',
-    'int256',
-    'double',
-    'vector',
-    'string',
-    'bool',
-    'true',
-    'bytes',
-    'date',
-])
-
-const mkdir = (path) => fs.mkdirSync(path, { recursive: true })
-
-const titleCase = (text) =>
-    text
-        .toLowerCase()
-        .split(/(\W)/)
-        .map((word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`)
-        .join('')
-
-/**
- * ``ClassName -> class_name.html``.
- */
-const getFileName = (tlobject) => {
-    const name = tlobject instanceof TLObject ? tlobject.name : tlobject
-    // Courtesy of http://stackoverflow.com/a/1176023/4759433
-    const s1 = name.replace(/(.)([A-Z][a-z]+)/, '$1_$2')
-    const result = s1.replace(/([a-z0-9])([A-Z])/, '$1_$2').toLowerCase()
-    return `${result}.html`
-}
-
-/**
- * ``TLObject -> const { ... } = require(...);``.
- */
-const getImportCode = (tlobject) => {
-    const kind = tlobject.isFunction ? 'functions' : 'types'
-    const ns = tlobject.namespace ? `/${tlobject.namespace}` : ''
-    return `const { ${tlobject.className} } = require('gramjs/tl/${kind}${ns}');`
-}
-
-/**
- * Returns the path for the given TLObject.
- */
-const getPathFor = (tlobject) => {
-    let outDir = tlobject.isFunction ? 'methods' : 'constructors'
-
-    if (tlobject.namespace) {
-        outDir += `/${tlobject.namespace}`
-    }
-
-    return `${outDir}/${getFileName(tlobject)}`
-}
-
-/**
- * Similar to `getPathFor` but for only type names.
- */
-const getPathForType = (type) => {
-    if (CORE_TYPES.has(type.toLowerCase())) {
-        return `index.html#${type.toLowerCase()}`
-    } else if (type.includes('.')) {
-        const [namespace, name] = type.split('.')
-        return `types/${namespace}/${getFileName(name)}`
-    } else {
-        return `types/${getFileName(type)}`
-    }
-}
-
-/**
- * Finds the <title> for the given HTML file, or (Unknown).
- */
-const findTitle = (htmlFile) => {
-    const f = fs.readFileSync(htmlFile, { encoding: 'utf-8' })
-
-    for (const line of f.split('\n')) {
-        if (line.includes('<title>')) {
-            // +7 to skip '<title>'.length
-            return line.slice(
-                line.indexOf('<title>') + 7,
-                line.indexOf('</title>')
-            )
-        }
-    }
-
-    return '(Unknown)'
-}
-
-/**
- * Builds the menu used for the current ``DocumentWriter``.
- */
-const buildMenu = (docs) => {
-    const paths = []
-    let current = docs.filename
-
-    while (current !== '.') {
-        current = path.join(current, '..')
-        paths.push(current)
-    }
-
-    for (const path_ of paths.reverse()) {
-        const name = path.parse(path_).name
-
-        docs.addMenu(
-            name === '.' ? 'API' : titleCase(name),
-            `${path_}/index.html`
-        )
-    }
-
-    if (path.parse(docs.filename).name !== 'index') {
-        docs.addMenu(docs.title, docs.filename)
-    }
-
-    docs.endMenu()
-}
-
-/**
- * Generates the index file for the specified folder
- */
-const generateIndex = (folder, paths, botsIndex, botsIndexPaths) => {
-    botsIndexPaths = botsIndexPaths || []
-
-    // Determine the namespaces listed here (as sub folders)
-    // and the files (.html files) that we should link to
-    const namespaces = []
-    const files = []
-    const INDEX = 'index.html'
-    const BOT_INDEX = 'botindex.html'
-
-    for (const item of botsIndexPaths.length ? botsIndexPaths : fs.readdirSync(folder)) {
-        const fullPath = botsIndexPaths.length ? item : `${folder}/${item}`
-
-        if (fs.statSync(fullPath).isDirectory()) {
-            namespaces.push(fullPath)
-        } else if (![INDEX, BOT_INDEX].includes(item)) {
-            files.push(fullPath)
-        }
-    }
-
-    // Now that everything is setup, write the index.html file
-    const filename = `${folder}/${botsIndex ? BOT_INDEX : INDEX}`
-    const docs = new DocsWriter(filename, getPathForType).open()
-
-    // Title should be the current folder name
-    docs.writeHead(
-        titleCase(folder.replace(new RegExp(`\\${path.sep}`, 'g'), '/')),
-        paths.css,
-        paths.defaultCss
-    )
-
-    docs.setMenuSeparator(paths.arrow)
-    buildMenu(docs)
-    docs.writeTitle(
-        titleCase(
-            path
-                .join(filename, '..')
-                .replace(new RegExp(`\\${path.sep}`, 'g'), '/')
-        )
-    )
-
-    if (botsIndex) {
-        docs.writeText(
-            `These are the methods that you may be able to use as a bot. Click <a href="${INDEX}">here</a> to view them all.`
-        )
-    } else {
-        docs.writeText(
-            `Click <a href="${BOT_INDEX}">here</a> to view the methods that you can use as a bot.`
-        )
-    }
-
-    if (namespaces.length) {
-        docs.writeTitle('Namespaces', 3)
-        docs.beginTable(4)
-        namespaces.sort()
-
-        for (const namespace of namespaces) {
-            // For every namespace, also write the index of it
-            const namespacePaths = []
-
-            if (botsIndex) {
-                for (const item of botsIndexPaths) {
-                    if (path.relative(item, '..') === namespace) {
-                        namespacePaths.push(item)
-                    }
-                }
-            }
-
-            generateIndex(namespace, paths, botsIndex, namespacePaths)
-
-            docs.addRow(
-                titleCase(path.parse(namespace).name),
-                `${namespace}/${botsIndex ? BOT_INDEX : INDEX}`
-            )
-        }
-
-        docs.endTable()
-    }
-
-    docs.writeTitle('Available items')
-    docs.beginTable(2)
-
-    files
-        .sort((x, y) => x.localeCompare(y))
-        .forEach((file) => {
-            docs.addRow(findTitle(file), file)
-        })
-
-    docs.endTable()
-    docs.endBody()
-    docs.close()
-}
-
-/**
- * Generates a proper description for the given argument.
- */
-const getDescription = (arg) => {
-    const desc = []
-    let otherwise = false
-
-    if (arg.canBeInferred) {
-        desc.push('If left unspecified, it will be inferred automatically.')
-        otherwise = true
-    } else if (arg.isFlag) {
-        desc.push(
-            'This argument defaults to <code>null</code> and can be omitted.'
-        )
-        otherwise = true
-    }
-
-    if (
-        [
-            'InputPeer',
-            'InputUser',
-            'InputChannel',
-            'InputNotifyPeer',
-            'InputDialogPeer',
-        ].includes(arg.type)
-    ) {
-        desc.push(
-            'Anything entity-like will work if the library can find its <code>Input</code> version (e.g., usernames, <code>Peer</code>, <code>User</code> or <code>Channel</code> objects, etc.).'
-        )
-    }
-
-    if (arg.isVector) {
-        if (arg.isGeneric) {
-            desc.push('A list of other Requests must be supplied.')
-        } else {
-            desc.push('A list must be supplied.')
-        }
-    } else if (arg.isGeneric) {
-        desc.push('A different Request must be supplied for this argument.')
-    } else {
-        otherwise = false // Always reset to false if no other text is added
-    }
-
-    if (otherwise) {
-        desc.splice(1, 0, 'Otherwise,')
-        desc[desc.length - 1] =
-            desc[desc.length - 1].slice(0, -1).toLowerCase() +
-            desc[desc.length - 1].slice(1)
-    }
-
-    return desc
-        .join(' ')
-        .replace(
-            /list/g,
-            '<span class="tooltip" title="Any iterable that supports .length will work too">list</span>'
-        )
-}
-
-/**
- * Copies the src file into dst applying the replacements dict
- */
-const copyReplace = (src, dst, replacements) => {
-    const infile = fs.readFileSync(src, { encoding: 'utf-8' })
-
-    fs.writeFileSync(
-        dst,
-        format(infile, replacements)
-        // infile.replace(
-        //     new RegExp(
-        //         Object.keys(replacements)
-        //             .map(k => escapeRegex(k))
-        //             .join('|')
-        //     ),
-        //     m => replacements[m].toString()
-        // )
-    )
-}
-
-/**
- * Generates the documentation HTML files from from ``scheme.tl``
- * to ``/methods`` and ``/constructors``, etc.
- */
-const writeHtmlPages = (tlobjects, methods, layer, inputRes) => {
-    // Save 'Type: [Constructors]' for use in both:
-    // * Seeing the return type or constructors belonging to the same type.
-    // * Generating the types documentation, showing available constructors.
-    const paths = {
-        '404': '404.html',
-        'css': 'css',
-        'arrow': 'img/arrow.svg',
-        'search.js': 'js/search.js',
-        'indexAll': 'index.html',
-        'botIndex': 'botindex.html',
-        'indexTypes': 'types/index.html',
-        'indexMethods': 'methods/index.html',
-        'indexConstructors': 'constructors/index.html',
-        'defaultCss': 'light',
-    }
-
-    const typeToConstructors = {}
-    const typeToFunctions = {}
-
-    for (const tlobject of tlobjects) {
-        const d = tlobject.isFunction ? typeToFunctions : typeToConstructors
-
-        if (!d[tlobject.innermostResult]) {
-            d[tlobject.innermostResult] = []
-        }
-
-        d[tlobject.innermostResult].push(tlobject)
-    }
-
-    for (const [t, cs] of Object.entries(typeToConstructors)) {
-        typeToConstructors[t] = cs.sort((x, y) => x.name.localeCompare(y.name))
-    }
-
-    methods = methods.reduce((x, m) => ({ ...x, [m.name]: m }), {})
-    const botDocsPath = []
-
-    for (const tlobject of tlobjects) {
-        const filename = getPathFor(tlobject)
-        const docs = new DocsWriter(filename, getPathForType).open()
-        docs.writeHead(tlobject.className, paths.css, paths.defaultCss)
-
-        // Create the menu (path to the current TLObject)
-        docs.setMenuSeparator(paths.arrow)
-        buildMenu(docs)
-
-        // Create the page title
-        docs.writeTitle(tlobject.className)
-
-        if (tlobject.isFunction) {
-            let start
-
-            if (tlobject.usability === Usability.USER) {
-                start = '<strong>Only users</strong> can'
-            } else if (tlobject.usability === Usability.BOT) {
-                botDocsPath.push(filename)
-                start = '<strong>Only bots</strong> can'
-            } else if (tlobject.usability === Usability.BOTH) {
-                botDocsPath.push(filename)
-                start = '<strong>Both users and bots</strong> can'
-            } else {
-                botDocsPath.push(filename)
-                start = 'Both users and bots <strong>may</strong> be able to'
-            }
-
-            docs.writeText(
-                `${start} use this method. <a href="#examples">See code examples.</a>`
-            )
-        }
-
-        // Write the code definition for this TLObject
-        docs.writeCode(tlobject)
-        docs.writeCopyButton(
-            'Copy import to clipboard',
-            getImportCode(tlobject)
-        )
-
-        // Write the return type (or constructors belonging to the same type)
-        docs.writeTitle(tlobject.isFunction ? 'Returns' : 'Belongs to', 3)
-
-        let [genericArg] = tlobject.args
-            .filter((arg) => arg.genericDefinition)
-            .map((arg) => arg.name)
-
-        if (tlobject.result === genericArg) {
-            //  We assume it's a function returning a generic type
-            [genericArg] = tlobject.args
-                .filter((arg) => arg.isGeneric)
-                .map((arg) => arg.name)
-
-            docs.writeText(
-                `This function returns the result of whatever the result from invoking the request passed through <i>${genericArg}</i> is.`
-            )
-        } else {
-            let inner = tlobject.result
-
-            if (/^vector</i.test(tlobject.result)) {
-                docs.writeText('A list of the following type is returned.')
-                inner = tlobject.innermostResult
-            }
-
-            docs.beginTable(1)
-            docs.addRow(inner, getPathForType(inner))
-            docs.endTable()
-
-            const cs = typeToConstructors[inner] || []
-            if (!cs.length) {
-                docs.writeText('This type has no instances available.')
-            } else if (cs.length === 1) {
-                docs.writeText('This type can only be an instance of:')
-            } else {
-                docs.writeText('This type can be an instance of either:')
-            }
-
-            docs.beginTable(2)
-
-            for (const constructor of cs) {
-                const link = getPathFor(constructor)
-                docs.addRow(constructor.className, link)
-            }
-
-            docs.endTable()
-        }
-
-        // Return (or similar types) written. Now parameters/members
-        docs.writeTitle(tlobject.isFunction ? 'Parameters' : 'Members', 3)
-
-        // Sort the arguments in the same way they're sorted
-        // on the generated code (flags go last)
-        const args = tlobject
-            .sortedArgs()
-            .filter((a) => !a.flagIndicator && !a.genericDefinition)
-
-        if (args.length) {
-            // Writing parameters
-            docs.beginTable(3)
-
-            for (const arg of args) {
-                // Name row
-                docs.addRow(arg.name, null, true)
-
-                // Type row
-                const friendlyType = arg.type === 'true' ? 'flag' : arg.type
-                if (arg.isGeneric) {
-                    docs.addRow(`!${friendlyType}`, null, null, 'center')
-                } else {
-                    docs.addRow(
-                        friendlyType,
-                        getPathForType(arg.type),
-                        null,
-                        'center'
-                    )
-                }
-
-                // Add a description for this argument
-                docs.addRow(getDescription(arg))
-            }
-
-            docs.endTable()
-        } else {
-            if (tlobject.isFunction) {
-                docs.writeText('This request takes no input parameters.')
-            } else {
-                docs.writeText('This type has no members.')
-            }
-        }
-
-        if (tlobject.isFunction) {
-            docs.writeTitle('Known RPC errors')
-            const methodInfo = methods[tlobject.fullname]
-            const errors = methodInfo && methodInfo.errors
-
-            if (!errors || !errors.length) {
-                docs.writeText(
-                    'This request can\'t cause any RPC error as far as we know.'
-                )
-            } else {
-                docs.writeText(
-                    `This request can cause ${errors.length} known error${
-                        errors.length === 1 ? '' : 's'
-                    }:`
-                )
-
-                docs.beginTable(2)
-
-                for (const error of errors) {
-                    docs.addRow(`<code>${error.name}</code>`)
-                    docs.addRow(`${error.description}.`)
-                }
-
-                docs.endTable()
-                docs.writeText(
-                    'You can import these from <code>gramjs/errors</code>.'
-                )
-            }
-
-            docs.writeTitle('Example', null, 'examples')
-            if (tlobject.friendly) {
-                const [ns, friendly] = tlobject.friendly
-                docs.writeText(
-                    `Please refer to the documentation of <a href="https://docs.telethon.dev/en/latest/modules/client.html#telethon.client.${ns}.${friendly}"><code>client.${friendly}()</code></a> to learn about the parameters and see several code examples on how to use it.`
-                )
-                docs.writeText(
-                    'The method above is the recommended way to do it. If you need more control over the parameters or want to learn how it is implemented, open the details by clicking on the "Details" text.'
-                )
-                docs.write('<details>')
-            }
-
-            docs.write(`<pre><strong>const</strong> { TelegramClient } <strong>=</strong> require('gramjs');
-<strong>const</strong> { functions, types } <strong>=</strong> require('gramjs/tl');
-
-(<strong>async</strong> () => {
-    <strong>const</strong> client <strong>=</strong> <strong>new</strong> TelegramClient(name, apiId, apiHash);
-    await client.start();
-
-    <strong>const</strong> result <strong>= await</strong> client.invoke(`)
-            tlobject.asExample(docs, 1)
-            docs.write(');\n')
-
-            if (tlobject.result.startsWith('Vector')) {
-                docs.write(
-                    `<strong>for</strong> x <strong>in</strong> result:
-        print(x`
-                )
-            } else {
-                docs.write('    console.log(result')
-                if (
-                    tlobject.result !== 'Bool' &&
-                    !tlobject.result.startsWith('Vector')
-                ) {
-                    docs.write('.stringify()')
-                }
-            }
-
-            docs.write(');\n})();</pre>')
-            if (tlobject.friendly) {
-                docs.write('</details>')
-            }
-
-            const depth = '../'.repeat(tlobject.namespace ? 2 : 1)
-            docs.addScript(`prependPath = "${depth}";`)
-            docs.addScript(null, paths['search.js'])
-            docs.endBody()
-        }
-
-        docs.close()
-    }
-
-    // Find all the available types (which are not the same as the constructors)
-    // Each type has a list of constructors associated to it, hence is a map
-    for (const [t, cs] of Object.entries(typeToConstructors)) {
-        const filename = getPathForType(t)
-        const outDir = path.join(filename, '..')
-
-        if (outDir) {
-            mkdir(outDir)
-        }
-
-        // Since we don't have access to the full TLObject, split the type
-        let name = t
-
-        if (t.includes('.')) {
-            [, name] = t.split('.')
-        }
-
-        const docs = new DocsWriter(filename, getPathForType).open()
-
-        docs.writeHead(snakeToCamelCase(name), paths.css, paths.defaultCss)
-        docs.setMenuSeparator(paths.arrow)
-        buildMenu(docs)
-
-        // Main file title
-        docs.writeTitle(snakeToCamelCase(name))
-
-        // List available constructors for this type
-        docs.writeTitle('Available constructors', 3)
-        if (!cs.length) {
-            docs.writeText('This type has no constructors available.')
-        } else if (cs.length === 1) {
-            docs.writeText('This type has one constructor available.')
-        } else {
-            docs.writeText(
-                `This type has ${cs.length} constructors available.`
-            )
-        }
-
-        docs.beginTable(2)
-
-        for (const constructor of cs) {
-            // Constructor full name
-            const link = getPathFor(constructor)
-            docs.addRow(constructor.className, link)
-        }
-
-        docs.endTable()
-
-        // List all the methods which return this type
-        docs.writeTitle('Methods returning this type', 3)
-        const functions = typeToFunctions[t] || []
-
-        if (!functions.length) {
-            docs.writeText('No method returns this type.')
-        } else if (functions.length === 1) {
-            docs.writeText('Only the following method returns this type.')
-        } else {
-            docs.writeText(
-                `The following ${functions.length} methods return this type as a result.`
-            )
-        }
-
-        docs.beginTable(2)
-
-        for (const func of functions) {
-            const link = getPathFor(func)
-            docs.addRow(func.className, link)
-        }
-
-        docs.endTable()
-
-        // List all the methods which take this type as input
-        docs.writeTitle('Methods accepting this type as input', 3)
-        const otherMethods = tlobjects
-            .filter((u) => u.isFunction && u.args.some((a) => a.type === t))
-            .sort((x, y) => x.name.localeCompare(y.name))
-
-        if (!otherMethods.length) {
-            docs.writeText(
-                'No methods accept this type as an input parameter.'
-            )
-        } else if (otherMethods.length === 1) {
-            docs.writeText('Only this method has a parameter with this type.')
-        } else {
-            docs.writeText(
-                `The following ${otherMethods.length} methods accept this type as an input parameter.`
-            )
-        }
-
-        docs.beginTable(2)
-
-        for (const ot of otherMethods) {
-            const link = getPathFor(ot)
-            docs.addRow(ot.className, link)
-        }
-
-        docs.endTable()
-
-        // List every other type which has this type as a member
-        docs.writeTitle('Other types containing this type', 3)
-        const otherTypes = tlobjects
-            .filter((u) => !u.isFunction && u.args.some((a) => a.type === t))
-            .sort((x, y) => x.name.localeCompare(y.name))
-
-        if (!otherTypes.length) {
-            docs.writeText('No other types have a member of this type.')
-        } else if (otherTypes.length === 1) {
-            docs.writeText(
-                'You can find this type as a member of this other type.'
-            )
-        } else {
-            docs.writeText(
-                `You can find this type as a member of any of the following ${otherTypes.length} types.`
-            )
-        }
-
-        docs.beginTable(2)
-
-        for (const ot of otherTypes) {
-            const link = getPathFor(ot)
-            docs.addRow(ot.className, link)
-        }
-
-        docs.endTable()
-        docs.endBody()
-        docs.close()
-    }
-
-    // After everything's been written, generate an index.html per folder.
-    // This will be done automatically and not taking into account any extra
-    // information that we have available, simply a file listing all the others
-    // accessible by clicking on their title
-    for (const folder of ['types', 'methods', 'constructors']) {
-        generateIndex(folder, paths)
-    }
-
-    generateIndex('methods', paths, true, botDocsPath)
-
-    // Write the final core index, the main index for the rest of files
-    const types = new Set()
-    const methods_ = []
-    const cs = []
-
-    for (const tlobject of tlobjects) {
-        if (tlobject.isFunction) {
-            methods_.push(tlobject)
-        } else {
-            cs.push(tlobject)
-        }
-
-        if (!CORE_TYPES.has(tlobject.result.toLowerCase())) {
-            if (/^vector</i.test(tlobject.result)) {
-                types.add(tlobject.innermostResult)
-            } else {
-                types.add(tlobject.result)
-            }
-        }
-    }
-
-    fs.copyFileSync(`${inputRes}/404.html`, paths['404'])
-    copyReplace(`${inputRes}/core.html`, paths.indexAll, {
-        typeCount: [...types].length,
-        methodCount: methods_.length,
-        constructorCount: tlobjects.length - methods_.length,
-        layer,
-    })
-
-    let fmt = (xs) => {
-        const zs = [] // create an object to hold those which have duplicated keys
-
-        for (const x of xs) {
-            zs[x.className] = x.className in zs
-        }
-
-        return xs
-            .map((x) =>
-                zs[x.className] && x.namespace ?
-                    `"${x.namespace}.${x.className}"` :
-                    `"${x.className}"`
-            )
-            .join(', ')
-    }
-
-    const requestNames = fmt(methods_)
-    const constructorNames = fmt(cs)
-
-    fmt = (xs, formatter) => {
-        return xs
-            .map(
-                (x) =>
-                    `"${formatter(x).replace(
-                        new RegExp(`\\${path.sep}`, 'g'),
-                        '/'
-                    )}"`
-            )
-            .join(', ')
-    }
-
-    const typeNames = fmt([...types], (x) => x)
-
-    const requestUrls = fmt(methods_, getPathFor)
-    const typeUrls = fmt([...types], getPathForType)
-    const constructorUrls = fmt(cs, getPathFor)
-
-    mkdir(path.join(paths['search.js'], '..'))
-    copyReplace(`${inputRes}/js/search.js`, paths['search.js'], {
-        requestNames,
-        typeNames,
-        constructorNames,
-        requestUrls,
-        typeUrls,
-        constructorUrls,
-    })
-}
-
-const copyResources = (resDir) => {
-    for (const [dirname, files] of [
-        ['css', ['docs.light.css', 'docs.dark.css']],
-        ['img', ['arrow.svg']],
-    ]) {
-        mkdir(dirname)
-
-        for (const file of files) {
-            fs.copyFileSync(
-                `${resDir}/${dirname}/${file}`,
-                `${dirname}/${file}`
-            )
-        }
-    }
-}
-
-/**
- * Pre-create the required directory structure.
- */
-const createStructure = (tlobjects) => {
-    const typeNs = new Set()
-    const methodsNs = new Set()
-
-    for (const obj of tlobjects) {
-        if (obj.namespace) {
-            if (obj.isFunction) {
-                methodsNs.add(obj.namespace)
-            } else {
-                typeNs.add(obj.namespace)
-            }
-        }
-    }
-
-    const outputDir = '.'
-    const typeDir = `${outputDir}/types`
-    mkdir(typeDir)
-
-    const consDir = `${outputDir}/constructors`
-    mkdir(consDir)
-
-    for (const ns of typeNs) {
-        mkdir(`${typeDir}/${ns}`)
-        mkdir(`${consDir}/${ns}`)
-    }
-
-    const methDir = `${outputDir}/methods`
-    mkdir(methDir)
-
-    for (const ns of typeNs) {
-        mkdir(`${methDir}/${ns}`)
-    }
-}
-
-const generateDocs = (tlobjects, methodsInfo, layer, inputRes) => {
-    createStructure(tlobjects)
-    writeHtmlPages(tlobjects, methodsInfo, layer, inputRes)
-    copyResources(inputRes)
-}
-
-module.exports = {
-    generateDocs,
-}

+ 0 - 95
gramjs_generator/generators/errors.js

@@ -1,95 +0,0 @@
-const generateErrors = (errors, f) => {
-    // Exact/regex match to create {CODE: ErrorClassName}
-    const exactMatch = []
-    const regexMatch = []
-
-    // Find out what subclasses to import and which to create
-    const importBase = new Set()
-    const createBase = {}
-
-    for (const error of errors) {
-        if (error.subclassExists) {
-            importBase.add(error.subclass)
-        } else {
-            createBase[error.subclass] = error.intCode
-        }
-
-        if (error.hasCaptures) {
-            regexMatch.push(error)
-        } else {
-            exactMatch.push(error)
-        }
-    }
-
-    // Imports and new subclass creation
-    f.write(`const { RPCError, ${[...importBase.values()].join(', ')} } = require('./RPCBaseErrors');`)
-
-    f.write('\nconst format = require(\'string-format\');')
-
-    for (const [cls, intCode] of Object.entries(createBase)) {
-        f.write(`\n\nclass ${cls} extends RPCError {\n    constructor() {\n        this.code = ${intCode};\n    }\n}`)
-    }
-
-    // Error classes generation
-    for (const error of errors) {
-        f.write(`\n\nclass ${error.name} extends ${error.subclass} {\n    constructor(args) {\n        `)
-
-        if (error.hasCaptures) {
-            f.write(`const ${error.captureName} = Number(args.capture || 0);\n        `)
-        }
-
-        const capture = error.description.replace(/'/g, '\\\'')
-
-        if (error.hasCaptures) {
-            f.write(`super(format('${capture}', {${error.captureName}})`)
-            f.write(' + RPCError._fmtRequest(args.request));\n')
-
-            f.write(`this.message = format('${capture}', {${error.captureName}})`)
-            f.write(' + RPCError._fmtRequest(args.request);\n')
-
-        } else {
-            f.write(`super('${capture}'`)
-            f.write(' + RPCError._fmtRequest(args.request));\n')
-
-            f.write(`this.message = '${capture}'`)
-            f.write(' + RPCError._fmtRequest(args.request);\n')
-
-        }
-
-
-        if (error.hasCaptures) {
-            f.write(`        this.${error.captureName} = ${error.captureName};\n`)
-        }
-
-        f.write('    }\n}\n')
-    }
-
-    f.write('\n\nconst rpcErrorsObject = {\n')
-
-    for (const error of exactMatch) {
-        f.write(`    ${error.pattern}: ${error.name},\n`)
-    }
-
-    f.write('};\n\nconst rpcErrorRe = [\n')
-
-    for (const error of regexMatch) {
-        f.write(`    [/${error.pattern}/, ${error.name}],\n`)
-    }
-
-    f.write('];')
-    f.write('module.exports = {')
-    for (const error of regexMatch) {
-        f.write(`     ${error.name},\n`)
-    }
-    for (const error of exactMatch) {
-        f.write(`     ${error.name},\n`)
-    }
-    f.write('     rpcErrorsObject,\n')
-    f.write('     rpcErrorRe,\n')
-
-    f.write('}')
-}
-
-module.exports = {
-    generateErrors,
-}

+ 0 - 10
gramjs_generator/generators/index.js

@@ -1,10 +0,0 @@
-const { generateErrors } = require('./errors')
-const { generateTLObjects, cleanTLObjects } = require('./tlobject')
-const { generateDocs } = require('./docs')
-
-module.exports = {
-    generateErrors,
-    generateTLObjects,
-    cleanTLObjects,
-    generateDocs,
-}

+ 0 - 916
gramjs_generator/generators/tlobject.js

@@ -1,916 +0,0 @@
-const fs = require('fs')
-const util = require('util')
-const { crc32 } = require('crc')
-const SourceBuilder = require('../sourcebuilder')
-const { snakeToCamelCase, variableSnakeToCamelCase } = require('../utils')
-
-const AUTO_GEN_NOTICE = '/*! File generated by TLObjects\' generator. All changes will be ERASED !*/'
-
-const AUTO_CASTS = {
-    InputPeer: 'utils.getInputPeer(await client.getInputEntity(%s))',
-    InputChannel: 'utils.getInputChannel(await client.getInputEntity(%s))',
-    InputUser: 'utils.getInputUser(await client.getInputEntity(%s))',
-    InputDialogPeer: 'await client._getInputDialog(%s)',
-    InputNotifyPeer: 'await client._getInputNotify(%s)',
-    InputMedia: 'utils.getInputMedia(%s)',
-    InputPhoto: 'utils.getInputPhoto(%s)',
-    InputMessage: 'utils.getInputMessage(%s)',
-    InputDocument: 'utils.getInputDocument(%s)',
-    InputChatPhoto: 'utils.getInputChatPhoto(%s)',
-}
-
-const NAMED_AUTO_CASTS = {
-    'chatId,int': 'await client.getPeerId(%s, add_mark=False)',
-}
-
-// Secret chats have a chat_id which may be negative.
-// With the named auto-cast above, we would break it.
-// However there are plenty of other legit requests
-// with `chat_id:int` where it is useful.
-//
-// NOTE: This works because the auto-cast is not recursive.
-//       There are plenty of types that would break if we
-//       did recurse into them to resolve them.
-const NAMED_BLACKLIST = new Set(['messages.discardEncryption'])
-
-// const BASE_TYPES = ['string', 'bytes', 'int', 'long', 'int128', 'int256', 'double', 'Bool', 'true'];
-
-// Patched types {fullname: custom.ns.Name}
-
-// No patches currently
-/**
- const PATCHED_TYPES = {
-    messageEmpty: 'message.Message',
-    message: 'message.Message',
-    messageService: 'message.Message',
-};*/
-const PATCHED_TYPES = {}
-
-const writeModules = (outDir, depth, kind, namespaceTlobjects, typeConstructors) => {
-    // namespace_tlobjects: {'namespace', [TLObject]}
-    fs.mkdirSync(outDir, { recursive: true })
-
-    for (const [ns, tlobjects] of Object.entries(namespaceTlobjects)) {
-        const file = `${outDir}/${ns === 'null' ? 'index' : ns}.js`
-        const stream = fs.createWriteStream(file)
-        const builder = new SourceBuilder(stream)
-        const dotDepth = '.'.repeat(depth || 1)
-
-        builder.writeln(AUTO_GEN_NOTICE)
-        builder.writeln(`const { TLObject } = require('${dotDepth}/tlobject');`)
-
-        if (kind !== 'TLObject') {
-            builder.writeln(`const { ${kind} } = require('${dotDepth}/tlobject');`)
-        }
-
-        // Add the relative imports to the namespaces,
-        // unless we already are in a namespace.
-        if (!ns) {
-            const imports = Object.keys(namespaceTlobjects)
-                .filter(Boolean)
-                .join(`, `)
-
-            builder.writeln(`const { ${imports} } = require('.');`)
-        }
-
-        // Import struct for the .__bytes__(self) serialization
-        builder.writeln('const struct = require(\'python-struct\');')
-        builder.writeln(`const { readBigIntFromBuffer, 
-        readBufferFromBigInt, generateRandomBytes } = require('../../Helpers')`)
-
-        const typeNames = new Set()
-        const typeDefs = []
-        /*
-        // Find all the types in this file and generate type definitions
-        // based on the types. The type definitions are written to the
-        // file at the end.
-        for (const t of tlobjects) {
-            if (!t.isFunction) {
-                let typeName = t.result;
-
-                if (typeName.includes('.')) {
-                    typeName = typeName.slice(typeName.lastIndexOf('.'));
-                }
-
-                if (typeNames.has(typeName)) {
-                    continue;
-                }
-
-                typeNames.add(typeName);
-
-                const constructors = typeConstructors[typeName];
-
-                if (!constructors) {
-                } else if (constructors.length === 1) {
-                    typeDefs.push(
-                        `Type${typeName} = ${constructors[0].className}`
-                    );
-                } else {
-                    typeDefs.push(
-                        `Type${typeName} = Union[${constructors
-                            .map(x => constructors.className)
-                            .join(',')}]`
-                    );
-                }
-            }
-        }*/
-
-        const imports = {}
-        const primitives = new Set(['int', 'long', 'int128', 'int256', 'double', 'string', 'bytes', 'Bool', 'true'])
-
-        // Find all the types in other files that are used in this file
-        // and generate the information required to import those types.
-        for (const t of tlobjects) {
-            for (const arg of t.args) {
-                let name = arg.type
-
-                if (!name || primitives.has(name)) {
-                    continue
-                }
-
-                let importSpace = `${dotDepth}/tl/types`
-
-                if (name.includes('.')) {
-                    const [namespace] = name.split('.')
-                    name = name.split('.')
-                    importSpace += `/${namespace}`
-                }
-
-                if (!typeNames.has(name)) {
-                    typeNames.add(name)
-
-                    if (name === 'date') {
-                        imports.datetime = ['datetime']
-                        continue
-                    } else if (!(importSpace in imports)) {
-                        imports[importSpace] = new Set()
-                    }
-
-                    imports[importSpace].add(`Type${name}`)
-                }
-            }
-        }
-
-        // Add imports required for type checking
-        /**
-         if (imports) {
-            builder.writeln('if (false) { // TYPE_CHECKING {');
-
-            for (const [namespace, names] of Object.entries(imports)) {
-                builder.writeln(
-                    `const { ${[...names.values()].join(
-                        ', '
-                    )} } =  require('${namespace}');`
-                );
-            }
-
-            builder.endBlock();
-        }*/
-
-        // Generate the class for every TLObject
-        for (const t of tlobjects) {
-            if (t.fullname in PATCHED_TYPES) {
-                builder.writeln(`const ${t.className} = null; // Patched`)
-            } else {
-                writeSourceCode(t, kind, builder, typeConstructors)
-                builder.currentIndent = 0
-            }
-        }
-
-        // Write the type definitions generated earlier.
-        builder.writeln()
-
-        for (const line of typeDefs) {
-            builder.writeln(line)
-        }
-        writeModuleExports(tlobjects, builder)
-        if (file.indexOf('index.js') > 0) {
-            for (const [ns] of Object.entries(namespaceTlobjects)) {
-                if (ns !== 'null') {
-                    builder.writeln('let %s = require(\'./%s\');', ns, ns)
-                }
-            }
-            for (const [ns] of Object.entries(namespaceTlobjects)) {
-                if (ns !== 'null') {
-                    builder.writeln('module.exports.%s = %s;', ns, ns)
-                }
-            }
-        }
-    }
-}
-
-const writeReadResult = (tlobject, builder) => {
-    // Only requests can have a different response that's not their
-    // serialized body, that is, we'll be setting their .result.
-    //
-    // The default behaviour is reading a TLObject too, so no need
-    // to override it unless necessary.
-    if (!tlobject.isFunction) {
-        return
-    }
-
-    // https://core.telegram.org/mtproto/serialize#boxed-and-bare-types
-    // TL;DR; boxed types start with uppercase always, so we can use
-    // this to check whether everything in it is boxed or not.
-    //
-    // Currently only un-boxed responses are Vector<int>/Vector<long>.
-    // If this weren't the case, we should check upper case after
-    // max(index('<'), index('.')) (and if it is, it's boxed, so return).
-    const m = tlobject.result.match(/Vector<(int|long)>/)
-    if (!m) {
-        return
-    }
-
-    // builder.endBlock();
-    builder.writeln('readResult(reader){')
-    builder.writeln('reader.readInt();  // Vector ID')
-    builder.writeln('let temp = [];')
-    builder.writeln('let len = reader.readInt(); //fix this')
-    builder.writeln('for (let i=0;i<len;i++){')
-    const read = m[1][0].toUpperCase() + m[1].slice(1)
-    builder.writeln('temp.push(reader.read%s())', read)
-    builder.endBlock()
-    builder.writeln('return temp')
-    builder.endBlock()
-}
-
-/**
- * Writes the source code corresponding to the given TLObject
- * by making use of the ``builder`` `SourceBuilder`.
- *
- * Additional information such as file path depth and
- * the ``Type: [Constructors]`` must be given for proper
- * importing and documentation strings.
- */
-const writeSourceCode = (tlobject, kind, builder, typeConstructors) => {
-    writeClassConstructor(tlobject, kind, typeConstructors, builder)
-    writeResolve(tlobject, builder)
-    // writeToJson(tlobject, builder);
-    writeToBytes(tlobject, builder)
-    builder.currentIndent--
-    writeFromReader(tlobject, builder)
-    writeReadResult(tlobject, builder)
-    builder.currentIndent--
-    builder.writeln('}')
-}
-
-const writeClassConstructor = (tlobject, kind, typeConstructors, builder) => {
-    builder.writeln()
-    builder.writeln()
-    builder.writeln(`class ${tlobject.className} extends ${kind} {`)
-    builder.writeln(`static CONSTRUCTOR_ID = 0x${tlobject.id.toString(16).padStart(8, '0')};`)
-    builder.writeln(`static SUBCLASS_OF_ID = 0x${crc32(tlobject.result).toString(16)};`)
-    builder.writeln()
-    // Write the __init__ function if it has any argument
-    if (!tlobject.realArgs.length) {
-        builder.writeln(`constructor() {`)
-        builder.writeln(`super();`)
-        builder.writeln(`this.CONSTRUCTOR_ID = 0x${tlobject.id.toString(16).padStart(8, '0')};`)
-        builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(tlobject.result).toString(16)};`)
-
-        builder.writeln()
-        builder.currentIndent--
-        builder.writeln('}')
-        return
-    }
-
-    // Note : this is needed to be able to access them
-    // with or without an instance
-
-
-    builder.writeln('/**')
-
-    if (tlobject.isFunction) {
-        builder.write(`:returns ${tlobject.result}: `)
-    } else {
-        builder.write(`Constructor for ${tlobject.result}: `)
-    }
-
-    const constructors = typeConstructors[tlobject.result]
-
-    if (!constructors) {
-        builder.writeln('This type has no constructors.')
-    } else if (constructors.length === 1) {
-        builder.writeln(`Instance of ${constructors[0].className}`)
-    } else {
-        builder.writeln(`Instance of either ${constructors.map((c) => c.className).join(', ')}`)
-    }
-
-    builder.writeln('*/')
-    builder.writeln(`constructor(args) {`)
-    builder.writeln(`super();`)
-    builder.writeln(`args = args || {}`)
-    // Class-level variable to store its Telegram's constructor ID
-    builder.writeln(`this.CONSTRUCTOR_ID = 0x${tlobject.id.toString(16).padStart(8, '0')};`)
-    builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(tlobject.result).toString(16)};`)
-
-    builder.writeln()
-
-    // Set the arguments
-    for (const arg of tlobject.realArgs) {
-        if (!arg.canBeInferred) {
-            builder.writeln(
-                `this.${variableSnakeToCamelCase(arg.name)} = args.${variableSnakeToCamelCase(
-                    arg.name,
-                )}${arg.isFlag || arg.canBeInferred ? ' || null' : ''};`,
-            )
-
-            // Currently the only argument that can be
-            // inferred are those called 'random_id'
-        } else if (arg.name === 'random_id') {
-            // Endianness doesn't really matter, and 'big' is shorter
-            let code = `readBigIntFromBuffer(generateRandomBytes(${
-                arg.type === 'long' ? 8 : 4
-            }),false,true)`
-
-            if (arg.isVector) {
-                // Currently for the case of "messages.forwardMessages"
-                // Ensure we can infer the length from id:Vector<>
-                if (!tlobject.realArgs.find((a) => a.name === 'id').isVector) {
-                    throw new Error(`Cannot infer list of random ids for ${tlobject}`)
-                }
-
-                code = `new Array(id.length).fill().map(_ => ${code})`
-            }
-
-            builder.writeln(`this.randomId = args.randomId !== undefined ? args.randomId : ${code};`)
-        } else {
-            throw new Error(`Cannot infer a value for ${arg}`)
-        }
-    }
-
-    builder.endBlock()
-}
-
-const writeResolve = (tlobject, builder) => {
-    if (
-        tlobject.isFunction &&
-        tlobject.realArgs.some(
-            (arg) =>
-                arg.type in AUTO_CASTS ||
-                (`${arg.name},${arg.type}` in NAMED_AUTO_CASTS && !NAMED_BLACKLIST.has(tlobject.fullname)),
-        )
-    ) {
-        builder.writeln('async resolve(client, utils) {')
-
-        for (const arg of tlobject.realArgs) {
-            let ac = AUTO_CASTS[arg.type]
-
-            if (!ac) {
-                ac = NAMED_AUTO_CASTS[`${variableSnakeToCamelCase(arg.name)},${arg.type}`]
-
-                if (!ac) {
-                    continue
-                }
-            }
-
-            if (arg.isFlag) {
-                builder.writeln(`if (this.${variableSnakeToCamelCase(arg.name)}) {`)
-            }
-
-            if (arg.isVector) {
-                builder.write(`const _tmp = [];`)
-                builder.writeln(`for (const _x of this.${variableSnakeToCamelCase(arg.name)}) {`)
-                builder.writeln(`_tmp.push(%s);`, util.format(ac, '_x'))
-                builder.endBlock()
-                builder.writeln(`this.${variableSnakeToCamelCase(arg.name)} = _tmp;`)
-            } else {
-                builder.writeln(`this.${arg.name} = %s`,
-                    util.format(ac, `this.${variableSnakeToCamelCase(arg.name)}`))
-            }
-
-            if (arg.isFlag) {
-                builder.currentIndent--
-                builder.writeln('}')
-            }
-        }
-
-        builder.endBlock()
-    }
-}
-/**
- const writeToJson = (tlobject, builder) => {
-    builder.writeln('toJson() {');
-    builder.writeln('return {');
-
-    builder.write("_: '%s'", tlobject.className);
-
-    for (const arg of tlobject.realArgs) {
-        builder.writeln(',');
-        builder.write('%s: ', arg.name);
-
-        if (BASE_TYPES.includes(arg.type)) {
-            if (arg.isVector) {
-                builder.write(
-                    'this.%s === null ? [] : this.%s.slice()',
-                    arg.name,
-                    arg.name
-                );
-            } else {
-                builder.write('this.%s', arg.name);
-            }
-        } else {
-            if (arg.isVector) {
-                builder.write(
-                    'this.%s === null ? [] : this.%s.map(x => x instanceof TLObject ? x.toJson() : x)',
-                    arg.name,
-                    arg.name
-                );
-            } else {
-                builder.write(
-                    'this.%s instanceof TLObject ? this.%s.toJson() : this.%s',
-                    arg.name,
-                    arg.name,
-                    arg.name
-                );
-            }
-        }
-    }
-
-    builder.writeln();
-    builder.endBlock();
-    builder.currentIndent--;
-    builder.writeln('}');
-};
- */
-const writeToBytes = (tlobject, builder) => {
-    builder.writeln('getBytes() {')
-
-    // Some objects require more than one flag parameter to be set
-    // at the same time. In this case, add an assertion.
-    const repeatedArgs = {}
-    for (const arg of tlobject.args) {
-        if (arg.isFlag) {
-            if (!repeatedArgs[arg.flagIndex]) {
-                repeatedArgs[arg.flagIndex] = []
-            }
-            repeatedArgs[arg.flagIndex].push(arg)
-        }
-    }
-
-    for (const ra of Object.values(repeatedArgs)) {
-        if (ra.length > 1) {
-            const cnd1 = []
-            const cnd2 = []
-            const names = []
-
-            for (const a of ra) {
-                cnd1.push(`this.${a.name} || this.${a.name}!==null`)
-                cnd2.push(`this.${a.name}===null || this.${a.name}===false`)
-                names.push(a.name)
-            }
-            builder.writeln(
-                'if (!((%s) && (%s)))\n\t throw new Error(\'%s paramaters must all be false-y or all true\')',
-                cnd1.join(' && '),
-                cnd2.join(' && '),
-                names.join(', '),
-            )
-        }
-    }
-    builder.writeln('return Buffer.concat([')
-    builder.currentIndent++
-    const b = Buffer.alloc(4)
-    b.writeUInt32LE(tlobject.id, 0)
-    // First constructor code, we already know its bytes
-    builder.writeln('Buffer.from("%s","hex"),', b.toString('hex'))
-    for (const arg of tlobject.args) {
-        if (writeArgToBytes(builder, arg, tlobject.args)) {
-            builder.writeln(',')
-        }
-    }
-    builder.writeln('])')
-    builder.endBlock()
-}
-
-// writeFromReader
-const writeFromReader = (tlobject, builder) => {
-    builder.writeln('static fromReader(reader) {')
-    for (const arg of tlobject.args) {
-        if (arg.name !== 'flag') {
-            if (arg.name !== 'x') {
-                builder.writeln('let %s', '_' + arg.name + ';')
-            }
-        }
-    }
-
-    // TODO fix this really
-    builder.writeln('let _x;')
-    builder.writeln('let len;')
-
-    for (const arg of tlobject.args) {
-        writeArgReadCode(builder, arg, tlobject.args, '_' + arg.name)
-    }
-    const temp = []
-    for (const a of tlobject.realArgs) {
-        temp.push(`${variableSnakeToCamelCase(a.name)}:_${a.name}`)
-    }
-    builder.writeln('return new this({%s})', temp.join(',\n\t'))
-    builder.endBlock()
-}
-// writeReadResult
-
-/**
- * Writes the .__bytes__() code for the given argument
- * @param builder: The source code builder
- * @param arg: The argument to write
- * @param args: All the other arguments in TLObject same __bytes__.
- *              This is required to determine the flags value
- * @param name: The name of the argument. Defaults to "self.argname"
- *              This argument is an option because it's required when
- *              writing Vectors<>
- */
-const writeArgToBytes = (builder, arg, args, name = null) => {
-    if (arg.genericDefinition) {
-        return // Do nothing, this only specifies a later type
-    }
-
-    if (name === null) {
-        name = `this.${arg.name}`
-    }
-
-    name = variableSnakeToCamelCase(name)
-    // The argument may be a flag, only write if it's not None AND
-    // if it's not a True type.
-    // True types are not actually sent, but instead only used to
-    // determine the flags.
-    if (arg.isFlag) {
-        if (arg.type === 'true') {
-            return // Exit, since true type is never written
-        } else if (arg.isVector) {
-            // Vector flags are special since they consist of 3 values,
-            // so we need an extra join here. Note that empty vector flags
-            // should NOT be sent either!
-            builder.write(
-                '(%s === undefined || %s === false || %s ===null) ? Buffer.alloc(0) :Buffer.concat([',
-                name,
-                name,
-                name,
-            )
-        } else {
-            builder.write('(%s === undefined || %s === false || %s ===null) ? Buffer.alloc(0) : [', name, name, name)
-        }
-    }
-
-    if (arg.isVector) {
-        if (arg.useVectorId) {
-            builder.write('Buffer.from(\'15c4b51c\', \'hex\'),')
-        }
-
-        builder.write('struct.pack(\'<i\', %s.length),', name)
-
-        // Cannot unpack the values for the outer tuple through *[(
-        // since that's a Python >3.5 feature, so add another join.
-        builder.write('Buffer.concat(%s.map(x => ', name)
-        // Temporary disable .is_vector, not to enter this if again
-        // Also disable .is_flag since it's not needed per element
-        const oldFlag = arg.isFlag
-        arg.isVector = arg.isFlag = false
-        writeArgToBytes(builder, arg, args, 'x')
-        arg.isVector = true
-        arg.isFlag = oldFlag
-
-        builder.write('))')
-    } else if (arg.flagIndicator) {
-        // Calculate the flags with those items which are not None
-        if (!args.some((f) => f.isFlag)) {
-            // There's a flag indicator, but no flag arguments so it's 0
-            builder.write('Buffer.alloc(4)')
-        } else {
-            builder.write('struct.pack(\'<I\', ')
-            builder.write(
-                args
-                    .filter((flag) => flag.isFlag)
-                    .map(
-                        (flag) =>
-                            `(this.${variableSnakeToCamelCase(
-                                flag.name,
-                            )} === undefined || this.${variableSnakeToCamelCase(
-                                flag.name,
-                            )} === false || this.${variableSnakeToCamelCase(flag.name)} === null) ? 0 : ${1 <<
-                            flag.flagIndex}`,
-                    )
-                    .join(' | '),
-            )
-            builder.write(')')
-        }
-    } else if (arg.type === 'int') {
-        builder.write('struct.pack(\'<i\', %s)', name)
-    } else if (arg.type === 'long') {
-        builder.write('readBufferFromBigInt(%s,8,true,true)', name)
-    } else if (arg.type === 'int128') {
-        builder.write('readBufferFromBigInt(%s,16,true,true)', name)
-    } else if (arg.type === 'int256') {
-        builder.write('readBufferFromBigInt(%s,32,true,true)', name)
-    } else if (arg.type === 'double') {
-        builder.write('struct.pack(\'<d\', %s.toString())', name)
-    } else if (arg.type === 'string') {
-        builder.write('TLObject.serializeBytes(%s)', name)
-    } else if (arg.type === 'Bool') {
-        builder.write('%s ? 0xb5757299 : 0x379779bc', name)
-    } else if (arg.type === 'true') {
-        // These are actually NOT written! Only used for flags
-    } else if (arg.type === 'bytes') {
-        builder.write('TLObject.serializeBytes(%s)', name)
-    } else if (arg.type === 'date') {
-        builder.write('TLObject.serializeDatetime(%s)', name)
-    } else {
-        // Else it may be a custom type
-        builder.write('%s.getBytes()', name)
-
-        // If the type is not boxed (i.e. starts with lowercase) we should
-        // not serialize the constructor ID (so remove its first 4 bytes).
-        let boxed = arg.type.charAt(arg.type.indexOf('.') + 1)
-        boxed = boxed === boxed.toUpperCase()
-
-        if (!boxed) {
-            builder.write('.slice(4)')
-        }
-    }
-
-    if (arg.isFlag) {
-        builder.write(']')
-
-        if (arg.isVector) {
-            builder.write(')')
-        }
-    }
-
-    return true
-}
-
-/**
- * Writes the read code for the given argument, setting the
- * arg.name variable to its read value.
- * @param builder The source code builder
- * @param arg The argument to write
- * @param args All the other arguments in TLObject same on_send.
- * This is required to determine the flags value
- * @param name The name of the argument. Defaults to "self.argname"
- * This argument is an option because it's required when
- * writing Vectors<>
- */
-const writeArgReadCode = (builder, arg, args, name) => {
-    if (arg.genericDefinition) {
-        return // Do nothing, this only specifies a later type
-    }
-    // The argument may be a flag, only write that flag was given!
-    let wasFlag = false
-    if (arg.isFlag) {
-        // Treat 'true' flags as a special case, since they're true if
-        // they're set, and nothing else needs to actually be read.
-        if (arg.type === 'true') {
-            builder.writeln('%s = Boolean(flags & %s);', name, 1 << arg.flagIndex)
-            return
-        }
-
-        wasFlag = true
-        builder.writeln('if (flags & %s) {', 1 << arg.flagIndex)
-        // Temporary disable .is_flag not to enter this if
-        // again when calling the method recursively
-        arg.isFlag = false
-    }
-
-    if (arg.isVector) {
-        if (arg.useVectorId) {
-            // We have to read the vector's constructor ID
-            builder.writeln('reader.readInt();')
-        }
-
-        builder.writeln('%s = [];', name)
-        builder.writeln('len = reader.readInt();')
-        builder.writeln('for (let i=0;i<len;i++){')
-
-        // Temporary disable .is_vector, not to enter this if again
-        arg.isVector = false
-        writeArgReadCode(builder, arg, args, '_x')
-        builder.writeln('%s.push(_x);', name)
-        arg.isVector = true
-    } else if (arg.flagIndicator) {
-        // Read the flags, which will indicate what items we should read next
-        builder.writeln('let flags = reader.readInt();')
-        builder.writeln()
-    } else if (arg.type === 'int') {
-        builder.writeln('%s = reader.readInt();', name)
-    } else if (arg.type === 'long') {
-        builder.writeln('%s = reader.readLong();', name)
-    } else if (arg.type === 'int128') {
-        builder.writeln('%s = reader.readLargeInt(128);', name)
-    } else if (arg.type === 'int256') {
-        builder.writeln('%s = reader.readLargeInt(256);', name)
-    } else if (arg.type === 'double') {
-        builder.writeln('%s = reader.readDouble();', name)
-    } else if (arg.type === 'string') {
-        builder.writeln('%s = reader.tgReadString();', name)
-    } else if (arg.type === 'Bool') {
-        builder.writeln('%s = reader.tgReadBool();', name)
-    } else if (arg.type === 'true') {
-        builder.writeln('%s = true;', name)
-    } else if (arg.type === 'bytes') {
-        builder.writeln('%s = reader.tgReadBytes();', name)
-    } else if (arg.type === 'date') {
-        builder.writeln('%s = reader.tgReadDate();', name)
-    } else {
-        // Else it may be a custom type
-        if (!arg.skipConstructorId) {
-            builder.writeln('%s = reader.tgReadObject();', name)
-        } else {
-            // Import the correct type inline to avoid cyclic imports.
-            // There may be better solutions so that we can just access
-            // all the types before the files have been parsed, but I
-            // don't know of any.
-            const sepIndex = arg.type.indexOf('.')
-            let ns
-            let t
-
-            if (sepIndex === -1) {
-                ns = '.'
-                t = arg.type
-            } else {
-                ns = '.' + arg.type.slice(0, sepIndex)
-                t = arg.type.slice(sepIndex + 1)
-            }
-
-            const className = snakeToCamelCase(t)
-
-            // There would be no need to import the type if we're in the
-            // file with the same namespace, but since it does no harm
-            // and we don't have information about such thing in the
-            // method we just ignore that case.
-            builder.writeln('let %s = require("%s");', className, ns)
-            builder.writeln('%s = %s.fromReader(reader);', name, className)
-        }
-    }
-
-    // End vector and flag blocks if required (if we opened them before)
-    if (arg.isVector) {
-        builder.writeln('}')
-    }
-
-    if (wasFlag) {
-        builder.endBlock()
-        builder.writeln('else {')
-        builder.writeln('%s = null', name)
-        builder.endBlock()
-        // Restore .isFlag;
-        arg.isFlag = true
-    }
-}
-
-const writePatched = (outDir, namespaceTlobjects) => {
-    fs.mkdirSync(outDir, { recursive: true })
-
-    for (const [ns, tlobjects] of Object.entries(namespaceTlobjects)) {
-        const file = `${outDir}/${ns === 'null' ? 'index' : ns}.js`
-        const stream = fs.createWriteStream(file)
-        const builder = new SourceBuilder(stream)
-
-        builder.writeln(AUTO_GEN_NOTICE)
-        builder.writeln('const struct = require(\'python-struct\');')
-        builder.writeln(`const { TLObject, types, custom } = require('..');`)
-
-        builder.writeln()
-
-        for (const t of tlobjects) {
-            builder.writeln('class %s extends custom.%s {', t.className, PATCHED_TYPES[t.fullname])
-            builder.writeln(`static CONSTRUCTOR_ID = 0x${t.id.toString(16)}`)
-            builder.writeln(`static SUBCLASS_OF_ID = 0x${crc32(t.result).toString('16')}`)
-            builder.writeln()
-            builder.writeln('constructor() {')
-            builder.writeln('super();')
-            builder.writeln(`this.CONSTRUCTOR_ID = 0x${t.id.toString(16)}`)
-            builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(t.result).toString('16')}`)
-
-            builder.endBlock()
-
-            // writeToJson(t, builder);
-            writeToBytes(t, builder)
-            writeFromReader(t, builder)
-
-            builder.writeln()
-            builder.endBlock()
-            builder.currentIndent = 0
-            builder.writeln('types.%s%s = %s', t.namespace ? `${t.namespace}.` : '', t.className, t.className)
-            builder.writeln()
-        }
-    }
-}
-
-const writeAllTLObjects = (tlobjects, layer, builder) => {
-    builder.writeln(AUTO_GEN_NOTICE)
-    builder.writeln()
-
-    builder.writeln('const { types, functions, patched } = require(\'.\');')
-    builder.writeln()
-
-    // Create a constant variable to indicate which layer this is
-    builder.writeln(`const LAYER = %s;`, layer)
-    builder.writeln()
-
-    // Then create the dictionary containing constructor_id: class
-    builder.writeln('const tlobjects = {')
-
-    // Fill the dictionary (0x1a2b3c4f: tl.full.type.path.Class)
-    for (const tlobject of tlobjects) {
-        builder.write('0x0%s: ', tlobject.id.toString(16).padStart(8, '0'))
-
-        if (tlobject.fullname in PATCHED_TYPES) {
-            builder.write('patched')
-        } else {
-            builder.write(tlobject.isFunction ? 'functions' : 'types')
-        }
-
-        if (tlobject.namespace) {
-            builder.write('.%s', tlobject.namespace)
-        }
-
-        builder.writeln('.%s,', tlobject.className)
-    }
-
-    builder.endBlock(true)
-    builder.writeln('')
-    builder.writeln('module.exports = {')
-    builder.writeln('LAYER,')
-    builder.writeln('tlobjects')
-    builder.endBlock(true)
-}
-
-const generateTLObjects = (tlobjects, layer, importDepth, outputDir) => {
-    // Group everything by {namespace :[tlobjects]} to generate index.js
-    const namespaceFunctions = {}
-    const namespaceTypes = {}
-    const namespacePatched = {}
-
-    // Group {type: [constructors]} to generate the documentation
-    const typeConstructors = {}
-
-    for (const tlobject of tlobjects) {
-        if (tlobject.isFunction) {
-            if (!namespaceFunctions[tlobject.namespace]) {
-                namespaceFunctions[tlobject.namespace] = []
-            }
-
-            namespaceFunctions[tlobject.namespace].push(tlobject)
-        } else {
-            if (!namespaceTypes[tlobject.namespace]) {
-                namespaceTypes[tlobject.namespace] = []
-            }
-
-            if (!typeConstructors[tlobject.result]) {
-                typeConstructors[tlobject.result] = []
-            }
-
-            namespaceTypes[tlobject.namespace].push(tlobject)
-            typeConstructors[tlobject.result].push(tlobject)
-
-            if (tlobject.fullname in PATCHED_TYPES) {
-                if (!namespacePatched[tlobject.namespace]) {
-                    namespacePatched[tlobject.namespace] = []
-                }
-
-                namespacePatched[tlobject.namespace].push(tlobject)
-            }
-        }
-    }
-
-    writeModules(`${outputDir}/functions`, importDepth, 'TLRequest', namespaceFunctions, typeConstructors)
-    writeModules(`${outputDir}/types`, importDepth, 'TLObject', namespaceTypes, typeConstructors)
-    writePatched(`${outputDir}/patched`, namespacePatched)
-
-    const filename = `${outputDir}/AllTLObjects.js`
-    const stream = fs.createWriteStream(filename)
-    const builder = new SourceBuilder(stream)
-
-    writeAllTLObjects(tlobjects, layer, builder)
-}
-
-const cleanTLObjects = (outputDir) => {
-    for (let d of ['functions', 'types', 'patched']) {
-        d = `${outputDir}/d`
-
-        if (fs.statSync(d).isDirectory()) {
-            fs.rmdirSync(d)
-        }
-    }
-
-    const tl = `${outputDir}/AllTLObjects.js`
-
-    if (fs.statSync(tl).isFile()) {
-        fs.unlinkSync(tl)
-    }
-}
-
-const writeModuleExports = (tlobjects, builder) => {
-    builder.writeln('module.exports = {')
-
-    for (const t of tlobjects) {
-        builder.writeln(`${t.className},`)
-    }
-
-    builder.currentIndent--
-    builder.writeln('};')
-}
-
-module.exports = {
-    generateTLObjects,
-    cleanTLObjects,
-}

+ 0 - 92
gramjs_generator/parsers/errors.js

@@ -1,92 +0,0 @@
-const fs = require('fs')
-const csvParse = require('csv-parse/lib/sync')
-const { snakeToCamelCase, variableSnakeToCamelCase } = require('../utils')
-
-const KNOWN_BASE_CLASSES = {
-    303: 'InvalidDCError',
-    400: 'BadRequestError',
-    401: 'UnauthorizedError',
-    403: 'ForbiddenError',
-    404: 'NotFoundError',
-    406: 'AuthKeyError',
-    420: 'FloodError',
-    500: 'ServerError',
-    503: 'TimedOutError',
-}
-
-/**
- * Gets the corresponding class name for the given error code,
- * this either being an integer (thus base error name) or str.
- */
-const getClassName = (errorCode) => {
-    if (typeof errorCode === 'number') {
-        return KNOWN_BASE_CLASSES[Math.abs(errorCode)] || 'RPCError' + errorCode.toString().replace('-', 'Neg')
-    }
-
-    return snakeToCamelCase(
-        errorCode
-            .replace('FIRSTNAME', 'FIRST_NAME')
-            .replace('SLOWMODE', 'SLOW_MODE')
-            .toLowerCase(),
-        'Error',
-    )
-}
-
-class TelegramError {
-    constructor(codes, name, description) {
-        // TODO Some errors have the same name but different integer codes
-        // Should these be split into different files or doesn't really matter?
-        // Telegram isn't exactly consistent with returned errors anyway.
-        [this.intCode] = codes
-        this.stringCode = name
-        this.subclass = getClassName(codes[0])
-        this.subclassExists = Math.abs(codes[0]) in KNOWN_BASE_CLASSES
-        this.description = description
-        this.hasCaptures = name.includes('_X')
-
-        if (this.hasCaptures) {
-            this.name = variableSnakeToCamelCase(getClassName(name.replace('_X', '')))
-            this.pattern = variableSnakeToCamelCase(name.replace('_X', '_(\\d+)'))
-            this.captureName = variableSnakeToCamelCase(description.match(/{(\w+)}/)[1])
-        } else {
-            this.name = variableSnakeToCamelCase(getClassName(name))
-            this.pattern = variableSnakeToCamelCase(name)
-            this.captureName = null
-        }
-    }
-}
-
-/**
- * Parses the input CSV file with columns (name, error codes, description)
- * and yields `Error` instances as a result.
- */
-const parseErrors = function* (csvFile) {
-    const f = csvParse(fs.readFileSync(csvFile, { encoding: 'utf-8' })).slice(1)
-
-    for (let line = 0; line < f.length; line++) {
-        if (f[line].length !== 3) {
-            throw new Error(`Columns count mismatch, unquoted comma in desc? (line ${line + 2})`)
-        }
-
-        let [name, codes, description] = f[line]
-
-        codes =
-            codes === '' ?
-                [400] :
-                codes.split(' ').map((x) => {
-                    if (isNaN(x)) {
-                        throw new Error(`Not all codes are integers (line ${line + 2})`)
-                    }
-
-                    return Number(x)
-                })
-
-        yield new TelegramError(codes, name, description)
-    }
-}
-
-module.exports = {
-    KNOWN_BASE_CLASSES,
-    TelegramError,
-    parseErrors,
-}

+ 0 - 14
gramjs_generator/parsers/index.js

@@ -1,14 +0,0 @@
-const { TelegramError, parseErrors } = require('./errors')
-const { MethodInfo, Usability, parseMethods } = require('./methods')
-const { TLObject, parseTl, findLayer } = require('./tlobject')
-
-module.exports = {
-    TelegramError,
-    parseErrors,
-    MethodInfo,
-    Usability,
-    parseMethods,
-    TLObject,
-    parseTl,
-    findLayer,
-}

+ 0 - 65
gramjs_generator/parsers/methods.js

@@ -1,65 +0,0 @@
-const fs = require('fs')
-const csvParse = require('csv-parse/lib/sync')
-
-const Usability = {
-    UNKNOWN: 0,
-    USER: 1,
-    BOT: 2,
-    BOTH: 4,
-}
-
-class MethodInfo {
-    constructor(name, usability, errors, friendly) {
-        this.name = name
-        this.errors = errors
-        this.friendly = friendly
-
-        if (usability.toUpperCase() in Usability) {
-            this.usability = Usability[usability.toUpperCase()]
-        } else {
-            throw new Error(`Usability must be either user, bot, both or unknown, not ${usability}`)
-        }
-    }
-}
-
-/**
- * Parses the input CSV file with columns (method, usability, errors)
- * and yields `MethodInfo` instances as a result.
- */
-const parseMethods = function* (csvFile, friendlyCsvFile, errorsDict) {
-    const rawToFriendly = {}
-    const f1 = csvParse(fs.readFileSync(friendlyCsvFile, { encoding: 'utf-8' }))
-
-    for (const [ns, friendly, rawList] of f1.slice(1)) {
-        for (const raw of rawList.split(' ')) {
-            rawToFriendly[raw] = [ns, friendly]
-        }
-    }
-
-    const f2 = csvParse(fs.readFileSync(csvFile, { encoding: 'utf-8' })).slice(1)
-
-    for (let line = 0; line < f2.length; line++) {
-        let [method, usability, errors] = f2[line]
-
-        errors = errors
-            .split(' ')
-            .filter(Boolean)
-            .map((x) => {
-                if (x && !(x in errorsDict)) {
-                    throw new Error(`Method ${method} references unknown errors ${errors}`)
-                }
-
-                return errorsDict[x]
-            })
-
-        const friendly = rawToFriendly[method]
-        delete rawToFriendly[method]
-        yield new MethodInfo(method, usability, errors, friendly)
-    }
-}
-
-module.exports = {
-    Usability,
-    MethodInfo,
-    parseMethods,
-}

+ 0 - 10
gramjs_generator/parsers/tlobject/index.js

@@ -1,10 +0,0 @@
-const { TLArg } = require('./tlarg')
-const { TLObject } = require('./tlobject')
-const { parseTl, findLayer } = require('./parser')
-
-module.exports = {
-    TLArg,
-    TLObject,
-    parseTl,
-    findLayer,
-}

+ 0 - 188
gramjs_generator/parsers/tlobject/parser.js

@@ -1,188 +0,0 @@
-const fs = require('fs')
-const { TLArg } = require('./tlarg')
-const { TLObject } = require('./tlobject')
-const { Usability } = require('../methods')
-
-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;
-])
-
-// Telegram Desktop (C++) doesn't care about string/bytes, and the .tl files
-// don't either. However in Python we *do*, and we want to deal with bytes
-// for the authorization key process, not UTF-8 strings (they won't be).
-//
-// Every type with an ID that's in here should get their attribute types
-// with string being replaced with bytes.
-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 findall = (regex, str, matches) => {
-    if (!matches) {
-        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
-}
-
-const fromLine = (line, isFunction, methodInfo, layer) => {
-    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 [, name] = match
-    methodInfo = methodInfo[name]
-
-    let usability
-    let friendly
-
-    if (methodInfo) {
-        usability = methodInfo.usability
-        friendly = methodInfo.friendly
-    } else {
-        usability = Usability.UNKNOWN
-        friendly = null
-    }
-
-    return new TLObject(
-        name,
-        match[2],
-        argsMatch.map(([brace, name, argType]) => new TLArg(name, argType, brace !== undefined)),
-        match[3],
-        isFunction,
-        usability,
-        friendly,
-        layer,
-    )
-}
-
-/**
- * This method yields TLObjects from a given .tl file.
- *
- * Note that the file is parsed completely before the function yields
- * because references to other objects may appear later in the file.
- */
-const parseTl = function* (filePath, layer, methods, ignoreIds = CORE_TYPES) {
-    const methodInfo = (methods || []).reduce((o, m) => ({ ...o, [m.name]: m }), {})
-    const objAll = []
-    const objByName = {}
-    const objByType = {}
-
-    const file = fs.readFileSync(filePath, { encoding: 'utf-8' })
-
-    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, methodInfo, layer)
-
-            if (ignoreIds.has(result.id)) {
-                continue
-            }
-
-            objAll.push(result)
-
-            if (!result.isFunction) {
-                if (!objByType[result.result]) {
-                    objByType[result.result] = []
-                }
-
-                objByName[result.fullname] = 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) {
-        if (AUTH_KEY_TYPES.has(obj.id)) {
-            for (const arg of obj.args) {
-                if (arg.type === 'string') {
-                    arg.type = 'bytes'
-                }
-            }
-        }
-
-        for (const arg of obj.args) {
-            arg.cls = objByType[arg.type] || (arg.type in objByName ? [objByName[arg.type]] : [])
-        }
-    }
-
-    for (const obj of objAll) {
-        yield obj
-    }
-}
-
-/**
- * Finds the layer used on the specified scheme.tl file.
- */
-const findLayer = (filePath) => {
-    const layerRegex = /^\/\/\s*LAYER\s*(\d+)/
-
-    const file = fs.readFileSync(filePath, { encoding: 'utf-8' })
-    for (const line of file.split('\n')) {
-        const match = line.match(layerRegex)
-        if (match) {
-            return Number(match[1])
-        }
-    }
-}
-
-module.exports = {
-    parseTl,
-    findLayer,
-}

+ 0 - 287
gramjs_generator/parsers/tlobject/tlarg.js

@@ -1,287 +0,0 @@
-const fmtStrings = (...objects) => {
-    for (const object of objects) {
-        for (const [k, v] of Object.entries(object)) {
-            if (['null', 'true', 'false'].includes(v)) {
-                object[k] = `<strong>${v}</strong>`
-            } else {
-                object[k] = v.replace(/((['"]).*\2)/, (_, g) => `<em>${g}</em>`)
-            }
-        }
-    }
-}
-
-const KNOWN_NAMED_EXAMPLES = {
-    'message,string': '\'Hello there!\'',
-    'expires_at,date': 'datetime.timedelta(minutes=5)',
-    'until_date,date': 'datetime.timedelta(days=14)',
-    'view_messages,true': 'None',
-    'send_messages,true': 'None',
-    'limit,int': '100',
-    'hash,int': '0',
-    'hash,string': '\'A4LmkR23G0IGxBE71zZfo1\'',
-    'min_id,int': '0',
-    'max_id,int': '0',
-    'min_id,long': '0',
-    'max_id,long': '0',
-    'add_offset,int': '0',
-    'title,string': '\'My awesome title\'',
-    'device_model,string': '\'ASUS Laptop\'',
-    'system_version,string': '\'Arch Linux\'',
-    'app_version,string': '\'1.0\'',
-    'system_lang_code,string': '\'en\'',
-    'lang_pack,string': '\'\'',
-    'lang_code,string': '\'en\'',
-    'chat_id,int': '478614198',
-    'client_id,long': 'random.randrange(-2**63, 2**63)',
-}
-
-const KNOWN_TYPED_EXAMPLES = {
-    int128: 'int.from_bytes(crypto.randomBytes(16), \'big\')',
-    bytes: 'b\'arbitrary\\x7f data \\xfa here\'',
-    long: '-12398745604826',
-    string: '\'some string here\'',
-    int: '42',
-    date: 'datetime.datetime(2018, 6, 25)',
-    double: '7.13',
-    Bool: 'False',
-    true: 'True',
-    InputChatPhoto: 'client.upload_file(\'/path/to/photo.jpg\')',
-    InputFile: 'client.upload_file(\'/path/to/file.jpg\')',
-    InputPeer: '\'username\'',
-}
-
-fmtStrings(KNOWN_NAMED_EXAMPLES, KNOWN_TYPED_EXAMPLES)
-
-const SYNONYMS = {
-    InputUser: 'InputPeer',
-    InputChannel: 'InputPeer',
-    InputDialogPeer: 'InputPeer',
-    InputNotifyPeer: 'InputPeer',
-    InputMessage: 'int',
-}
-
-// These are flags that are cleaner to leave off
-const OMITTED_EXAMPLES = [
-    'silent',
-    'background',
-    'clear_draft',
-    'reply_to_msg_id',
-    'random_id',
-    'reply_markup',
-    'entities',
-    'embed_links',
-    'hash',
-    'min_id',
-    'max_id',
-    'add_offset',
-    'grouped',
-    'broadcast',
-    'admins',
-    'edit',
-    'delete',
-]
-
-/**
- * Initializes a new .tl argument
- * :param name: The name of the .tl argument
- * :param argType: The type of the .tl argument
- * :param genericDefinition: Is the argument a generic definition?
- *                           (i.e. {X:Type})
- */
-class TLArg {
-    constructor(name, argType, genericDefinition) {
-        this.name = name === 'self' ? 'is_self' : name
-
-        // Default values
-        this.isVector = false
-        this.isFlag = false
-        this.skipConstructorId = false
-        this.flagIndex = -1
-        this.cls = 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)
-        this.canBeInferred = name === 'random_id'
-
-        // The type can be an indicator that other arguments will be flags
-        if (argType === '#') {
-            this.flagIndicator = true
-            this.type = null
-            this.isGeneric = false
-        } else {
-            this.flagIndicator = false
-            this.isGeneric = argType.startsWith('!')
-            // Strip the exclamation mark always to have only the name
-            this.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'
-            const flagMatch = this.type.match(/flags.(\d+)\?([\w<>.]+)/)
-
-            if (flagMatch) {
-                this.isFlag = true
-                this.flagIndex = Number(flagMatch[1]);
-                // Update the type to match the exact type, not the "flagged" one
-                [, , this.type] = flagMatch
-            }
-
-            // Then check if the type is a Vector<REAL_TYPE>
-            const vectorMatch = this.type.match(/[Vv]ector<([\w\d.]+)>/)
-
-            if (vectorMatch) {
-                this.isVector = true
-
-                // If the type's first letter is not uppercase, then
-                // it is a constructor and we use (read/write) its ID.
-                this.useVectorId = this.type.charAt(0) === 'V';
-
-                // Update the type to match the one inside the vector
-                [, this.type] = vectorMatch
-            }
-
-            // See use_vector_id. An example of such case is ipPort in
-            // help.configSpecial
-            if (
-                /^[a-z]$/.test(
-                    this.type
-                        .split('.')
-                        .pop()
-                        .charAt(0),
-                )
-            ) {
-                this.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';
-            // }
-        }
-
-        this.genericDefinition = genericDefinition
-    }
-
-    typeHint() {
-        let cls = this.type
-
-        if (cls.includes('.')) {
-            [, cls] = cls.split('.')
-        }
-
-        let result = {
-            int: 'int',
-            long: 'int',
-            int128: 'int',
-            int256: 'int',
-            double: 'float',
-            string: 'str',
-            date: 'Optional[datetime]', // None date = 0 timestamp
-            bytes: 'bytes',
-            Bool: 'bool',
-            true: 'bool',
-        }
-
-        result = result[cls] || `'Type${cls}'`
-
-        if (this.isVector) {
-            result = `List[${result}]`
-        }
-
-        if (this.isFlag && cls !== 'date') {
-            result = `Optional[${result}]`
-        }
-
-        return result
-    }
-
-    realType() {
-        // Find the real type representation by updating it as required
-        let realType = this.flagIndicator ? '#' : this.type
-
-        if (this.isVector) {
-            if (this.useVectorId) {
-                realType = `Vector<${realType}>`
-            } else {
-                realType = `vector<${realType}>`
-            }
-        }
-
-        if (this.isGeneric) {
-            realType = `!${realType}`
-        }
-
-        if (this.isFlag) {
-            realType = `flags.${this.flagIndex}?${realType}`
-        }
-
-        return realType
-    }
-
-    toString() {
-        if (this.genericDefinition) {
-            return `{${this.name}:${this.realType()}}`
-        } else {
-            return `${this.name}:${this.realType()}`
-        }
-    }
-
-    toJson() {
-        return {
-            name: this.name.replace('is_self', 'self'),
-            type: this.realType().replace(/\bdate$/, 'int'),
-        }
-    }
-
-    asExample(f, indent) {
-        if (this.isGeneric) {
-            f.write('other_request')
-            return
-        }
-
-        const known =
-            KNOWN_NAMED_EXAMPLES[`${this.name},${this.type}`] ||
-            KNOWN_TYPED_EXAMPLES[this.type] ||
-            KNOWN_TYPED_EXAMPLES[SYNONYMS[this.type]]
-
-        if (known) {
-            f.write(known)
-            return
-        }
-
-        // assert self.omit_example() or self.cls, 'TODO handle ' + str(self)
-
-        // Pick an interesting example if any
-        for (const cls of this.cls) {
-            if (cls.isGoodExample()) {
-                cls.asExample(f, indent || 0)
-                return
-            }
-        }
-
-        // If no example is good, just pick the first
-        this.cls[0].asExample(f, indent || 0)
-    }
-
-    omitExample() {
-        return this.isFlag || (this.canBeInferred && OMITTED_EXAMPLES.includes(this.name))
-    }
-}
-
-module.exports = {
-    KNOWN_NAMED_EXAMPLES,
-    KNOWN_TYPED_EXAMPLES,
-    SYNONYMS,
-    OMITTED_EXAMPLES,
-    TLArg,
-}

+ 0 - 191
gramjs_generator/parsers/tlobject/tlobject.js

@@ -1,191 +0,0 @@
-const { crc32 } = require('crc')
-const struct = require('python-struct')
-const { snakeToCamelCase } = require('../../utils')
-
-// https://github.com/telegramdesktop/tdesktop/blob/4bf66cb6e93f3965b40084771b595e93d0b11bcd/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py#L57-L62
-const WHITELISTED_MISMATCHING_IDS = {
-    // 0 represents any layer
-    0: new Set([
-        'channel', // Since layer 77, there seems to be no going back...
-        'ipPortSecret',
-        'accessPointRule',
-        'help.configSimple',
-    ]),
-}
-
-/**
- * Initializes a new TLObject, given its properties.
- *
- * :param fullname: The fullname of the TL object (namespace.name)
- *                  The namespace can be omitted.
- * :param object_id: The hexadecimal string representing the object ID
- * :param args: The arguments, if any, of the TL object
- * :param result: The result type of the TL object
- * :param is_function: Is the object a function or a type?
- * :param usability: The usability for this method.
- * :param friendly: A tuple (namespace, friendly method name) if known.
- * :param layer: The layer this TLObject belongs to.
- */
-class TLObject {
-    constructor(fullname, objectId, args, result, isFunction, usability, friendly, layer) {
-        // The name can or not have a namespace
-        this.fullname = fullname
-
-        if (fullname.includes('.')) {
-            [this.namespace, this.name] = fullname.split(/\.(.+)/)
-            // console.log(fullname.split(/\.(.+)/));
-            // const [namespace, ...name] = fullname.split('.');
-            // this.namespace = namespace;
-            // this.name = name.join('.');
-        } else {
-            this.namespace = null
-            this.name = fullname
-        }
-
-        this.args = args
-        this.result = result
-        this.isFunction = isFunction
-        this.usability = usability
-        this.friendly = friendly
-        this.id = null
-
-        if (!objectId) {
-            this.id = this.inferId()
-        } else {
-            this.id = parseInt(objectId, 16)
-
-            const whitelist = new Set([
-                ...WHITELISTED_MISMATCHING_IDS[0],
-                ...(WHITELISTED_MISMATCHING_IDS[layer] || []),
-            ])
-
-            if (!whitelist.has(this.fullname)) {
-                if (this.id !== this.inferId()) {
-                    throw new Error(`Invalid inferred ID for ${this.repr()}`)
-                }
-            }
-        }
-
-        this.className = snakeToCamelCase(this.name, this.isFunction ? 'Request' : '')
-
-        this.realArgs = this.sortedArgs().filter((a) => !(a.flagIndicator || a.genericDefinition))
-    }
-
-    get innermostResult() {
-        const index = this.result.indexOf('<')
-        return index === -1 ? this.result : this.result.slice(index + 1, -1)
-    }
-
-    /**
-     * Returns the arguments properly sorted and ready to plug-in
-     * into a Python's method header (i.e., flags and those which
-     * can be inferred will go last so they can default =None)
-     */
-    sortedArgs() {
-        return this.args.sort((x) => x.isFlag || x.canBeInferred)
-    }
-
-    repr(ignoreId) {
-        let hexId
-        let args
-
-        if (this.id === null || ignoreId) {
-            hexId = ''
-        } else {
-            hexId = `#${this.id.toString(16).padStart(8, '0')}`
-        }
-
-        if (this.args.length) {
-            args = ` ${this.args.map((arg) => arg.toString()).join(' ')}`
-        } else {
-            args = ''
-        }
-
-        return `${this.fullname}${hexId}${args} = ${this.result}`
-    }
-
-    inferId() {
-        const representation = this.repr(true)
-            .replace(/(:|\?)bytes /g, '$1string ')
-            .replace(/</g, ' ')
-            .replace(/>|{|}/g, '')
-            .replace(/ \w+:flags\.\d+\?true/g, '')
-
-        if (this.fullname === 'inputMediaInvoice') {
-            // eslint-disable-next-line no-empty
-            if (this.fullname === 'inputMediaInvoice') {
-            }
-        }
-
-        return crc32(Buffer.from(representation, 'utf8'))
-    }
-
-    toJson() {
-        return {
-            id: struct.unpack('i', struct.pack('I', this.id))[0].toString(),
-            [this.isFunction ? 'method' : 'predicate']: this.fullname,
-            param: this.args.filter((x) => !x.genericDefinition).map((x) => x.toJson()),
-            type: this.result,
-        }
-    }
-
-    isGoodExample() {
-        return !this.className.endsWith('Empty')
-    }
-
-    asExample(f, indent) {
-        f.write('<strong>new</strong> ')
-        f.write(this.isFunction ? 'functions' : 'types')
-
-        if (this.namespace) {
-            f.write('.')
-            f.write(this.namespace)
-        }
-
-        f.write('.')
-        f.write(this.className)
-        f.write('(')
-
-        const args = this.realArgs.filter((arg) => !arg.omitExample())
-
-        if (!args.length) {
-            f.write(')')
-            return
-        }
-
-        f.write('\n')
-        indent++
-        let remaining = args.length
-
-        for (const arg of args) {
-            remaining--
-            f.write('    '.repeat(indent))
-            f.write(arg.name)
-            f.write('=')
-
-            if (arg.isVector) {
-                f.write('[')
-            }
-
-            arg.asExample(f, indent || 0)
-
-            if (arg.isVector) {
-                f.write(']')
-            }
-
-            if (remaining) {
-                f.write(',')
-            }
-
-            f.write('\n')
-        }
-
-        indent--
-        f.write('    '.repeat(indent))
-        f.write(')')
-    }
-}
-
-module.exports = {
-    TLObject,
-}

+ 0 - 77
gramjs_generator/sourcebuilder.js

@@ -1,77 +0,0 @@
-const util = require('util')
-
-/**
- * This class should be used to build .py source files
- */
-class SourceBuilder {
-    constructor(stream, indentSize) {
-        this.currentIndent = 0
-        this.onNewLine = false
-        this.indentSize = indentSize || 4
-        this.stream = stream
-
-        // Was a new line added automatically before? If so, avoid it
-        this.autoAddedLine = false
-    }
-
-    /**
-     * Indents the current source code line
-     * by the current indentation level
-     */
-    indent() {
-        this.write(' '.repeat(Math.abs(this.currentIndent * this.indentSize)))
-    }
-
-    /**
-     * Writes a string into the source code,
-     * applying indentation if required
-     */
-    write(string, ...args) {
-        if (this.onNewLine) {
-            this.onNewLine = false // We're not on a new line anymore
-
-            // If the string was not empty, indent; Else probably a new line
-            if (string.trim()) {
-                this.indent()
-            }
-        }
-
-        if (args.length) {
-            this.stream.write(util.format(string, ...args))
-        } else {
-            this.stream.write(string)
-        }
-    }
-
-    /**
-     * Writes a string into the source code _and_ appends a new line,
-     * applying indentation if required
-     */
-    writeln(string, ...args) {
-        this.write(`${string || ''}\n`, ...args)
-        this.onNewLine = true
-
-        // If we're writing a block, increment indent for the next time
-        if (string && string.endsWith('{')) {
-            this.currentIndent++
-        }
-
-        // Clear state after the user adds a new line
-        this.autoAddedLine = false
-    }
-
-    /**
-     * Ends an indentation block, leaving an empty line afterwards
-     */
-    endBlock(semiColon = false) {
-        this.currentIndent--
-
-        // If we did not add a new line automatically yet, now it's the time!
-        if (!this.autoAddedLine) {
-            this.writeln('}%s', semiColon ? ';' : '')
-            this.autoAddedLine = true
-        }
-    }
-}
-
-module.exports = SourceBuilder

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff