Browse Source

refactor most code to TS

painor 4 years ago
parent
commit
00d5d83f0e
100 changed files with 5971 additions and 3677 deletions
  1. 1 1
      .bablerc
  2. 2 2
      .eslintignore
  3. 2 2
      .npmignore
  4. 0 0
      Helpers.ts
  5. 0 13
      examples/main.js
  6. 22 0
      examples/main.ts
  7. 0 351
      gramjs/Helpers.js
  8. 394 0
      gramjs/Helpers.ts
  9. 79 70
      gramjs/Password.ts
  10. 196 197
      gramjs/Utils.ts
  11. 0 1
      gramjs/Version.js
  12. 1 0
      gramjs/Version.ts
  13. 4 18
      gramjs/client/TelegramClient--old.js
  14. 0 22
      gramjs/client/TelegramClient.d.ts
  15. 60 0
      gramjs/client/TelegramClient.ts
  16. 15 0
      gramjs/client/account.ts
  17. 270 237
      gramjs/client/auth.ts
  18. 33 0
      gramjs/client/bots.ts
  19. 78 0
      gramjs/client/buttons.ts
  20. 340 0
      gramjs/client/chats.ts
  21. 15 0
      gramjs/client/dialogs.ts
  22. 1 1
      gramjs/client/downloadFile.ts
  23. 3 0
      gramjs/client/downloads.ts
  24. 156 0
      gramjs/client/messageParse.ts
  25. 480 0
      gramjs/client/messages.ts
  26. 367 0
      gramjs/client/telegramBaseClient.ts
  27. 143 0
      gramjs/client/updates.ts
  28. 1 5
      gramjs/client/uploadFile.ts
  29. 3 0
      gramjs/client/uploads.ts
  30. 380 0
      gramjs/client/users.ts
  31. 0 74
      gramjs/crypto/AuthKey.js
  32. 80 0
      gramjs/crypto/AuthKey.ts
  33. 6 6
      gramjs/crypto/CTR.ts
  34. 0 79
      gramjs/crypto/Factorizator.js
  35. 78 0
      gramjs/crypto/Factorizator.ts
  36. 10 8
      gramjs/crypto/IGE.ts
  37. 17 21
      gramjs/crypto/RSA.ts
  38. 0 123
      gramjs/crypto/crypto.js
  39. 128 0
      gramjs/crypto/crypto.ts
  40. 36 0
      gramjs/define.d.ts
  41. 72 0
      gramjs/entityCache.ts
  42. 43 42
      gramjs/errors/Common.ts
  43. 31 40
      gramjs/errors/RPCBaseErrors.ts
  44. 52 48
      gramjs/errors/RPCErrorList.ts
  45. 11 12
      gramjs/errors/index.ts
  46. 0 22
      gramjs/events/common.js
  47. 27 0
      gramjs/events/common.ts
  48. 0 30
      gramjs/extensions/AsyncQueue.js
  49. 37 0
      gramjs/extensions/AsyncQueue.ts
  50. 47 48
      gramjs/extensions/BinaryReader.ts
  51. 5 5
      gramjs/extensions/BinaryWriter.ts
  52. 20 21
      gramjs/extensions/Logger.ts
  53. 0 96
      gramjs/extensions/MessagePacker.js
  54. 107 0
      gramjs/extensions/MessagePacker.ts
  55. 0 117
      gramjs/extensions/PromisedNetSockets.js
  56. 135 0
      gramjs/extensions/PromisedNetSockets.ts
  57. 0 130
      gramjs/extensions/PromisedWebSockets.js
  58. 149 0
      gramjs/extensions/PromisedWebSockets.ts
  59. 0 14
      gramjs/extensions/index.js
  60. 7 0
      gramjs/extensions/index.ts
  61. 87 0
      gramjs/extensions/markdown.ts
  62. 1 1
      gramjs/index.d.ts
  63. 0 16
      gramjs/index.js
  64. 11 0
      gramjs/index.ts
  65. 0 179
      gramjs/network/Authenticator.js
  66. 181 0
      gramjs/network/Authenticator.ts
  67. 14 14
      gramjs/network/MTProtoPlainSender.ts
  68. 274 237
      gramjs/network/MTProtoSender.ts
  69. 115 87
      gramjs/network/MTProtoState.ts
  70. 0 16
      gramjs/network/RequestState.js
  71. 30 0
      gramjs/network/RequestState.ts
  72. 62 47
      gramjs/network/connection/Connection.ts
  73. 0 51
      gramjs/network/connection/TCPAbridged.js
  74. 51 0
      gramjs/network/connection/TCPAbridged.ts
  75. 0 56
      gramjs/network/connection/TCPFull.js
  76. 55 0
      gramjs/network/connection/TCPFull.ts
  77. 37 38
      gramjs/network/connection/TCPObfuscated.ts
  78. 0 11
      gramjs/network/connection/index.js
  79. 4 0
      gramjs/network/connection/index.ts
  80. 0 32
      gramjs/network/index.js
  81. 30 0
      gramjs/network/index.ts
  82. 114 0
      gramjs/requestIter.ts
  83. 12 12
      gramjs/sessions/Abstract.ts
  84. 0 256
      gramjs/sessions/Memory.js
  85. 267 0
      gramjs/sessions/Memory.ts
  86. 21 14
      gramjs/sessions/StringSession.ts
  87. 0 9
      gramjs/sessions/index.js
  88. 5 0
      gramjs/sessions/index.ts
  89. 0 19
      gramjs/tl/AllTLObjects.js
  90. 16 0
      gramjs/tl/AllTLObjects.ts
  91. 0 42
      gramjs/tl/MTProtoRequest.js
  92. 53 0
      gramjs/tl/MTProtoRequest.ts
  93. 213 514
      gramjs/tl/api.d.ts
  94. 56 49
      gramjs/tl/api.js
  95. 0 57
      gramjs/tl/core/GZIPPacked.js
  96. 59 0
      gramjs/tl/core/GZIPPacked.ts
  97. 20 17
      gramjs/tl/core/MessageContainer.ts
  98. 0 33
      gramjs/tl/core/RPCResult.js
  99. 39 0
      gramjs/tl/core/RPCResult.ts
  100. 0 14
      gramjs/tl/core/TLMessage.js

+ 1 - 1
.bablerc

@@ -12,7 +12,7 @@
   ],
   "plugins": [
     [
-      "@babel/plugin-proposal-class-properties"
+      "@babel/plugin-proposal-class-properties",
     ]
   ]
 }

+ 2 - 2
.eslintignore

@@ -2,7 +2,7 @@ gramjs_generator/data/html/js/search.js
 gramjs/tl/functions
 gramjs/tl/patched
 gramjs/tl/types
-gramjs/tl/AllTLObjects.js
-gramjs/errors/RPCErrorList.js
+gramjs/tl/AllTLObjects.ts
+gramjs/errors/RPCErrorList.ts
 browser/
 example.js

+ 2 - 2
.npmignore

@@ -6,8 +6,8 @@
 /gramjs/tl/functions/
 /gramjs/tl/types/
 /gramjs/tl/patched/
-/gramjs/tl/AllTLObjects.js
-/gramjs/errors/RPCErrorList.js
+/gramjs/tl/AllTLObjects.ts
+/gramjs/errors/RPCErrorList.ts
 /dist/
 
 # User session

+ 0 - 0
Helpers.ts


+ 0 - 13
examples/main.js

@@ -1,13 +0,0 @@
-const { TelegramClient } = require('../gramjs');
-const { StringSession } = require('../gramjs').sessions;
-
-
-(async () => {
-    console.log('Loading interactive example...')
-    const sessionName = 'anon'
-    const apiId = -1 // put your api id here [for example 123456789]
-    const apiHash = "" // put your api hash here [for example '123456abcfghe']
-    const client = new TelegramClient(new StringSession(''), apiId, apiHash)
-    await client.connect()
-    console.log('You should now be connected.')
-})()

+ 22 - 0
examples/main.ts

@@ -0,0 +1,22 @@
+import {TelegramClient} from "../gramjs";
+import {StringSession} from "../gramjs/sessions";
+
+
+(async () => {
+    console.log('Loading interactive example...');
+    const apiId = -1; // put your api id here [for example 123456789]
+    const apiHash = ""; // put your api hash here [for example '123456abcfghe']
+    const client = new TelegramClient(new StringSession(''), apiId, apiHash, {
+        connectionRetries: 3,
+    });
+    await client.start({
+        botAuthToken:'YOUR BOT TOKEN'
+
+    });
+    console.log('You should now be connected.')
+    console.log(await client.getMe());
+    // USE THIS STRING TO AVOID RELOGGING EACH TIME
+    console.log(await client.session.save());
+
+
+})();

+ 0 - 351
gramjs/Helpers.js

@@ -1,351 +0,0 @@
-const { isBrowser, isNode } = require('browser-or-node' )
-
-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.BigInteger}
- */
-function readBigIntFromBuffer(buffer, little = true, signed = false) {
-    let randBuffer = Buffer.from(buffer)
-    const bytesNumber = randBuffer.length
-    if (little) {
-        randBuffer = randBuffer.reverse()
-    }
-    let bigInt = BigInt(randBuffer.toString('hex'), 16)
-    if (signed && Math.floor(bigInt.toString('2').length / 8) >= bytesNumber) {
-        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 {BigInteger}
- * @param bytesNumber
- * @param little
- * @param signed
- * @returns {Buffer}
- */
-function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) {
-    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.lesser(BigInt(0))) {
-        throw new Error('Cannot convert to unsigned')
-    }
-    let below = false
-    if (bigInt.lesser(BigInt(0))) {
-        below = true
-        bigInt = bigInt.abs()
-    }
-
-    const hex = bigInt.toString('16')
-        .padStart(bytesNumber * 2, '0')
-    let l = Buffer.from(hex, 'hex')
-    if (little) {
-        l = l.reverse()
-    }
-
-    if (signed && below) {
-        if (little) {
-            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 {
-            l[l.length - 1] = 256 - l[l.length - 1]
-            for (let i = 0; i < l.length - 1; i++) {
-                l[i] = 255 - l[i]
-            }
-        }
-    }
-    return l
-}
-
-/**
- * Generates a random long integer (8 bytes), which is optionally signed
- * @returns {BigInteger}
- */
-function generateRandomLong(signed = true) {
-    return readBigIntFromBuffer(generateRandomBytes(8), true, signed)
-}
-
-/**
- * .... really javascript
- * @param n {number}
- * @param m {number}
- * @returns {number}
- */
-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 Buffer.from(crypto.randomBytes(count))
-}
-
-/**
- * Calculate the key based on Telegram guidelines, specifying whether it's the client or not
- * @param sharedKey
- * @param msgKey
- * @param client
- * @returns {{iv: Buffer, key: Buffer}}
- */
-/*CONTEST
-this is mtproto 1 (mostly used for secret chats)
-async function calcKey(sharedKey, msgKey, client) {
-    const x = client === true ? 0 : 8
-    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
-    }
-}
-
- */
-
-/**
- * Generates the key data corresponding to the given nonces
- * @param serverNonce
- * @param newNonce
- * @returns {{key: Buffer, iv: Buffer}}
- */
-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,
-    }
-}
-
-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 {Promise}
- */
-function sha1(data) {
-    const shaSum = crypto.createHash('sha1')
-    shaSum.update(data)
-    return shaSum.digest()
-}
-
-
-/**
- * Calculates the SHA256 digest for the given data
- * @param data
- * @returns {Promise}
- */
-function sha256(data) {
-    const shaSum = crypto.createHash('sha256')
-    shaSum.update(data)
-    return shaSum.digest()
-}
-
-/**
- * Fast mod pow for RSA calculation. a^b % n
- * @param a
- * @param b
- * @param n
- * @returns {bigInt.BigInteger}
- */
-function modExp(a, b, n) {
-    a = a.remainder(n)
-    let result = BigInt.one
-    let x = a
-    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.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
- * @param max
- * @returns {number}
- */
-function getRandomInt(min, max) {
-    min = Math.ceil(min)
-    max = Math.floor(max)
-    return Math.floor(Math.random() * (max - min + 1)) + min
-}
-
-/**
- * Sleeps a specified amount of time
- * @param ms time in milliseconds
- * @returns {Promise}
- */
-const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
-
-/**
- * Checks if the obj is an array
- * @param obj
- * @returns {boolean}
- */
-/*
-CONTEST
-we do'nt support array requests anyway
-function isArrayLike(obj) {
-    if (!obj) return false
-    const l = obj.length
-    if (typeof l != 'number' || l < 0) return false
-    if (Math.floor(l) !== l) return false
-    // fast check
-    if (l > 0 && !(l - 1 in obj)) return false
-    // more complete check (optional)
-    for (let i = 0; i < l; ++i) {
-        if (!(i in obj)) return false
-    }
-    return true
-}
-*/
-// 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))
-        }
-        crcTable[n] = c
-    }
-    return crcTable
-}
-
-let crcTable = null
-
-function crc32(buf) {
-    if (!crcTable) {
-        crcTable = makeCRCTable()
-    }
-    if (!Buffer.isBuffer(buf)) {
-        buf = Buffer.from(buf)
-    }
-    let crc = -1
-
-    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 = {
-    readBigIntFromBuffer,
-    readBufferFromBigInt,
-    generateRandomLong,
-    mod,
-    crc32,
-    generateRandomBytes,
-    //calcKey,
-    generateKeyDataFromNonce,
-    sha1,
-    sha256,
-    bigIntMod,
-    modExp,
-    getRandomInt,
-    sleep,
-    getByteArray,
-    //isArrayLike,
-    toSignedLittleBuffer,
-    convertToLittle,
-    IS_NODE,
-}

+ 394 - 0
gramjs/Helpers.ts

@@ -0,0 +1,394 @@
+import {isNode} from 'browser-or-node';
+import bigInt from "big-integer";
+import {EntityLike} from "./define";
+
+export const IS_NODE = isNode;
+const crypto = require(isNode ? 'crypto' : './crypto/crypto');
+
+/**
+ * converts a buffer to big int
+ * @param buffer
+ * @param little
+ * @param signed
+ * @returns {bigInt.BigInteger}
+ */
+export function readBigIntFromBuffer(buffer: Buffer, little = true, signed = false): bigInt.BigInteger {
+    let randBuffer = Buffer.from(buffer);
+    const bytesNumber = randBuffer.length;
+    if (little) {
+        randBuffer = randBuffer.reverse()
+    }
+    let bigIntVar = bigInt(randBuffer.toString('hex'), 16) as bigInt.BigInteger;
+
+    if (signed && Math.floor(bigIntVar.toString(2).length / 8) >= bytesNumber) {
+        bigIntVar = bigIntVar.subtract(bigInt(2)
+            .pow(bigInt(bytesNumber * 8)));
+    }
+    return bigIntVar;
+}
+
+export function generateRandomBigInt() {
+    return readBigIntFromBuffer(generateRandomBytes(8), false);
+}
+
+export function escapeRegex(string: string) {
+    return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+}
+
+/*
+export function addSurrogate(text: string) {
+    let temp = "";
+    for (const letter of text) {
+        const t = letter.charCodeAt(0);
+        if (0x1000 < t && t < 0x10FFFF) {
+            const b = Buffer.from(letter, "utf16le");
+            const r = String.fromCharCode(b.readUInt16LE(0)) + String.fromCharCode(b.readUInt16LE(2));
+            temp += r;
+        } else {
+            text += letter;
+        }
+    }
+    return temp;
+}
+
+ */
+
+/**
+ * Special case signed little ints
+ * @param big
+ * @param number
+ * @returns {Buffer}
+ */
+export function toSignedLittleBuffer(big: bigInt.BigInteger, number = 8): Buffer {
+    const bigNumber = bigInt(big);
+    const byteArray = [];
+    for (let i = 0; i < number; i++) {
+        byteArray[i] = bigNumber.shiftRight(8 * i).and(255);
+    }
+    // smh hacks
+    return Buffer.from(byteArray as unknown as number[]);
+}
+
+
+/**
+ * converts a big int to a buffer
+ * @param bigIntVar {BigInteger}
+ * @param bytesNumber
+ * @param little
+ * @param signed
+ * @returns {Buffer}
+ */
+export function readBufferFromBigInt(bigIntVar: bigInt.BigInteger, bytesNumber: number, little = true, signed = false): Buffer {
+    bigIntVar = bigInt(bigIntVar);
+    const bitLength = bigIntVar.bitLength().toJSNumber();
+
+    const bytes = Math.ceil(bitLength / 8);
+    if (bytesNumber < bytes) {
+        throw new Error('OverflowError: int too big to convert')
+    }
+    if (!signed && bigIntVar.lesser(BigInt(0))) {
+        throw new Error('Cannot convert to unsigned')
+    }
+    let below = false;
+    if (bigIntVar.lesser(BigInt(0))) {
+        below = true;
+        bigIntVar = bigIntVar.abs();
+    }
+
+    const hex = bigIntVar.toString(16)
+        .padStart(bytesNumber * 2, '0');
+    let littleBuffer = Buffer.from(hex, 'hex');
+    if (little) {
+        littleBuffer = littleBuffer.reverse();
+    }
+
+    if (signed && below) {
+        if (little) {
+            let reminder = false;
+            if (littleBuffer[0] !== 0) {
+                littleBuffer[0] -= 1;
+            }
+            for (let i = 0; i < littleBuffer.length; i++) {
+                if (littleBuffer[i] === 0) {
+                    reminder = true;
+                    continue;
+                }
+                if (reminder) {
+                    littleBuffer[i] -= 1;
+                    reminder = false;
+                }
+                littleBuffer[i] = 255 - littleBuffer[i]
+            }
+        } else {
+            littleBuffer[littleBuffer.length - 1] = 256 - littleBuffer[littleBuffer.length - 1];
+            for (let i = 0; i < littleBuffer.length - 1; i++) {
+                littleBuffer[i] = 255 - littleBuffer[i];
+            }
+        }
+    }
+    return littleBuffer;
+}
+
+/**
+ * Generates a random long integer (8 bytes), which is optionally signed
+ * @returns {BigInteger}
+ */
+export function generateRandomLong(signed = true) {
+    return readBigIntFromBuffer(generateRandomBytes(8), true, signed)
+}
+
+/**
+ * .... really javascript
+ * @param n {number}
+ * @param m {number}
+ * @returns {number}
+ */
+export function mod(n: number, m: number) {
+    return ((n % m) + m) % m
+}
+
+/**
+ * returns a positive bigInt
+ * @param n {BigInt}
+ * @param m {BigInt}
+ * @returns {BigInt}
+ */
+export function bigIntMod(n: bigInt.BigInteger, m: bigInt.BigInteger): bigInt.BigInteger {
+    return ((n.remainder(m)).add(m)).remainder(m)
+}
+
+/**
+ * Generates a random bytes array
+ * @param count
+ * @returns {Buffer}
+ */
+export function generateRandomBytes(count: number) {
+    return Buffer.from(crypto.randomBytes(count))
+}
+
+/**
+ * Calculate the key based on Telegram guidelines, specifying whether it's the client or not
+ * @param sharedKey
+ * @param msgKey
+ * @param client
+ * @returns {{iv: Buffer, key: Buffer}}
+ */
+
+/*CONTEST
+this is mtproto 1 (mostly used for secret chats)
+async function calcKey(sharedKey, msgKey, client) {
+    const x = client === true ? 0 : 8
+    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
+    }
+}
+
+ */
+
+/**
+ * Generates the key data corresponding to the given nonces
+ * @param serverNonceBigInt
+ * @param newNonceBigInt
+ * @returns {{key: Buffer, iv: Buffer}}
+ */
+export async function generateKeyDataFromNonce(serverNonceBigInt: bigInt.BigInteger, newNonceBigInt: bigInt.BigInteger) {
+
+    const serverNonce = toSignedLittleBuffer(serverNonceBigInt, 16);
+    const newNonce = toSignedLittleBuffer(newNonceBigInt, 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,
+    }
+}
+
+export function convertToLittle(buf: Buffer) {
+    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 {Promise}
+ */
+export function sha1(data: Buffer): Promise<Buffer> {
+    const shaSum = crypto.createHash('sha1');
+    shaSum.update(data);
+    return shaSum.digest()
+}
+
+
+/**
+ * Calculates the SHA256 digest for the given data
+ * @param data
+ * @returns {Promise}
+ */
+export function sha256(data: Buffer): Promise<Buffer> {
+    const shaSum = crypto.createHash('sha256');
+    shaSum.update(data);
+    return shaSum.digest()
+}
+
+/**
+ * Fast mod pow for RSA calculation. a^b % n
+ * @param a
+ * @param b
+ * @param n
+ * @returns {bigInt.BigInteger}
+ */
+export function modExp(a: bigInt.BigInteger, b: bigInt.BigInteger, n: bigInt.BigInteger): bigInt.BigInteger {
+    a = a.remainder(n);
+    let result = bigInt.one;
+    let x = a;
+    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.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}
+ */
+export function getByteArray(integer: bigInt.BigInteger | number, signed = false) {
+    const bits = integer.toString(2).length;
+    const byteLength = Math.floor((bits + 8 - 1) / 8);
+    return readBufferFromBigInt(typeof integer == "number" ? bigInt(integer) : integer, byteLength, false, signed);
+}
+
+/**
+ * returns a random int from min (inclusive) and max (inclusive)
+ * @param min
+ * @param max
+ * @returns {number}
+ */
+export function getRandomInt(min: number, max: number) {
+    min = Math.ceil(min);
+    max = Math.floor(max);
+    return Math.floor(Math.random() * (max - min + 1)) + min;
+}
+
+/**
+ * Sleeps a specified amount of time
+ * @param ms time in milliseconds
+ * @returns {Promise}
+ */
+export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
+
+/**
+ * Checks if the obj is an array
+ * @param obj
+ * @returns {boolean}
+ */
+
+// 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))
+        }
+        crcTable[n] = c
+    }
+    return crcTable
+}
+
+let crcTable: number[] | undefined = undefined;
+
+export function crc32(buf: Buffer | string) {
+    if (!crcTable) {
+        crcTable = makeCRCTable()
+    }
+    if (!Buffer.isBuffer(buf)) {
+        buf = Buffer.from(buf)
+    }
+    let crc = -1;
+
+    for (let index = 0; index < buf.length; index++) {
+        const byte = buf[index];
+        crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8)
+    }
+    return (crc ^ (-1)) >>> 0
+}
+
+export class TotalList extends Array {
+    public total?: number;
+
+    constructor() {
+        super();
+        this.total = 0
+    }
+}
+
+
+export const _EntityType = {
+    USER: 0,
+    CHAT: 1,
+    CHANNEL: 2,
+};
+Object.freeze(_EntityType);
+
+export function _entityType(entity: EntityLike) {
+
+    if (typeof entity !== 'object' || !('SUBCLASS_OF_ID' in entity)) {
+        throw new Error(`${entity} is not a TLObject, cannot determine entity type`)
+    }
+    if (![
+        0x2d45687,  // crc32('Peer')
+        0xc91c90b6,  // crc32('InputPeer')
+        0xe669bf46,  // crc32('InputUser')
+        0x40f202fd,  // crc32('InputChannel')
+        0x2da17977,  // crc32('User')
+        0xc5af5d94,  // crc32('Chat')
+        0x1f4661b9,  // crc32('UserFull')
+        0xd49a2697,  // crc32('ChatFull')
+    ].includes(entity.SUBCLASS_OF_ID)) {
+        throw new Error(`${entity} does not have any entity type`)
+    }
+    const name = entity.className;
+    if (name.includes('User')) {
+        return _EntityType.USER
+    } else if (name.includes('Chat')) {
+        return _EntityType.CHAT
+    } else if (name.includes('Channel')) {
+        return _EntityType.CHANNEL
+    } else if (name.includes('Self')) {
+        return _EntityType.USER
+    }
+    // 'Empty' in name or not found, we don't care, not a valid entity.
+    throw new Error(`${entity} does not have any entity type`)
+
+}
+

+ 79 - 70
gramjs/Password.js → gramjs/Password.ts

@@ -1,10 +1,8 @@
-const BigInt = require('big-integer')
-const Factorizator = require('./crypto/Factorizator')
-const { constructors } = require('./tl')
-const { readBigIntFromBuffer, readBufferFromBigInt, sha256, bigIntMod, modExp,
-    generateRandomBytes } = require('./Helpers')
-const crypto = require('./crypto/crypto')
-const SIZE_FOR_HASH = 256
+import {Api} from "./tl";
+import {bigIntMod, generateRandomBytes, modExp, readBigIntFromBuffer, readBufferFromBigInt, sha256} from "./Helpers";
+import bigInt from 'big-integer';
+
+const SIZE_FOR_HASH = 256;
 
 /**
  *
@@ -12,6 +10,7 @@ const SIZE_FOR_HASH = 256
  * @param prime{BigInteger}
  * @param g{BigInteger}
  */
+
 /*
 We don't support changing passwords yet
 function checkPrimeAndGoodCheck(prime, g) {
@@ -62,7 +61,7 @@ function checkPrimeAndGoodCheck(prime, g) {
  * @param primeBytes{Buffer}
  * @param g{number}
  */
-function checkPrimeAndGood(primeBytes, g) {
+function checkPrimeAndGood(primeBytes: Buffer, g: number) {
     const goodPrime = Buffer.from([
         0xC7, 0x1C, 0xAE, 0xB9, 0xC6, 0xB1, 0xC9, 0x04, 0x8E, 0x6C, 0x52, 0x2F, 0x70, 0xF1, 0x3F, 0x73,
         0x98, 0x0D, 0x40, 0x23, 0x8E, 0x3E, 0x21, 0xC1, 0x49, 0x34, 0xD0, 0x37, 0x56, 0x3D, 0x93, 0x0F,
@@ -80,9 +79,9 @@ function checkPrimeAndGood(primeBytes, g) {
         0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F,
         0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF,
         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
         }
     }
@@ -96,7 +95,7 @@ function checkPrimeAndGood(primeBytes, g) {
  * @param p{BigInteger}
  * @returns {boolean}
  */
-function isGoodLarge(number, p) {
+function isGoodLarge(number: bigInt.BigInteger, p: bigInt.BigInteger) {
     return (number.greater(BigInt(0)) && (p.subtract(number).greater(BigInt(0))))
 }
 
@@ -105,16 +104,16 @@ function isGoodLarge(number, p) {
  * @param number {Buffer}
  * @returns {Buffer}
  */
-function numBytesForHash(number) {
-    return Buffer.concat([ Buffer.alloc(SIZE_FOR_HASH - number.length), number ])
+function numBytesForHash(number: Buffer) {
+    return Buffer.concat([Buffer.alloc(SIZE_FOR_HASH - number.length), number])
 }
 
 /**
  *
- * @param g {Buffer}
+ * @param g {bigInt}
  * @returns {Buffer}
  */
-function bigNumForHash(g) {
+function bigNumForHash(g: bigInt.BigInteger) {
     return readBufferFromBigInt(g, SIZE_FOR_HASH, false)
 }
 
@@ -124,19 +123,19 @@ function bigNumForHash(g) {
  * @param prime {BigInteger}
  * @returns {Boolean}
  */
-function isGoodModExpFirst(modexp, prime) {
-    const diff = prime.subtract(modexp)
+function isGoodModExpFirst(modexp: bigInt.BigInteger, prime: bigInt.BigInteger) {
+    const diff = prime.subtract(modexp);
 
-    const minDiffBitsCount = 2048 - 64
-    const maxModExpSize = 256
+    const minDiffBitsCount = 2048 - 64;
+    const maxModExpSize = 256;
 
-    return !(diff.lesser(BigInt(0)) || diff.bitLength() < minDiffBitsCount ||
-        modexp.bitLength() < minDiffBitsCount ||
-        Math.floor((modexp.bitLength() + 7) / 8) > maxModExpSize)
+    return !(diff.lesser(BigInt(0)) || diff.bitLength().toJSNumber() < minDiffBitsCount ||
+        modexp.bitLength().toJSNumber() < minDiffBitsCount ||
+        Math.floor((modexp.bitLength().toJSNumber() + 7) / 8) > maxModExpSize)
 }
 
-function xor(a, b) {
-    const length = Math.min(a.length, b.length)
+function xor(a: Buffer, b: Buffer) {
+    const length = Math.min(a.length, b.length);
 
     for (let i = 0; i < length; i++) {
         a[i] = a[i] ^ b[i]
@@ -153,7 +152,9 @@ function xor(a, b) {
  * @returns {*}
  */
 
-function pbkdf2sha512(password, salt, iterations) {
+function pbkdf2sha512(password: Buffer, salt: Buffer, iterations: number) {
+
+    // @ts-ignore should probably add pollifier
     return crypto.pbkdf2(password, salt, iterations, 64, 'sha512')
 }
 
@@ -163,11 +164,11 @@ function pbkdf2sha512(password, salt, iterations) {
  * @param password
  * @returns {Buffer|*}
  */
-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 ]))
+async function computeHash(algo: Api.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, password: string) {
+    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]))
 }
 
 /**
@@ -175,16 +176,16 @@ async function computeHash(algo, password) {
  * @param algo {constructors.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow}
  * @param password
  */
-async function computeDigest(algo, password) {
+async function computeDigest(algo: Api.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, password: string) {
     try {
         checkPrimeAndGood(algo.p, algo.g)
     } catch (e) {
         throw new Error('bad p/g in password')
     }
 
-    const value = modExp(BigInt(algo.g),
+    const value = modExp(bigInt(algo.g),
         readBigIntFromBuffer(await computeHash(algo, password), false),
-        readBigIntFromBuffer(algo.p, false))
+        readBigIntFromBuffer(algo.p, false));
     return bigNumForHash(value)
 }
 
@@ -193,16 +194,20 @@ async function computeDigest(algo, password) {
  * @param request {constructors.account.Password}
  * @param password {string}
  */
-async function computeCheck(request, password) {
-    const algo = request.currentAlgo
-    if (!(algo instanceof constructors.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)) {
-        throw new Error(`Unsupported password algorithm ${algo.className}`)
+async function computeCheck(request: Api.account.Password, password: string) {
+    const algo = request.currentAlgo;
+    if (!(algo instanceof Api.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)) {
+        throw new Error(`Unsupported password algorithm ${algo?.className}`)
     }
-
-    const pwHash = await computeHash(algo, password)
-    const p = readBigIntFromBuffer(algo.p, false)
-    const g = algo.g
-    const B = readBigIntFromBuffer(request.srp_B, false)
+    const srp_B = request.srp_B;
+    const srpId = request.srpId;
+    if (!srp_B || !srpId) {
+        throw new Error(`Undefined srp_b  ${request}`)
+    }
+    const pwHash = await computeHash(algo, password);
+    const p = readBigIntFromBuffer(algo.p, false);
+    const g = algo.g;
+    const B = readBigIntFromBuffer(srp_B, false);
     try {
         checkPrimeAndGood(algo.p, g)
     } catch (e) {
@@ -211,64 +216,68 @@ async function computeCheck(request, password) {
     if (!isGoodLarge(B, p)) {
         throw new Error('bad b in check')
     }
-    const x = readBigIntFromBuffer(pwHash, false)
-    const pForHash = numBytesForHash(algo.p)
-    const gForHash = bigNumForHash(g)
-    const bForHash = numBytesForHash(request.srp_B)
-    const gX = modExp(BigInt(g), x, p)
-    const k = readBigIntFromBuffer(await sha256(Buffer.concat([ pForHash, gForHash ])), false)
-    const kgX = bigIntMod(k.multiply(gX),p)
-    const generateAndCheckRandom =async () => {
-        const randomSize = 256
+    const x = readBigIntFromBuffer(pwHash, false);
+    const pForHash = numBytesForHash(algo.p);
+    const gForHash = bigNumForHash(bigInt(g));
+    const bForHash = numBytesForHash(srp_B);
+    const gX = modExp(bigInt(g), x, p);
+    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) {
-            const random = generateRandomBytes(randomSize)
-            const a = readBigIntFromBuffer(random, false)
-            const A = modExp(BigInt(g), a, p)
+            const random = generateRandomBytes(randomSize);
+            const a = readBigIntFromBuffer(random, false);
+            const A = modExp(bigInt(g), a, p);
             if (isGoodModExpFirst(A, p)) {
-                const aForHash = bigNumForHash(A)
-                const u = readBigIntFromBuffer(await sha256(Buffer.concat([ aForHash, bForHash ])), false)
+                const aForHash = bigNumForHash(A);
+                const u = readBigIntFromBuffer(await sha256(Buffer.concat([aForHash, bForHash])), false);
                 if (u.greater(BigInt(0))) {
-                    return [ a, aForHash, u ]
+                    return {
+                        a: a,
+                        aForHash: aForHash,
+                        u: u,
+                    };
                 }
             }
         }
-    }
-    const [ a, aForHash, u ] =await  generateAndCheckRandom()
-    const gB = bigIntMod(B.subtract(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.multiply(x)
-    const aUx = a.add(ux)
-    const S = modExp(gB, aUx, p)
-    const [K, pSha ,gSha, salt1Sha, salt2Sha] = await Promise.all([
+    const ux = u.multiply(x);
+    const aUx = a.add(ux);
+    const S = modExp(gB, aUx, p);
+    const [K, pSha, gSha, salt1Sha, salt2Sha] = await Promise.all([
         sha256(bigNumForHash(S)),
         sha256(pForHash),
         sha256(gForHash),
         sha256(algo.salt1),
         sha256(algo.salt2),
-    ])
+    ]);
     const M1 = await sha256(Buffer.concat([
-        xor(pSha,gSha),
+        xor(pSha, gSha),
         salt1Sha,
         salt2Sha,
         aForHash,
         bForHash,
         K,
-    ]))
+    ]));
 
 
-    return new constructors.InputCheckPasswordSRP({
-        srpId: request.srpId,
+    return new Api.InputCheckPasswordSRP({
+        srpId: srpId,
         A: Buffer.from(aForHash),
         M1: M1,
 
     })
 }
 
-module.exports = {
+export {
     computeCheck,
     computeDigest,
 }

+ 196 - 197
gramjs/Utils.js → gramjs/Utils.ts

@@ -1,20 +1,28 @@
-const {IS_NODE} = require('./Helpers')
-const { constructors } = require('./tl')
-const { requests } = require('./tl')
+import {Entity, EntityLike, FileLike} from "./define";
+import {Api} from "./tl";
+import bigInt from "big-integer";
+import TypeInputPeer = Api.TypeInputPeer;
+import TypeMessageEntity = Api.TypeMessageEntity;
+import * as markdown from "./extensions/markdown"
+import {EntityCache} from "./entityCache";
 
 const USERNAME_RE = new RegExp('@|(?:https?:\\/\\/)?(?:www\\.)?' +
-    '(?:telegram\\.(?:me|dog)|t\\.me)\\/(@|joinchat\\/)?')
+    '(?:telegram\\.(?:me|dog)|t\\.me)\\/(@|joinchat\\/)?');
 
 const JPEG_HEADER = Buffer.from('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00', 'hex')
-const JPEG_FOOTER = Buffer.from('ffd9', 'hex')
+const JPEG_FOOTER = Buffer.from('ffd9', 'hex');
 
-const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite=')
+const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite=');
 
 const VALID_USERNAME_RE = new RegExp('^([a-z]((?!__)[\\w\\d]){3,30}[a-z\\d]|gif|vid|' +
-    'pic|bing|wiki|imdb|bold|vote|like|coub)$')
+    'pic|bing|wiki|imdb|bold|vote|like|coub)$');
 
-function _raiseCastFail(entity, target) {
-    throw new Error(`Cannot cast ${entity.className} to any kind of ${target}`)
+function _raiseCastFail(entity: EntityLike, target: any): never {
+    let toWrite = entity;
+    if (typeof entity === 'object') {
+        toWrite = entity.className;
+    }
+    throw new Error(`Cannot cast ${toWrite} to any kind of ${target}`)
 }
 
 /**
@@ -34,12 +42,11 @@ function _raiseCastFail(entity, target) {
  * @param allowSelf
  * @param checkHash
  */
-function getInputPeer(entity, allowSelf = true, checkHash = true) {
+export function getInputPeer(entity: any, allowSelf = true, checkHash = true): TypeInputPeer {
     if (entity.SUBCLASS_OF_ID === undefined) {
         // e.g. custom.Dialog (can't cyclic import).
-
         if (allowSelf && 'inputEntity' in entity) {
-            return entity.inputEntity
+            return entity.inputEntity;
         } else if ('entity' in entity) {
             return getInputPeer(entity.entity)
         } else {
@@ -50,69 +57,113 @@ function getInputPeer(entity, allowSelf = true, checkHash = true) {
         return entity
     }
 
-    if (entity instanceof constructors.User) {
-        if (entity.isSelf && allowSelf) {
-            return new constructors.InputPeerSelf()
-        } else if ((entity.accessHash !== undefined && !entity.min) || !checkHash) {
-            return new constructors.InputPeerUser({
+    if (entity instanceof Api.User) {
+        if (entity.self && allowSelf) {
+            return new Api.InputPeerSelf();
+        } else if (entity.accessHash !== undefined || !checkHash) {
+            return new Api.InputPeerUser({
                 userId: entity.id,
-                accessHash: entity.accessHash,
+                accessHash: entity.accessHash || bigInt(0),
             })
         } else {
-            throw new Error('User without accessHash or min info cannot be input')
+            throw new Error('User without accessHash cannot be input')
         }
     }
-    if (entity instanceof constructors.Chat || entity instanceof constructors.ChatEmpty ||
-        entity instanceof constructors.ChatForbidden) {
-        return new constructors.InputPeerChat({ chatId: entity.id })
+    if (entity instanceof Api.Chat || entity instanceof Api.ChatEmpty ||
+        entity instanceof Api.ChatForbidden) {
+        return new Api.InputPeerChat({chatId: entity.id})
     }
-    if (entity instanceof constructors.Channel) {
-        if ((entity.accessHash !== undefined && !entity.min) || !checkHash) {
-            return new constructors.InputPeerChannel({
+    if (entity instanceof Api.Channel) {
+        if (entity.accessHash !== undefined || !checkHash) {
+            return new Api.InputPeerChannel({
                 channelId: entity.id,
-                accessHash: entity.accessHash,
+                accessHash: entity.accessHash || bigInt(0),
             })
         } else {
             throw new TypeError('Channel without accessHash or min info cannot be input')
         }
     }
-    if (entity instanceof constructors.ChannelForbidden) {
+    if (entity instanceof Api.ChannelForbidden) {
         // "channelForbidden are never min", and since their hash is
         // also not optional, we assume that this truly is the case.
-        return new constructors.InputPeerChannel({
+        return new Api.InputPeerChannel({
             channelId: entity.id,
             accessHash: entity.accessHash,
         })
     }
 
-    if (entity instanceof constructors.InputUser) {
-        return new constructors.InputPeerUser({
+    if (entity instanceof Api.InputUser) {
+        return new Api.InputPeerUser({
             userId: entity.userId,
             accessHash: entity.accessHash,
         })
     }
-    if (entity instanceof constructors.InputChannel) {
-        return new constructors.InputPeerChannel({
+    if (entity instanceof Api.InputChannel) {
+        return new Api.InputPeerChannel({
             channelId: entity.channelId,
             accessHash: entity.accessHash,
         })
     }
-    if (entity instanceof constructors.UserEmpty) {
-        return new constructors.InputPeerEmpty()
+    if (entity instanceof Api.UserEmpty) {
+        return new Api.InputPeerEmpty()
     }
-    if (entity instanceof constructors.UserFull) {
+    if (entity instanceof Api.UserFull) {
         return getInputPeer(entity.user)
     }
 
-    if (entity instanceof constructors.ChatFull) {
-        return new constructors.InputPeerChat({ chatId: entity.id })
+    if (entity instanceof Api.ChatFull) {
+        return new Api.InputPeerChat({chatId: entity.id})
     }
 
-    if (entity instanceof constructors.PeerChat) {
-        return new constructors.InputPeerChat(entity.chatId)
+    if (entity instanceof Api.PeerChat) {
+        return new Api.InputPeerChat({
+            chatId: entity.chatId
+        })
     }
 
     _raiseCastFail(entity, 'InputPeer')
+
+}
+
+export function _photoSizeByteCount(size: FileLike) {
+    if (size instanceof Api.PhotoSize) {
+        return size.size
+    } else if (size instanceof Api.PhotoStrippedSize) {
+        if (size.bytes.length < 3 || size.bytes[0] != 1) {
+            return size.bytes.length;
+        }
+        return size.bytes.length + 622
+    } else if (size instanceof Api.PhotoCachedSize) {
+        return size.bytes.length;
+    } else if (size instanceof Api.PhotoSizeEmpty) {
+        return 0
+    } else {
+        return undefined;
+
+    }
+}
+
+export function _getEntityPair(entityId: number, entities: Map<number, Entity>,
+                               cache: EntityCache,
+                               getInputPeerFunction: any = getInputPeer): [Entity?, EntityLike?] {
+    const entity = entities.get(entityId);
+    let inputEntity = cache.get(entityId);
+    if (!inputEntity) {
+        inputEntity = getInputPeerFunction(inputEntity);
+    }
+    return [entity, inputEntity]
+
+}
+
+export function getInnerText(text: string, entities: Map<number, TypeMessageEntity>) {
+
+    const result: string[] = [];
+    entities.forEach(function (value, key) {
+        const start = value.offset;
+        const end = value.offset + value.length;
+        result.push(text.slice(start, end));
+    });
+    return result;
 }
 
 /**
@@ -137,15 +188,15 @@ function getInputChannel(entity) {
     if (entity.SUBCLASS_OF_ID === 0x40f202fd) { // crc32(b'InputChannel')
         return entity
     }
-    if (entity instanceof constructors.Channel || entity instanceof constructors.ChannelForbidden) {
-        return new constructors.InputChannel({
+    if (entity instanceof Api.Channel || entity instanceof Api.ChannelForbidden) {
+        return new Api.InputChannel({
             channelId: entity.id,
             accessHash: entity.accessHash || 0
         })
     }
 
-    if (entity instanceof constructors.InputPeerChannel) {
-        return new constructors.InputChannel({
+    if (entity instanceof Api.InputPeerChannel) {
+        return new Api.InputChannel({
             channelId: entity.channelId,
             accessHash: entity.accessHash
         })
@@ -173,29 +224,29 @@ function getInputUser(entity) {
         return entity
     }
 
-    if (entity instanceof constructors.User) {
+    if (entity instanceof Api.User) {
         if (entity.isSelf) {
-            return new constructors.InputPeerSelf()
+            return new Api.InputPeerSelf()
         } else {
-            return new constructors.InputUser({
+            return new Api.InputUser({
                 userId: entity.id,
                 accessHash: entity.accessHash || 0,
             })
         }
     }
-    if (entity instanceof constructors.InputPeerSelf) {
-        return new constructors.InputPeerSelf()
+    if (entity instanceof Api.InputPeerSelf) {
+        return new Api.InputPeerSelf()
     }
-    if (entity instanceof constructors.UserEmpty || entity instanceof constructors.InputPeerEmpty) {
-        return new constructors.InputUserEmpty()
+    if (entity instanceof Api.UserEmpty || entity instanceof Api.InputPeerEmpty) {
+        return new Api.InputUserEmpty()
     }
 
-    if (entity instanceof constructors.UserFull) {
+    if (entity instanceof Api.UserFull) {
         return getInputUser(entity.user)
     }
 
-    if (entity instanceof constructors.InputPeerUser) {
-        return new constructors.InputUser({
+    if (entity instanceof Api.InputPeerUser) {
+        return new Api.InputUser({
             userId: entity.userId,
             accessHash: entity.accessHash
         })
@@ -215,14 +266,14 @@ function getInputDialog(dialog) {
             return dialog
         }
         if (dialog.SUBCLASS_OF_ID === 0xc91c90b6) { // crc32(b'InputPeer')
-            return new constructors.InputDialogPeer({ peer: dialog })
+            return new Api.InputDialogPeer({ peer: dialog })
         }
     } catch (e) {
         _raiseCastFail(dialog, 'InputDialogPeer')
     }
 
     try {
-        return new constructors.InputDialogPeer(getInputPeer(dialog))
+        return new Api.InputDialogPeer(getInputPeer(dialog))
         // eslint-disable-next-line no-empty
     } catch (e) {
 
@@ -230,18 +281,19 @@ function getInputDialog(dialog) {
     _raiseCastFail(dialog, 'InputDialogPeer')
 }
 */
+
 /*CONTEST
 
 function getInputMessage(message) {
     try {
         if (typeof message == 'number') { // This case is really common too
-            return new constructors.InputMessageID({
+            return new Api.InputMessageID({
                 id: message,
             })
         } else if (message.SUBCLASS_OF_ID === 0x54b6bcc5) { // crc32(b'InputMessage')
             return message
         } else if (message.SUBCLASS_OF_ID === 0x790009e3) { // crc32(b'Message')
-            return new constructors.InputMessageID(message.id)
+            return new Api.InputMessageID(message.id)
         }
         // eslint-disable-next-line no-empty
     } catch (e) {
@@ -258,7 +310,7 @@ function getInputMessage(message) {
  * @param stripped{Buffer}
  * @returns {Buffer}
  */
-function strippedPhotoToJpg(stripped) {
+function strippedPhotoToJpg(stripped: Buffer) {
     // Note: Changes here should update _stripped_real_length
     if (stripped.length < 3 || stripped[0] !== 1) {
         return stripped
@@ -284,30 +336,30 @@ function getInputLocation(location) {
     } catch (e) {
         _raiseCastFail(location, 'InputFileLocation')
     }
-    if (location instanceof constructors.Message) {
+    if (location instanceof Api.Message) {
         location = location.media
     }
 
-    if (location instanceof constructors.MessageMediaDocument) {
+    if (location instanceof Api.MessageMediaDocument) {
         location = location.document
-    } else if (location instanceof constructors.MessageMediaPhoto) {
+    } else if (location instanceof Api.MessageMediaPhoto) {
         location = location.photo
     }
 
-    if (location instanceof constructors.Document) {
+    if (location instanceof Api.Document) {
         return {
             dcId: location.dcId,
-            inputLocation: new constructors.InputDocumentFileLocation({
+            inputLocation: new Api.InputDocumentFileLocation({
                 id: location.id,
                 accessHash: location.accessHash,
                 fileReference: location.fileReference,
                 thumbSize: '', // Presumably to download one of its thumbnails
             }),
         }
-    } else if (location instanceof constructors.Photo) {
+    } else if (location instanceof Api.Photo) {
         return {
             dcId: location.dcId,
-            inputLocation: new constructors.InputPhotoFileLocation({
+            inputLocation: new Api.InputPhotoFileLocation({
                 id: location.id,
                 accessHash: location.accessHash,
                 fileReference: location.fileReference,
@@ -316,7 +368,7 @@ function getInputLocation(location) {
         }
     }
 
-    if (location instanceof constructors.FileLocationToBeDeprecated) {
+    if (location instanceof Api.FileLocationToBeDeprecated) {
         throw new Error('Unavailable location cannot be used as input')
     }
     _raiseCastFail(location, 'InputFileLocation')
@@ -329,7 +381,7 @@ function getInputLocation(location) {
  * @param fileSize
  * @returns {Number}
  */
-function getAppropriatedPartSize(fileSize) {
+function getAppropriatedPartSize(fileSize: number) {
     if (fileSize <= 104857600) { // 100MB
         return 128
     }
@@ -343,44 +395,51 @@ function getAppropriatedPartSize(fileSize) {
     throw new Error('File size too large')
 }
 
-/*CONTEST
-function getPeer(peer) {
+export function getPeer(peer: EntityLike) {
+    if (!peer) {
+        _raiseCastFail(peer, 'undefined')
+
+    }
+    if (typeof peer === 'string') {
+        _raiseCastFail(peer, 'peer')
+    }
     try {
         if (typeof peer === 'number') {
             const res = resolveId(peer)
-
-            if (res[1] === constructors.PeerChannel) {
-                return new res[1]({ channelId: res[0] })
-            } else if (res[1] === constructors.PeerChat) {
-                return new res[1]({ chatId: res[0] })
+            if (res[1] === Api.PeerChannel) {
+                return new Api.PeerChannel({channelId: res[0]})
+            } else if (res[1] === Api.PeerChat) {
+                return new Api.PeerChat({chatId: res[0]})
             } else {
-                return new res[1]({ userId: res[0] })
+                return new Api.PeerUser({userId: res[0]})
             }
         }
         if (peer.SUBCLASS_OF_ID === undefined) {
             throw new Error()
         }
-        if (peer.SUBCLASS_OF_ID === 0x2d45687) {
+        if (peer.SUBCLASS_OF_ID === 0x2d45687) { // crc32('Peer')
             return peer
-        } else if (peer instanceof constructors.contacts.ResolvedPeer ||
-            peer instanceof constructors.InputNotifyPeer || peer instanceof constructors.TopPeer ||
-            peer instanceof constructors.Dialog || peer instanceof constructors.DialogPeer) {
+        } else if (peer instanceof Api.contacts.ResolvedPeer ||
+            peer instanceof Api.InputNotifyPeer || peer instanceof Api.TopPeer ||
+            peer instanceof Api.Dialog || peer instanceof Api.DialogPeer) {
             return peer.peer
-        } else if (peer instanceof constructors.ChannelFull) {
-            return new constructors.PeerChannel({ channelId: peer.id })
+        } else if (peer instanceof Api.ChannelFull) {
+            return new Api.PeerChannel({channelId: peer.id})
         }
         if (peer.SUBCLASS_OF_ID === 0x7d7c6f86 || peer.SUBCLASS_OF_ID === 0xd9c7fc18) {
             // ChatParticipant, ChannelParticipant
-            return new constructors.PeerUser({ userId: peer.userId })
+            if ("userId" in peer) {
+                return new Api.PeerUser({userId: peer.userId})
+            }
         }
         peer = getInputPeer(peer, false, false)
 
-        if (peer instanceof constructors.InputPeerUser) {
-            return new constructors.PeerUser({ userId: peer.userId })
-        } else if (peer instanceof constructors.InputPeerChat) {
-            return new constructors.PeerChat({ chatId: peer.chatId })
-        } else if (peer instanceof constructors.InputPeerChannel) {
-            return new constructors.PeerChannel({ channelId: peer.channelId })
+        if (peer instanceof Api.InputPeerUser) {
+            return new Api.PeerUser({userId: peer.userId})
+        } else if (peer instanceof Api.InputPeerChat) {
+            return new Api.PeerChat({chatId: peer.chatId})
+        } else if (peer instanceof Api.InputPeerChannel) {
+            return new Api.PeerChannel({channelId: peer.channelId})
         }
         // eslint-disable-next-line no-empty
     } catch (e) {
@@ -388,7 +447,18 @@ function getPeer(peer) {
     }
     _raiseCastFail(peer, 'peer')
 }
-*/
+
+export const isArrayLike = (<T>(x: any): x is ArrayLike<T> => x && typeof x.length === 'number' && typeof x !== 'function');
+
+export function sanitizeParseMode(mode: string) {
+    if (!mode) {
+        return null;
+    }
+    if (mode === "md" || mode === "markdown") {
+        return markdown;
+    }
+    throw new Error(`Invalid parse mode type ${mode}`);
+}
 
 /**
  Convert the given peer into its marked ID by default.
@@ -406,15 +476,14 @@ function getPeer(peer) {
  * @param peer
  * @param addMark
  */
-/*CONTEST
-function getPeerId(peer, addMark = true) {
+export function getPeerId(peer: EntityLike, addMark = true): number {
     // First we assert it's a Peer TLObject, or early return for integers
     if (typeof peer == 'number') {
         return addMark ? peer : resolveId(peer)[0]
     }
 
     // Tell the user to use their client to resolve InputPeerSelf if we got one
-    if (peer instanceof constructors.InputPeerSelf) {
+    if (peer instanceof Api.InputPeerSelf) {
         _raiseCastFail(peer, 'int (you might want to use client.get_peer_id)')
     }
 
@@ -423,18 +492,18 @@ function getPeerId(peer, addMark = true) {
     } catch (e) {
         _raiseCastFail(peer, 'int')
     }
-    if (peer instanceof constructors.PeerUser) {
+    if (peer instanceof Api.PeerUser) {
         return peer.userId
-    } else if (peer instanceof constructors.PeerChat) {
+    } else if (peer instanceof Api.PeerChat) {
         // Check in case the user mixed things up to avoid blowing up
-        if (!(0 < peer.chatId <= 0x7fffffff)) {
+        if (!(0 < peer.chatId && peer.chatId <= 0x7fffffff)) {
             peer.chatId = resolveId(peer.chatId)[0]
         }
 
         return addMark ? -(peer.chatId) : peer.chatId
-    } else { // if (peer instanceof constructors.PeerChannel)
+    } else if ("channelId" in peer) { // if (peer instanceof Api.PeerChannel)
         // Check in case the user mixed things up to avoid blowing up
-        if (!(0 < peer.channelId <= 0x7fffffff)) {
+        if (!(0 < peer.channelId && peer.channelId <= 0x7fffffff)) {
             peer.channelId = resolveId(peer.channelId)[0]
         }
         if (!addMark) {
@@ -442,22 +511,19 @@ function getPeerId(peer, addMark = true) {
         }
         // Concat -100 through math tricks, .to_supergroup() on
         // Madeline IDs will be strictly positive -> log works.
-        try {
-            return -(peer.channelId + Math.pow(10, Math.floor(Math.log10(peer.channelId) + 3)))
-        } catch (e) {
-            throw new Error('Cannot get marked ID of a channel unless its ID is strictly positive')
-        }
+        return -(1000000000000 + peer.channelId);
     }
+    _raiseCastFail(peer, 'int')
+
 }
-*/
+
 /**
  * Given a marked ID, returns the original ID and its :tl:`Peer` type.
  * @param markedId
  */
-/*CONTEST
-function resolveId(markedId) {
+export function resolveId(markedId: number): [number, typeof Api.PeerUser | typeof Api.PeerChannel | typeof Api.PeerChat] {
     if (markedId >= 0) {
-        return [markedId, constructors.PeerUser]
+        return [markedId, Api.PeerUser]
     }
 
     // There have been report of chat IDs being 10000xyz, which means their
@@ -467,11 +533,10 @@ function resolveId(markedId) {
     const m = markedId.toString()
         .match(/-100([^0]\d*)/)
     if (m) {
-        return [parseInt(m[1]), constructors.PeerChannel]
+        return [parseInt(m[1]), Api.PeerChannel]
     }
-    return [-markedId, constructors.PeerChat]
+    return [-markedId, Api.PeerChat]
 }
-*/
 
 /**
  * returns an entity pair
@@ -482,9 +547,10 @@ function resolveId(markedId) {
  * @returns {{inputEntity: *, entity: *}}
  * @private
  */
+
 /*CONTEST
 
-function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer) {
+export function  _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer) {
     const entity = entities.get(entityId)
     let inputEntity = cache[entityId]
     if (inputEntity === undefined) {
@@ -501,13 +567,14 @@ function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer)
 }
 */
 
-function getMessageId(message) {
+export function getMessageId(message: any): number | undefined {
     if (message === null || message === undefined) {
-        return null
+        return undefined;
     }
     if (typeof message == 'number') {
         return message
     }
+
     if (message.SUBCLASS_OF_ID === 0x790009e3) { // crc32(b'Message')
         return message.id
     }
@@ -519,8 +586,14 @@ function getMessageId(message) {
  * Parses the given phone, or returns `None` if it's invalid.
  * @param phone
  */
-function parsePhone(phone) {
-    return phone.toString().replace(/[+()\s-]/gm, '')
+export function parsePhone(phone: string | number) {
+    phone = phone.toString().replace(/[+()\s-]/gm, '');
+
+    return !isNaN(parseInt(phone)) ? phone : undefined;
+}
+
+export function resolveInviteLink(link: string): [number, number, number] {
+    throw new Error("not implemented");
 }
 
 /**
@@ -533,9 +606,9 @@ function parsePhone(phone) {
 
  * @param username {string}
  */
-/*CONTEST
 
-function parseUsername(username) {
+
+export function parseUsername(username: string): { username?: string, isInvite: boolean } {
     username = username.trim()
     const m = username.match(USERNAME_RE) || username.match(TG_JOIN_RE)
     if (m) {
@@ -556,28 +629,27 @@ function parseUsername(username) {
         }
     } else {
         return {
-            username: null,
+            username: undefined,
             isInvite: false
         }
     }
 }
 
-function rtrim(s, mask) {
+export function rtrim(s: string, mask: string) {
     while (~mask.indexOf(s[s.length - 1])) {
         s = s.slice(0, -1)
     }
     return s
 }
 
- */
 
 /**
  * Gets the display name for the given :tl:`User`,
  :tl:`Chat` or :tl:`Channel`. Returns an empty string otherwise
  * @param entity
  */
-function getDisplayName(entity) {
-    if (entity instanceof constructors.User) {
+export function getDisplayName(entity: EntityLike) {
+    if (entity instanceof Api.User) {
         if (entity.lastName && entity.firstName) {
             return `${entity.firstName} ${entity.lastName}`
         } else if (entity.firstName) {
@@ -587,7 +659,7 @@ function getDisplayName(entity) {
         } else {
             return ''
         }
-    } else if (entity instanceof constructors.Chat || entity instanceof constructors.Channel) {
+    } else if (entity instanceof Api.Chat || entity instanceof Api.Channel) {
         return entity.title
     }
     return ''
@@ -598,9 +670,10 @@ function getDisplayName(entity) {
  * @param item
  * @returns {boolean}
  */
+
 /*CONTEST
 Duplicate ?
-function isListLike(item) {
+export function  isListLike(item) {
     return (
         Array.isArray(item) ||
         (!!item &&
@@ -614,78 +687,4 @@ function isListLike(item) {
     )
 }
 */
-async function getDC(dcId,client, cdn = false) {
-    if (!IS_NODE){
-        switch (dcId) {
-        case 1:
-            return {
-                id: 1,
-                ipAddress: IS_NODE?'':'pluto.web.telegram.org',
-                port: 443,
-            }
-        case 2:
-            return {
-                id: 2,
-                ipAddress: 'venus.web.telegram.org',
-                port: 443,
-            }
-        case 3:
-            return {
-                id: 3,
-                ipAddress: 'aurora.web.telegram.org',
-                port: 443,
-            }
-        case 4:
-            return {
-                id: 4,
-                ipAddress: 'vesta.web.telegram.org',
-                port: 443,
-            }
-        case 5:
-            return {
-                id: 5,
-                ipAddress: 'flora.web.telegram.org',
-                port: 443,
-            }
-        default:
-            throw new Error(`Cannot find the DC with the ID of ${dcId}`)
-        }
-    }
-    if (!client._config) {
-        client._config = await client.invoke(new requests.help.GetConfig())
-    }
-    if (cdn) {
-        throw new Error('CDNs are Not supported')
-    }
-
-    for (const DC of client._config.dcOptions) {
-        if (DC.id === dcId && Boolean(DC.ipv6) === client._useIPV6 && Boolean(DC.cdn) === cdn) {
-            return {
-                id:DC.id,
-                ipAddress:DC.ipAddress,
-                port:443,
-            }
-        }
-    }
-}
 
-module.exports = {
-    getMessageId,
-    //_getEntityPair,
-    //getInputMessage,
-    //getInputDialog,
-    //getInputUser,
-    //getInputChannel,
-    getInputPeer,
-    //parsePhone,
-    //parseUsername,
-    //getPeer,
-    //getPeerId,
-    getDisplayName,
-    //resolveId,
-    //isListLike,
-    getAppropriatedPartSize,
-    //getInputLocation,
-    strippedPhotoToJpg,
-    getDC,
-}

+ 0 - 1
gramjs/Version.js

@@ -1 +0,0 @@
-module.exports = '0.0.2'

+ 1 - 0
gramjs/Version.ts

@@ -0,0 +1 @@
+export const version = "0.0.5";

+ 4 - 18
gramjs/client/TelegramClient.js → gramjs/client/TelegramClient--old.js

@@ -36,7 +36,7 @@ class TelegramClient {
         retryDelay: 1000,
         autoReconnect: true,
         sequentialUpdates: false,
-        floodSleepLimit: 60,
+        floodSleepThreshold: 60,
         deviceModel: null,
         systemVersion: null,
         appVersion: null,
@@ -74,7 +74,7 @@ class TelegramClient {
             throw new Error('The given session must be str or a session instance')
         }
 
-        this.floodSleepLimit = args.floodSleepLimit
+        this.floodSleepThreshold = args.floodSleepThreshold
         this._eventBuilders = []
 
         this._phoneCodeHash = {}
@@ -564,6 +564,7 @@ class TelegramClient {
             try {
                 const promise = this._sender.send(request)
                 const result = await promise
+
                 //this.session.processEntities(result)
                 this._entityCache.add(result)
                 return result
@@ -573,7 +574,7 @@ class TelegramClient {
                     this._log.warn(`Telegram is having internal issues ${e.constructor.name}`)
                     await sleep(2000)
                 } else if (e instanceof errors.FloodWaitError || e instanceof errors.FloodTestPhoneWaitError) {
-                    if (e.seconds <= this.floodSleepLimit) {
+                    if (e.seconds <= this.floodSleepThreshold) {
                         this._log.info(`Sleeping for ${e.seconds}s on flood wait`)
                         await sleep(e.seconds * 1000)
                     } else {
@@ -603,22 +604,7 @@ class TelegramClient {
         }
     }
 
-    async start(authParams) {
-        if (!this.isConnected()) {
-            await this.connect()
-        }
-
-        if (await checkAuthorization(this)) {
-            return
-        }
-
-        const apiCredentials = {
-            apiId: this.apiId,
-            apiHash: this.apiHash,
-        }
 
-        await authFlow(this, apiCredentials, authParams)
-    }
 
     uploadFile(fileParams) {
         return uploadFile(this, fileParams)

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

@@ -1,22 +0,0 @@
-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;

+ 60 - 0
gramjs/client/TelegramClient.ts

@@ -0,0 +1,60 @@
+import {DownloadMethods} from "./downloads";
+import {DialogMethods} from "./dialogs";
+import {TelegramBaseClient, TelegramClientParams} from "./telegramBaseClient";
+import {ButtonMethods} from "./buttons";
+import {UpdateMethods} from "./updates";
+import {MessageMethods} from "./messages";
+import {MessageParseMethods} from "./messageParse";
+import {AuthMethods} from "./auth";
+import {UserMethods} from "./users";
+import {ChatMethods} from "./chats";
+import {Mixin} from "ts-mixer";
+import {Session} from "../sessions/Abstract";
+import {IS_NODE} from "../Helpers";
+import {ConnectionTCPFull, ConnectionTCPObfuscated} from "../network/connection";
+
+// TODO add uploads
+
+export class TelegramClient extends Mixin(AuthMethods, DownloadMethods, DialogMethods, ChatMethods,
+    MessageMethods, ButtonMethods, UpdateMethods,
+    MessageParseMethods, UserMethods, TelegramBaseClient) {
+    constructor(session: string | Session, apiId: number, apiHash: string, {
+        connection = IS_NODE ? ConnectionTCPFull : ConnectionTCPObfuscated,
+        useIPV6 = false,
+        timeout = 10,
+        requestRetries = 5,
+        connectionRetries = 5,
+        retryDelay = 1000,
+        autoReconnect = true,
+        sequentialUpdates = false,
+        floodSleepThreshold = 60,
+        deviceModel = '',
+        systemVersion = '',
+        appVersion = '',
+        langCode = 'en',
+        systemLangCode = 'en',
+        baseLogger = 'gramjs',
+        useWSS = false,
+    }: TelegramClientParams) {
+        super(session, apiId, apiHash, {
+            connection,
+            useIPV6,
+            timeout,
+            requestRetries,
+            connectionRetries,
+            retryDelay,
+            autoReconnect,
+            sequentialUpdates ,
+            floodSleepThreshold,
+            deviceModel,
+            systemVersion,
+            appVersion,
+            langCode,
+            systemLangCode,
+            baseLogger,
+            useWSS,
+        });
+    }
+
+
+}

+ 15 - 0
gramjs/client/account.ts

@@ -0,0 +1,15 @@
+import {AuthMethods} from "./auth";
+import {DownloadMethods} from "./downloads";
+import {DialogMethods} from "./dialogs";
+import {BotMethods} from "./bots";
+import {MessageMethods} from "./messages";
+import {ButtonMethods} from "./buttons";
+import {UpdateMethods} from "./updates";
+import {MessageParseMethods} from "./messageParse";
+import {UserMethods} from "./users";
+import {TelegramBaseClient} from "./telegramBaseClient";
+import {ChatMethods} from "./chats";
+
+export class AccountMethods {
+
+}

+ 270 - 237
gramjs/client/auth.ts

@@ -1,11 +1,19 @@
-import  { default as Api }  from '../tl/api';
-import TelegramClient from './TelegramClient';
-// @ts-ignore
+import {Api} from '../tl';
 import * as utils from '../Utils';
-// @ts-ignore
-import { sleep } from '../Helpers';
-// @ts-ignore
-import { computeCheck as computePasswordSrpCheck } from '../Password';
+import {sleep} from '../Helpers';
+import {computeCheck as computePasswordSrpCheck} from '../Password';
+import {TelegramClient} from "../index";
+import {AccountMethods} from "./account";
+import {DownloadMethods} from "./downloads";
+import {DialogMethods} from "./dialogs";
+import {ChatMethods} from "./chats";
+import {BotMethods} from "./bots";
+import {MessageMethods} from "./messages";
+import {ButtonMethods} from "./buttons";
+import {UpdateMethods} from "./updates";
+import {MessageParseMethods} from "./messageParse";
+import {UserMethods} from "./users";
+import {TelegramBaseClient} from "./telegramBaseClient";
 
 export interface UserAuthParams {
     phoneNumber: string | (() => Promise<string>);
@@ -16,6 +24,7 @@ export interface UserAuthParams {
     onError: (err: Error) => void;
     forceSMS?: boolean;
 }
+
 interface ReturnString {
     (): string
 }
@@ -31,302 +40,326 @@ interface ApiCredentials {
 
 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 class AuthMethods {
 
-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;
+    // region public methods
 
-    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);
-                    }
+    async start(authParams: UserAuthParams | BotAuthParams) {
+        if (!this.connected) {
+            await this.connect();
+        }
 
-                    throw err;
-                }
-            } else {
-                phoneNumber = authParams.phoneNumber;
-            }
-            const sendCodeResult = await sendCode(client, apiCredentials, phoneNumber, authParams.forceSMS);
-            phoneCodeHash = sendCodeResult.phoneCodeHash;
-            isCodeViaApp = sendCodeResult.isCodeViaApp;
+        if (await this.checkAuthorization()) {
+            return;
+        }
 
-            if (typeof phoneCodeHash !== 'string') {
-                throw new Error('Failed to retrieve phone code hash');
-            }
+        const apiCredentials = {
+            apiId: this.apiId,
+            apiHash: this.apiHash,
+        };
 
-            break;
-        } catch (err) {
-            if (typeof authParams.phoneNumber !== 'function') {
-                throw err;
-            }
+        await this.authFlow(apiCredentials, authParams);
+    }
 
-            authParams.onError(err);
+    async checkAuthorization() {
+        try {
+            await this.invoke(new Api.updates.GetState());
+            return true;
+        } catch (e) {
+            return false;
         }
     }
 
-    let phoneCode;
-    let isRegistrationRequired = false;
-    let termsOfService;
+    async signInUser(
+        apiCredentials: ApiCredentials, authParams: UserAuthParams,
+    ): Promise<Api.TypeUser> {
+        let phoneNumber;
+        let phoneCodeHash;
+        let isCodeViaApp = false;
 
-    while (1) {
-        try {
+        while (1) {
             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 (typeof authParams.phoneNumber === 'function') {
+                    try {
+                        phoneNumber = await authParams.phoneNumber();
+                    } catch (err) {
+                        if (err.message === 'RESTART_AUTH_WITH_QR') {
+                            return this.signInUserWithQrCode(apiCredentials, authParams);
+                        }
+
+                        throw err;
+                    }
+                } else {
+                    phoneNumber = authParams.phoneNumber;
                 }
-            }
-
-            if (!phoneCode) {
-                throw new Error('Code is empty');
-            }
+                const sendCodeResult = await this.sendCode(apiCredentials, phoneNumber, authParams.forceSMS);
+                phoneCodeHash = sendCodeResult.phoneCodeHash;
+                isCodeViaApp = sendCodeResult.isCodeViaApp;
 
-            // May raise PhoneCodeEmptyError, PhoneCodeExpiredError,
-            // PhoneCodeHashEmptyError or PhoneCodeInvalidError.
-            const result = await client.invoke(new Api.auth.SignIn({
-                phoneNumber,
-                phoneCodeHash,
-                phoneCode,
-            }));
+                if (typeof phoneCodeHash !== 'string') {
+                    throw new Error('Failed to retrieve phone code hash');
+                }
 
-            if (result instanceof Api.auth.AuthorizationSignUpRequired) {
-                isRegistrationRequired = true;
-                termsOfService = result.termsOfService;
                 break;
-            }
+            } catch (err) {
+                if (typeof authParams.phoneNumber !== 'function') {
+                    throw err;
+                }
 
-            return result.user;
-        } catch (err) {
-            if (err.message === 'SESSION_PASSWORD_NEEDED') {
-                return signInWithPassword(client, apiCredentials, authParams);
-            } else {
                 authParams.onError(err);
             }
         }
-    }
 
-    if (isRegistrationRequired) {
+        let phoneCode;
+        let isRegistrationRequired = false;
+        let termsOfService;
+
         while (1) {
             try {
-                const [firstName, lastName] = await authParams.firstAndLastNames();
-                if (!firstName) {
-                    throw new Error('First name is required');
+                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 this.signInUser(apiCredentials, authParams);
+                    }
+                }
+
+                if (!phoneCode) {
+                    throw new Error('Code is empty');
                 }
 
-                const { user } = await client.invoke(new Api.auth.SignUp({
+                // May raise PhoneCodeEmptyError, PhoneCodeExpiredError,
+                // PhoneCodeHashEmptyError or PhoneCodeInvalidError.
+                const result = await this.invoke(new Api.auth.SignIn({
                     phoneNumber,
                     phoneCodeHash,
-                    firstName,
-                    lastName,
-                })) as Api.auth.Authorization;
+                    phoneCode,
+                }));
 
-                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 }));
+                if (result instanceof Api.auth.AuthorizationSignUpRequired) {
+                    isRegistrationRequired = true;
+                    termsOfService = result.termsOfService;
+                    break;
                 }
 
-                return user;
+                return result.user;
             } catch (err) {
-                authParams.onError(err);
+                if (err.message === 'SESSION_PASSWORD_NEEDED') {
+                    return this.signInWithPassword(apiCredentials, authParams);
+                } else {
+                    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');
-            }
+        if (isRegistrationRequired) {
+            while (1) {
+                try {
+                    const [firstName, lastName] = await authParams.firstAndLastNames();
+                    if (!firstName) {
+                        throw new Error('First name is required');
+                    }
 
-            const { token, expires } = result;
+                    const {user} = await this.invoke(new Api.auth.SignUp({
+                        phoneNumber,
+                        phoneCodeHash,
+                        firstName,
+                        lastName,
+                    })) as Api.auth.Authorization;
 
-            await Promise.race([
-                authParams.qrCode({ token, expires }),
-                sleep(QR_CODE_TIMEOUT),
-            ]);
-        }
-    })();
+                    if (termsOfService) {
+                        // This is a violation of Telegram rules: the user should be presented with and accept TOS.
+                        await this.invoke(new Api.help.AcceptTermsOfService({id: termsOfService.id}));
+                    }
 
-    const updatePromise = new Promise((resolve) => {
-        client.addEventHandler((update: Api.TypeUpdate) => {
-            if (update instanceof Api.UpdateLoginToken) {
-                resolve();
+                    return user;
+                } catch (err) {
+                    authParams.onError(err);
+                }
             }
-        }, { build: (update: object) => update });
-    });
-
-    try {
-        await Promise.race([updatePromise, inputPromise]);
-    } catch (err) {
-        if (err.message === 'RESTART_AUTH') {
-            return signInUser(client, apiCredentials, authParams);
         }
 
-        throw err;
+        authParams.onError(new Error('Auth failed'));
+        return this.signInUser(apiCredentials, authParams);
     }
 
-    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,
-            }));
+    async signInUserWithQrCode(
+        apiCredentials: ApiCredentials, authParams: UserAuthParams,
+    ): Promise<Api.TypeUser> {
+        const inputPromise = (async () => {
+            while (1) {
+                const result = await this.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;
 
-            if (migratedResult instanceof Api.auth.LoginTokenSuccess && migratedResult.authorization instanceof Api.auth.Authorization) {
-                return migratedResult.authorization.user;
+                await Promise.race([
+                    authParams.qrCode({token, expires}),
+                    sleep(QR_CODE_TIMEOUT),
+                ]);
             }
-        }
-    } 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);
-}
+        const updatePromise = new Promise((resolve) => {
+            this.addEventHandler((update: Api.TypeUpdate) => {
+                if (update instanceof Api.UpdateLoginToken) {
+                    resolve();
+                }
+            });
+        });
 
-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(),
-        }));
+        try {
+            await Promise.race([updatePromise, inputPromise]);
+        } catch (err) {
+            if (err.message === 'RESTART_AUTH') {
+                return this.signInUser(apiCredentials, authParams);
+            }
 
-        // 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,
-            };
+            throw err;
         }
 
-        const resendResult = await client.invoke(new Api.auth.ResendCode({
-            phoneNumber,
-            phoneCodeHash: sendResult.phoneCodeHash,
-        }));
+        try {
+            const result2 = await this.invoke(new Api.auth.ExportLoginToken({
+                apiId: Number(process.env.TELEGRAM_T_API_ID),
+                apiHash: process.env.TELEGRAM_T_API_HASH,
+                exceptIds: [],
+            }));
 
-        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;
+            if (result2 instanceof Api.auth.LoginTokenSuccess && result2.authorization instanceof Api.auth.Authorization) {
+                return result2.authorization.user;
+            } else if (result2 instanceof Api.auth.LoginTokenMigrateTo) {
+                await this._switchDC(result2.dcId);
+                const migratedResult = await this.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 this.signInWithPassword(apiCredentials, authParams);
+            }
         }
+
+        authParams.onError(new Error('QR auth failed'));
+        return this.signInUser(apiCredentials, authParams);
     }
-}
 
-async function signInWithPassword(
-    client: TelegramClient, apiCredentials: ApiCredentials, authParams: UserAuthParams,
-): Promise<Api.TypeUser> {
-    while (1) {
+    async sendCode(apiCredentials: ApiCredentials, phoneNumber: string, forceSMS = false,
+    ): Promise<{
+        phoneCodeHash: string;
+        isCodeViaApp: boolean;
+    }> {
         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 {apiId, apiHash} = apiCredentials;
+            const sendResult = await this.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 passwordSrpCheck = await computePasswordSrpCheck(passwordSrpResult, password);
-            const { user } = await client.invoke(new Api.auth.CheckPassword({
-                password: passwordSrpCheck,
-            })) as Api.auth.Authorization;
+            const resendResult = await this.invoke(new Api.auth.ResendCode({
+                phoneNumber,
+                phoneCodeHash: sendResult.phoneCodeHash,
+            }));
 
-            return user;
+            return {
+                phoneCodeHash: resendResult.phoneCodeHash,
+                isCodeViaApp: resendResult.type instanceof Api.auth.SentCodeTypeApp,
+            };
         } catch (err) {
-            authParams.onError(err);
+            if (err.message === 'AUTH_RESTART') {
+                return this.sendCode(apiCredentials, phoneNumber, forceSMS);
+            } else {
+                throw err;
+            }
         }
     }
 
-    return undefined!; // Never reached (TypeScript fix)
-}
+    async signInWithPassword(apiCredentials: ApiCredentials, authParams: UserAuthParams,
+    ): Promise<Api.TypeUser> {
+        while (1) {
+            try {
+                const passwordSrpResult = await this.invoke(new Api.account.GetPassword());
+                const password = await authParams.password(passwordSrpResult.hint);
+                if (!password) {
+                    throw new Error('Password is empty');
+                }
 
-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');
+                const passwordSrpCheck = await computePasswordSrpCheck(passwordSrpResult, password);
+                const {user} = await this.invoke(new Api.auth.CheckPassword({
+                    password: passwordSrpCheck,
+                })) as Api.auth.Authorization;
+
+                return user;
+            } catch (err) {
+                authParams.onError(err);
+            }
+        }
+
+        return undefined!; // Never reached (TypeScript fix)
     }
-    if (typeof botAuthToken === "function") {
-        let token;
-        while (true){
-            token = await botAuthToken();
-            if (token){
-                botAuthToken = token;
-                break;
+
+    async signInBot(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 this.invoke(new Api.auth.ImportBotAuthorization({
+            apiId,
+            apiHash,
+            botAuthToken,
+        })) as Api.auth.Authorization;
+        return user;
+    }
+
+    async authFlow(
+        apiCredentials: ApiCredentials,
+        authParams: UserAuthParams | BotAuthParams,
+    ) {
+        const me = 'phoneNumber' in authParams
+            ? await this.signInUser(apiCredentials, authParams)
+            : await this.signInBot(apiCredentials, authParams);
+
+        // TODO @logger
+        this._log.info('Signed in successfully as ' + utils.getDisplayName(me));
     }
+}
 
-    console.dir(botAuthToken)
-    const { user } = await client.invoke(new Api.auth.ImportBotAuthorization({
-        apiId,
-        apiHash,
-        botAuthToken,
-    })) as Api.auth.Authorization;
-    return user;
+export interface AuthMethods extends UserMethods, UpdateMethods {
 }
+

+ 33 - 0
gramjs/client/bots.ts

@@ -0,0 +1,33 @@
+import {EntityLike} from "../define";
+import {Api} from "../tl";
+import {InlineResults} from "../tl/custom/inlineResults";
+import GetInlineBotResults = Api.messages.GetInlineBotResults;
+import {UserMethods} from "./users";
+
+export class BotMethods {
+    async inlineQuery(bot: EntityLike, query: string, entity?: Api.InputPeerSelf | null,
+                      offset?: string, geoPoint?: Api.GeoPoint): Promise<InlineResults<Api.messages.BotResults>> {
+        bot = await this.getInputEntity(bot);
+        let peer = new Api.InputPeerSelf();
+        if (entity) {
+            peer = await this.getInputEntity(entity);
+        }
+        const result = await this.invoke(new GetInlineBotResults({
+            bot: bot,
+            peer: peer,
+            query: query,
+            offset: offset || '',
+            geoPoint: geoPoint
+        }));
+        // @ts-ignore
+        return new InlineResults(this, result, entity = entity ? peer : undefined);
+    }
+}
+
+export interface BotMethods extends UserMethods {
+
+}
+
+{
+
+}

+ 78 - 0
gramjs/client/buttons.ts

@@ -0,0 +1,78 @@
+import {Api} from "../tl/api";
+import {MarkupLike} from "../define";
+import {Button} from "../tl/custom/button";
+import {MessageButton} from "../tl/custom/messageButton";
+import {isArrayLike} from "../Utils";
+
+export class ButtonMethods {
+     buildReplyMarkup(buttons: MarkupLike, inlineOnly: boolean = false): Api.TypeReplyMarkup | undefined {
+        if (buttons == undefined) {
+            return undefined;
+        }
+        if (buttons.SUBCLASS_OF_ID == 0xe2e10ef2) {
+            return buttons;
+        }
+        if (!isArrayLike(buttons)) {
+            buttons = [[buttons]];
+        } else if (!buttons || !isArrayLike(buttons[0])) {
+            buttons = [buttons];
+        }
+        let isInline = false;
+        let isNormal = false;
+        let resize = undefined;
+        const singleUse = false;
+        const selective = false;
+
+        const rows = [];
+        for (const row of buttons) {
+            const current = [];
+            for (let button of row) {
+                if (button instanceof Button) {
+                    if (button.resize != undefined) {
+                        resize = button.resize;
+                    }
+                    if (button.singleUse != undefined) {
+                        resize = button.singleUse;
+                    }
+                    if (button.selective != undefined) {
+                        resize = button.selective;
+                    }
+                    button = button.button;
+                } else if (button instanceof MessageButton) {
+                    button = button.button;
+                }
+                const inline = Button._isInline(button);
+                if (!isInline && inline) {
+                    isInline = true;
+                }
+                if (!isNormal && inline) {
+                    isNormal = false;
+                }
+                if (button.SUBCLASS_OF_ID == 0xbad74a3) {
+                    // 0xbad74a3 == crc32(b'KeyboardButton')
+                    current.push(button);
+                }
+            }
+            if (current) {
+                rows.push(new Api.KeyboardButtonRow({
+                    buttons: current,
+                }))
+            }
+        }
+        if (inlineOnly && isNormal) {
+            throw new Error('You cannot use non-inline buttons here');
+        } else if (isInline === isNormal && isNormal) {
+            throw new Error('You cannot mix inline with normal buttons');
+        } else if (isInline) {
+            return new Api.ReplyInlineMarkup({
+                rows: rows
+            })
+        }
+        return new Api.ReplyKeyboardMarkup(({
+            rows: rows,
+            resize: resize,
+            singleUse: singleUse,
+            selective: selective
+        }))
+    }
+}

+ 340 - 0
gramjs/client/chats.ts

@@ -0,0 +1,340 @@
+import {TelegramClient} from "./TelegramClient";
+import {EntitiesLike, Entity, EntityLike, ValueOf} from "../define";
+// @ts-ignore
+import {sleep, getMinBigInt} from '../Helpers';
+import {RequestIter} from "../requestIter";
+import {helpers, utils} from "../index";
+import {Api} from "../tl/api";
+import GetFullChannel = Api.channels.GetFullChannel;
+import AnyRequest = Api.AnyRequest;
+import SetTyping = Api.messages.SetTyping;
+import GetParticipants = Api.channels.GetParticipants;
+import ChannelParticipantsSearch = Api.ChannelParticipantsSearch;
+import GetFullChat = Api.messages.GetFullChat;
+import ChatParticipants = Api.ChatParticipants;
+import ChannelParticipantsNotModified = Api.channels.ChannelParticipantsNotModified;
+import ChannelAdminLogEventsFilter = Api.ChannelAdminLogEventsFilter;
+import GetAdminLog = Api.channels.GetAdminLog;
+import bigInt, {BigInteger} from "big-integer";
+import ChannelAdminLogEventActionEditMessage = Api.ChannelAdminLogEventActionEditMessage;
+import {AccountMethods} from "./account";
+import {AuthMethods} from "./auth";
+import {DownloadMethods} from "./downloads";
+import {DialogMethods} from "./dialogs";
+import {BotMethods} from "./bots";
+import {MessageMethods} from "./messages";
+import {ButtonMethods} from "./buttons";
+import {UpdateMethods} from "./updates";
+import {MessageParseMethods} from "./messageParse";
+import {UserMethods} from "./users";
+import {TelegramBaseClient} from "./telegramBaseClient";
+
+const _MAX_PARTICIPANTS_CHUNK_SIZE = 200;
+const _MAX_ADMIN_LOG_CHUNK_SIZE = 100;
+const _MAX_PROFILE_PHOTO_CHUNK_SIZE = 100;
+
+interface ChatActionInterface {
+    delay: number,
+    autoCancel: boolean,
+}
+
+class _ChatAction {
+    static _str_mapping = {
+        'typing': new Api.SendMessageTypingAction(),
+        'contact': new Api.SendMessageChooseContactAction(),
+        'game': new Api.SendMessageGamePlayAction(),
+        'location': new Api.SendMessageGeoLocationAction(),
+
+        'record-audio': new Api.SendMessageRecordAudioAction(),
+        'record-voice': new Api.SendMessageRecordAudioAction(),  //alias
+        'record-round': new Api.SendMessageRecordRoundAction(),
+        'record-video': new Api.SendMessageRecordVideoAction(),
+
+        'audio': new Api.SendMessageUploadAudioAction({progress: 1,}),
+        'voice': new Api.SendMessageUploadAudioAction({progress: 1,}),  // alias
+        'song': new Api.SendMessageUploadAudioAction({progress: 1,}), // alias
+        'round': new Api.SendMessageUploadRoundAction({progress: 1,}),
+        'video': new Api.SendMessageUploadVideoAction({progress: 1,}),
+
+        'photo': new Api.SendMessageUploadPhotoAction({progress: 1,}),
+        'document': new Api.SendMessageUploadDocumentAction({progress: 1,}),
+        'file': new Api.SendMessageUploadDocumentAction({progress: 1,}),  // alias
+
+        'cancel': new Api.SendMessageCancelAction()
+    };
+
+    private _client: TelegramClient;
+    private _chat: EntityLike;
+    private _action: ValueOf<typeof _ChatAction._str_mapping>;
+    private _delay: number;
+    private autoCancel: boolean;
+    private _request?: AnyRequest;
+    private _task: null;
+    private _running: boolean;
+
+    constructor(client: TelegramClient, chat: EntityLike, action: ValueOf<typeof _ChatAction._str_mapping>, params: ChatActionInterface = {
+        delay: 4,
+        autoCancel: true,
+    }) {
+        this._client = client;
+        this._chat = chat;
+        this._action = action;
+        this._delay = params.delay;
+        this.autoCancel = params.autoCancel;
+        this._request = undefined;
+        this._task = null;
+        this._running = false;
+    }
+
+    async start() {
+        this._request = new SetTyping({
+            peer: this._chat,
+            action: this._action
+        });
+        this._running = true;
+        this._update();
+    }
+
+    async stop() {
+        this._running = false;
+        if (this.autoCancel) {
+            await this._client.invoke(new SetTyping({
+                peer: this._chat,
+                action: new Api.SendMessageCancelAction()
+            }));
+        }
+    }
+
+    async _update() {
+        while (this._running) {
+            if (this._request != undefined) {
+                await this._client.invoke(this._request);
+            }
+            await sleep(this._delay * 1000);
+        }
+
+    }
+
+    progress(current: number, total: number) {
+        if ('progress' in this._action) {
+            this._action.progress = 100 * Math.round(current / total)
+        }
+
+    }
+}
+
+class _ParticipantsIter extends RequestIter {
+    private filterEntity: ((entity: Entity) => boolean) | undefined;
+    private requests?: GetParticipants[];
+
+    async _init(entity: EntityLike, filter: any, search?: string): Promise<boolean | void> {
+        if (filter.constructor === Function) {
+            if ([Api.ChannelParticipantsBanned, Api.ChannelParticipantsKicked, Api.ChannelParticipantsSearch, Api.ChannelParticipantsContacts].includes(filter)) {
+                filter = new filter({
+                    q: '',
+                });
+            } else {
+                filter = new filter();
+            }
+        }
+        entity = await this.client.getInputEntity(entity);
+        const ty = helpers._entityType(entity);
+        if (search && (filter || ty != helpers._EntityType.CHANNEL)) {
+            // We need to 'search' ourselves unless we have a PeerChannel
+            search = search.toLowerCase();
+            this.filterEntity = (entity: Entity) => {
+                return utils.getDisplayName(entity).toLowerCase().includes(<string>search) ||
+                    ('username' in entity ? entity.username || '' : '').toLowerCase().includes(<string>search)
+            }
+        } else {
+            this.filterEntity = (entity: Entity) => true;
+        }
+        // Only used for channels, but we should always set the attribute
+        this.requests = [];
+        if (ty == helpers._EntityType.CHANNEL) {
+            const channel = (await this.client.invoke(new GetFullChannel({
+                channel: entity
+            })));
+            if (!(channel.fullChat instanceof Api.ChatFull)) {
+                this.total = channel.fullChat.participantsCount;
+            }
+            if (this.total && this.total <= 0) {
+                return false;
+            }
+            this.requests.push(new GetParticipants({
+                channel: entity,
+                filter: filter || new ChannelParticipantsSearch({
+                    q: search || '',
+                }),
+                offset: 0,
+                limit: _MAX_PARTICIPANTS_CHUNK_SIZE,
+                hash: 0,
+            }))
+        } else if (ty == helpers._EntityType.CHAT) {
+            const full = await this.client.invoke(new GetFullChat({
+                chatId: entity.chatId
+            }));
+
+            if (full.fullChat instanceof Api.ChatFull) {
+                if (!(full.fullChat.participants instanceof Api.ChatParticipantsForbidden)) {
+                    this.total = full.fullChat.participants.participants.length;
+                } else {
+                    this.total = 0;
+                    return false;
+                }
+
+                const users = new Map();
+                for (const user of full.users) {
+                    users.set(user.id, user);
+                }
+                for (const participant of full.fullChat.participants.participants) {
+                    const user = users.get(participant.userId);
+                    if (!this.filterEntity(user)) {
+                        continue;
+                    }
+                    user.participant = participant;
+                    this.buffer?.push(user);
+                }
+                return true;
+            }
+
+        } else {
+            this.total = 1;
+            if (this.limit != 0) {
+                const user = await this.client.getEntity(entity);
+                if (this.filterEntity(user)) {
+                    // @ts-ignore
+                    user.participant = null;
+                    this.buffer?.push(user);
+                }
+            }
+            return true;
+        }
+    }
+
+    async _loadNextChunk(): Promise<boolean | undefined> {
+        if (!this.requests) {
+            return true;
+        }
+        this.requests[0].limit = Math.min(
+            this.limit - this.requests[0].offset, _MAX_PARTICIPANTS_CHUNK_SIZE
+        );
+        if (this.requests[0].offset > this.limit) {
+            return true;
+        }
+        const results = [];
+        for (const request of this.requests) {
+            results.push(
+                await this.client.invoke(request)
+            );
+        }
+        for (let i = this.requests.length; i > 0; i--) {
+            const participants = results[i];
+            if (participants instanceof ChannelParticipantsNotModified || !participants.users) {
+                this.requests.splice(i, 1);
+                continue;
+            }
+            this.requests[i].offset += participants.participants.length;
+            const users = new Map();
+            for (const user of participants.users) {
+                users.set(user.id, user);
+            }
+            for (const participant of participants.participants) {
+                const user = users.get(participant.userId);
+                if (this.filterEntity && !this.filterEntity(user)) {
+                    continue;
+                }
+                user.participant = participant;
+                this.buffer?.push(user);
+            }
+        }
+        return undefined;
+    }
+}
+
+interface _AdminLogFilterInterface {
+    join?: boolean;
+    leave?: boolean;
+    invite?: boolean;
+    restrict?: boolean;
+    unrestrict?: boolean;
+    ban?: boolean;
+    unban?: boolean;
+    promote?: boolean;
+    demote?: boolean;
+    info?: boolean;
+    settings?: boolean;
+    pinned?: boolean;
+    edit?: boolean;
+    delete?: boolean;
+    groupCall?: boolean;
+}
+
+interface _AdminLogSearchInterface {
+    admins?: EntitiesLike;
+    search?: string;
+    minId?: BigInteger;
+    maxId?: BigInteger;
+}
+
+class _AdminLogIter extends RequestIter {
+    private entity?: Api.TypeInputPeer;
+    private request?: Api.channels.GetAdminLog;
+
+    async _init(entity: EntityLike, searchArgs?: _AdminLogSearchInterface, filterArgs?: _AdminLogFilterInterface) {
+        let eventsFilter = undefined;
+
+        if (filterArgs && Object.values(filterArgs).find(element => element === true)) {
+            eventsFilter = new ChannelAdminLogEventsFilter({
+                ...filterArgs
+            });
+        }
+        this.entity = await this.client.getInputEntity(entity);
+        const adminList = []
+        if (searchArgs && searchArgs.admins) {
+            for (const admin of searchArgs.admins) {
+                adminList.push(await this.client.getInputEntity(admin))
+            }
+        }
+        this.request = new GetAdminLog({
+                channel: this.entity,
+                q: searchArgs?.search || '',
+                minId: searchArgs?.minId,
+                maxId: searchArgs?.maxId,
+                limit: 0,
+                eventsFilter: eventsFilter,
+                admins: adminList || undefined,
+            }
+        )
+    }
+
+    async _loadNextChunk() {
+        if (!this.request) {
+            return true;
+        }
+        this.request.limit = Math.min(this.left, _MAX_ADMIN_LOG_CHUNK_SIZE);
+        const r = await this.client.invoke(this.request);
+        const entities = new Map();
+        for (const entity of [...r.users, ...r.chats]) {
+            entities.set(utils.getPeerId(entity), entity);
+        }
+        const eventIds = [];
+        for (const e of r.events) {
+            eventIds.push(e.id);
+        }
+        this.request.maxId = getMinBigInt([bigInt.zero, ...eventIds]);
+        for (const ev of r.events) {
+            if (ev.action instanceof ChannelAdminLogEventActionEditMessage) {
+                // @ts-ignore
+                ev.action.prevMessage._finishInit(this.client, entities, this.entity);
+                // @ts-ignore
+                ev.action.newMessage._finishInit(this.client, entities, this.entity);
+
+            }
+        }
+    }
+}
+
+export class ChatMethods {
+    // TODO implement
+}

+ 15 - 0
gramjs/client/dialogs.ts

@@ -0,0 +1,15 @@
+import {AccountMethods} from "./account";
+import {AuthMethods} from "./auth";
+import {DownloadMethods} from "./downloads";
+import {ChatMethods} from "./chats";
+import {BotMethods} from "./bots";
+import {MessageMethods} from "./messages";
+import {ButtonMethods} from "./buttons";
+import {UpdateMethods} from "./updates";
+import {MessageParseMethods} from "./messageParse";
+import {UserMethods} from "./users";
+import {TelegramBaseClient} from "./telegramBaseClient";
+
+export class DialogMethods {
+    // TODO
+}

+ 1 - 1
gramjs/client/downloadFile.ts

@@ -1,5 +1,5 @@
 import { default as Api } from '../tl/api';
-import TelegramClient from './TelegramClient';
+import {TelegramClient} from './TelegramClient';
 // @ts-ignore
 import { getAppropriatedPartSize } from '../Utils';
 // @ts-ignore

+ 3 - 0
gramjs/client/downloads.ts

@@ -0,0 +1,3 @@
+export class DownloadMethods {
+    // TODO
+}

+ 156 - 0
gramjs/client/messageParse.ts

@@ -0,0 +1,156 @@
+import {getPeer, getPeerId, isArrayLike, sanitizeParseMode} from "../Utils";
+import {Api} from "../tl/api";
+import {EntityLike} from "../define";
+import {_EntityType, _entityType} from "../Helpers";
+import {UserMethods} from "./users";
+
+export class MessageParseMethods {
+    _parseMode: any;
+    get parseMode(): any {
+        return this._parseMode;
+    }
+
+    set parseMode(mode: any) {
+        this._parseMode = sanitizeParseMode(mode);
+    }
+
+    async _replaceWithMention(entities: Api.TypeMessageEntity[], i: number, user: EntityLike) {
+        try {
+            entities[i] = new Api.InputMessageEntityMentionName(
+                {
+                    offset: entities[i].offset,
+                    length: entities[i].length,
+                    userId: await this.getInputEntity(user)
+                }
+            )
+            return true;
+        } catch (e) {
+            return false;
+        }
+    }
+
+    _parseMessageText(message: string, parseMode: any) {
+        if (!parseMode) {
+            parseMode = this._parseMode;
+        } else if (typeof parseMode === "string") {
+            parseMode = sanitizeParseMode(parseMode);
+        }
+        if (!parseMode) {
+            return [message, []]
+        }
+        return parseMode.parse(message);
+    }
+
+    _getResponseMessage(request: Api.AnyRequest, result: Api.TypeUpdates, inputChat: Api.TypeInputPeer) {
+        let updates = [];
+        let entities = new Map();
+        if (result instanceof Api.UpdateShort) {
+            updates = [result.update]
+        } else if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
+            updates = result.updates;
+            for (const x of [...result.users, ...result.chats]) {
+                entities.set(getPeerId(x), x);
+            }
+        } else {
+            return;
+        }
+        const randomToId = new Map();
+        const idToMessage = new Map();
+        const schedToMessage = new Map();
+        for (const update of updates) {
+            if (update instanceof Api.UpdateMessageID) {
+                randomToId.set(update.randomId, update.id);
+            } else if (update instanceof Api.UpdateNewChannelMessage || update instanceof Api.UpdateNewMessage) {
+                // @ts-ignore
+                update.message._finishInit(this, entities, inputChat);
+                if ('randomId' in request || isArrayLike(request)) {
+                    idToMessage.set(update.message.id, update.message);
+                } else {
+                    return update.message;
+                }
+            } else if (update instanceof Api.UpdateEditMessage && 'peer' in request && _entityType(request.peer) != _EntityType.CHANNEL) {
+                // @ts-ignore
+                update.message._finishInit(this, entities, inputChat);
+                if ('randomId' in request) {
+                    idToMessage.set(update.message.id, update.message);
+                } else if ('id' in request && request.id === update.message.id) {
+                    return update.message;
+                }
+            } else if (update instanceof Api.UpdateEditChannelMessage &&
+                update.message instanceof Api.Message && 'peer' in request && getPeerId(request.peer) == getPeerId(update.message.peerId)) {
+                schedToMessage.set(update.message.id, update.message);
+            } else if (update instanceof Api.UpdateMessagePoll) {
+                if ('media' in request && request.media && "poll" in request.media && request?.media.poll.id == update.pollId) {
+                    if ('peer' in request) {
+                        const peerId = getPeer(request.peer) as Api.TypePeer;
+                        const poll = update.poll;
+                        if (poll && 'id' in request) {
+                            const m = new Api.Message({
+                                id: request.id,
+                                peerId: peerId,
+                                media: new Api.MessageMediaPoll({
+                                    poll: poll,
+                                    results: update.results
+                                }),
+                                message: '',
+                                date: 0,
+                            });
+                            // @ts-ignore
+                            m._finishInit(this, entities, inputChat);
+                            return m;
+                        }
+                    }
+                }
+            }
+
+        }
+        if (!request) {
+            return idToMessage;
+        }
+        let mapping;
+        let opposite = new Map();
+
+        if (!("scheduleDate" in request)) {
+            mapping = idToMessage;
+        } else {
+            mapping = schedToMessage;
+            opposite = idToMessage;
+        }
+        let randomId: any = (typeof request == 'number' || isArrayLike(request)) ? request : 'randomId' in request ? request.randomId : undefined;
+
+        if (randomId === undefined) {
+            // TODO add logging
+            return null;
+        }
+        if (!isArrayLike(request)) {
+            let msg = mapping.get(randomToId.get(randomId));
+            if (!msg) {
+                msg = opposite.get(randomToId.get(randomId));
+            }
+            if (!msg) {
+                throw new Error(`Request ${request.className} had missing message mapping`)
+                // TODO add logging
+            }
+            return msg;
+        }
+        if (isArrayLike((randomId))) {
+
+            const maps = [];
+            // @ts-ignore
+            for (const rnd of randomId) {
+                const d = mapping.get(randomToId.get(rnd));
+                const o = opposite.get(randomToId.get(rnd));
+
+                maps.push(d ?? o)
+
+            }
+            return maps;
+        }
+    }
+
+}
+
+export interface MessageParseMethods extends UserMethods {
+
+}
+

+ 480 - 0
gramjs/client/messages.ts

@@ -0,0 +1,480 @@
+import {Api} from "../tl";
+import {Message} from '../tl/custom/message';
+import {DateLike, EntityLike, FileLike, MarkupLike, MessageLike} from "../define";
+import {RequestIter} from "../requestIter";
+import {_EntityType, _entityType} from "../Helpers";
+import {getMessageId, getPeerId, isArrayLike} from "../Utils";
+import {TelegramClient, utils} from "../index";
+import {MessageParseMethods} from "./messageParse";
+import {ButtonMethods} from "./buttons";
+
+const _MAX_CHUNK_SIZE = 100;
+
+export interface SendMessageInterface {
+    replyTo?: number | Api.Message,
+    parseMode?: any,
+    formattingEntities?: Api.TypeMessageEntity,
+    linkPreview?: boolean,
+    file?: FileLike | FileLike[],
+    forceDocument?: boolean,
+    clearDraft?: boolean,
+    buttons?: MarkupLike,
+    silent?: boolean,
+    schedule?: DateLike
+
+}
+
+interface MessageIterParams {
+    offsetId: number;
+    minId: number;
+    maxId: number;
+    fromUser?: EntityLike;
+    offsetDate: DateLike;
+    addOffset: number;
+    filter: any;
+    search: string;
+    replyTo: EntityLike;
+}
+
+class _MessagesIter extends RequestIter {
+    private entity?: Api.TypeInputPeer;
+
+    async _init(entity: EntityLike, {offsetId, minId, maxId, fromUser, offsetDate, addOffset, filter, search, replyTo}: MessageIterParams) {
+        if (entity) {
+            this.entity = await this.client.getInputEntity(entity);
+        } else {
+            this.entity = undefined;
+            if (this.reverse) {
+                throw new Error("Cannot reverse global search");
+            }
+        }
+        if (this.reverse) {
+            offsetId = Math.max(offsetId, minId);
+            if (offsetId && maxId) {
+                if (maxId - offsetId <= 1) {
+                    return false;
+                }
+            }
+            if (!maxId) {
+                maxId = Number.MAX_SAFE_INTEGER;
+            }
+        } else {
+            offsetId = Math.max(offsetId, maxId);
+            if (offsetId && minId) {
+                if (offsetId - minId <= 1) {
+                    return false;
+                }
+            }
+        }
+        if (this.reverse) {
+            if (offsetId) {
+                offsetId += 1;
+            } else if (!offsetDate) {
+                offsetId = 1;
+            }
+        }
+        if (fromUser) {
+            fromUser = await this.client.getInputEntity(fromUser);
+            this.fromId = await this.client.getPeerId(fromUser);
+        } else {
+            this.fromId = null;
+        }
+
+        if (!this.entity && fromUser) {
+            this.entity = new Api.InputPeerEmpty();
+        }
+        if (!filter) {
+            filter = new Api.InputMessagesFilterEmpty();
+        }
+        if (!this.entity) {
+            this.request = new Api.messages.SearchGlobal({
+                q: search || '',
+                filter: filter,
+                minDate: undefined,
+                // TODO fix this smh
+                maxDate: offsetDate,
+                offsetRate: undefined,
+                offsetPeer: new Api.InputPeerEmpty(),
+                offsetId: offsetId,
+                limit: 1,
+            })
+        } else if (!replyTo) {
+            this.request = new Api.messages.GetReplies({
+                peer: this.entity,
+                msgId: replyTo,
+                offsetId: offsetId,
+                offsetDate: offsetDate,
+                addOffset: addOffset,
+                limit: 0,
+                maxId: 0,
+                minId: 0,
+                hash: 0
+            });
+        } else if (!search || filter || fromUser) {
+            const ty = _entityType(this.entity);
+            if (ty == _EntityType.USER) {
+                fromUser = undefined;
+            } else {
+                this.fromId = undefined;
+            }
+            this.request = new Api.messages.Search({
+                peer: this.entity,
+                q: search || '',
+                filter: typeof filter === 'function' ? new filter() : filter,
+                minDate: undefined,
+                maxDate: offsetDate,
+                offsetId: offsetId,
+                addOffset: addOffset,
+                limit: 0,
+                maxId: 0,
+                minId: 0,
+                hash: 0,
+                fromId: fromUser
+            });
+            if (filter instanceof Api.InputMessagesFilterEmpty && offsetDate && !search && !offsetId) {
+                for await (const m of this.client.iterMessages(this.entity, {limit: 1, offsetDate: offsetDate})) {
+                    this.request.offsetId = m.id + 1;
+                }
+            }
+        } else {
+            this.request = new Api.messages.GetHistory({
+                peer: this.entity,
+                limit: 1,
+                offsetDate: offsetDate,
+                offsetId: offsetId,
+                minId: 0,
+                maxId: 0,
+                addOffset: addOffset,
+                hash: 0
+            })
+        }
+        if (this.limit <= 0) {
+            const result = await this.client.invoke(this.request);
+            if (result instanceof Api.messages.MessagesNotModified) {
+                this.total = result.count;
+            } else {
+                this.total = result.count ?? result.messages.length;
+            }
+            return false;
+        }
+        if (!this.waitTime) {
+            this.waitTime = this.limit > 3000 ? 1 : 0;
+        }
+        if (this.reverse) {
+            this.request.addOffset -= _MAX_CHUNK_SIZE;
+        }
+        this.addOffset = addOffset;
+        this.maxId = maxId;
+        this.minId = minId;
+        this.lastId = this.reverse ? 0 : Number.MAX_SAFE_INTEGER;
+    }
+
+    async _loadNextChunk() {
+        this.request.limit = Math.min(this.left, _MAX_CHUNK_SIZE);
+        if (this.reverse && this.request.limit != _MAX_CHUNK_SIZE) {
+            this.request.addOffset = this.addOffset - this.request.limit;
+        }
+        const r = await this.client.invoke(this.request);
+        this.total = r.count ?? r.messages.length;
+        const entities = new Map();
+        for (const x of [...r.user, ...r.chats]) {
+            entities.set(getPeerId(x), x);
+        }
+        const messages: Message[] = this.reverse ? r.messages.reverse() : r.messages;
+        for (const message of messages) {
+            if ((this.fromId && message.senderId != this.fromId)) {
+                continue;
+            }
+            if (!this._messageInRange(message)) {
+                return true;
+            }
+            this.lastId = message.id;
+            message._finishInit(this.client, entities, this.entity);
+            this.buffer?.push(message);
+        }
+        if (r.messages.length < this.request.limit) {
+            return true;
+        }
+
+        if (this.buffer) {
+            this._updateOffset(this.buffer[this.buffer.length - 1], r)
+        } else {
+            return true;
+        }
+    }
+
+    _messageInRange(message: Message) {
+        if (this.entity) {
+            if (this.reverse) {
+                if (message.id <= this.lastId || message.id >= this.maxId) {
+                    return false;
+                }
+            } else {
+                if (message.id >= this.lastId || message.id <= this.minId) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    _updateOffset(lastMessage: Message, response: any) {
+        this.request.offsetId = lastMessage.id;
+        if (this.reverse) {
+            this.request.offsetId += 1;
+        }
+        if (this.request instanceof Api.messages.Search) {
+            this.request.maxDate = -1;
+        } else {
+            this.request.offsetDate = lastMessage.date;
+        }
+        if (this.request instanceof Api.messages.SearchGlobal) {
+            if (lastMessage.inputChat) {
+                this.request.offsetPeer = lastMessage.inputChat;
+            } else {
+                this.request.offsetPeer = new Api.InputPeerEmpty();
+            }
+            this.request.offsetRate = response.nextRate;
+        }
+    }
+}
+
+class _IDsIter extends RequestIter {
+    async _init(entity: EntityLike, ids: MessageLike[]) {
+        this.total = ids.length;
+        this._ids = this.reverse ? ids.reverse() : ids;
+        this._offset = 0;
+        this._entity = entity ? (await this.client.getInputEntity(entity)) : undefined;
+        this._ty = this._entity ? _entityType(this._entity) : undefined;
+
+
+        if (!this.waitTime) {
+            this.waitTile = this.limit > 300 ? 10 : 0;
+        }
+    }
+
+    async _loadNextChunk() {
+        const ids = this._ids.slice(this._offset, this._offset + _MAX_CHUNK_SIZE);
+        if (!ids.length) {
+            return false;
+        }
+        this._offset += _MAX_CHUNK_SIZE;
+        let fromId;
+        let r;
+
+        if (this._ty == _EntityType.CHANNEL) {
+            try {
+                r = await this.client.invoke(new Api.channels.GetMessages({
+                    channel: this._entity,
+                    id: ids
+                }));
+            } catch (e) {
+                if (e.message == "MESSAGE_IDS_EMPTY") {
+                    r = new Api.messages.MessagesNotModified({
+                        count: ids.length
+                    });
+                } else {
+                    throw e;
+                }
+            }
+        } else {
+            r = await this.client.invoke(new Api.messages.GetMessages({
+                id: ids
+            }));
+            if (this._entity) {
+                fromId = await this.client._getPeer(this.entity);
+            }
+        }
+        if (r instanceof Api.messages.MessagesNotModified) {
+            this.buffer?.push(...Array(ids.length));
+            return
+        }
+        const entities = new Map();
+        for (const entity of [...r.users, ...r.chats]) {
+            entities.set(utils.getPeerId(entity), entity);
+        }
+        let message: Api.TypeMessage;
+        for (message of r.messages) {
+            if (message instanceof Api.MessageEmpty || fromId && message.peerId != fromId) {
+                this.buffer?.push(undefined)
+            } else {
+                // @ts-ignore
+                message._finishInit(this.client, entities, this._entity);
+                this.buffer?.push(message);
+            }
+        }
+    }
+}
+
+interface IterMessagesParams {
+    limit?: number;
+    offsetDate?: DateLike;
+    offsetId?: number;
+    maxId?: number;
+    minId?: number;
+    addOffset?: number;
+    search?: string;
+    filter?: Api.TypeMessagesFilter | Api.TypeMessagesFilter[];
+    fromUser?: EntityLike;
+    waitTime?: number;
+    ids?: number | number[];
+    reverse?: boolean;
+    replyTo?: number;
+}
+
+interface SendMessageParams {
+    message: MessageLike;
+    replyTo?: number | Api.Message;
+    parseMode?: any;
+    formattingEntities?: Api.TypeMessageEntity[];
+    linkPreview?: boolean;
+    file?: FileLike | FileLike[];
+    forceDocument?: false;
+    clearDraft?: false;
+    buttons?: MarkupLike;
+    silent?: boolean;
+    schedule?: DateLike;
+}
+
+export class MessageMethods {
+
+    iterMessages(entity: EntityLike, {limit, offsetDate, offsetId, maxId, minId, addOffset, search, filter, fromUser, waitTime, ids, reverse = false, replyTo}: IterMessagesParams) {
+        if (ids) {
+            if (typeof ids == 'number') {
+                ids = [ids]
+            }
+            // @ts-ignore
+            return new _IDsIter(this, ids.length, {
+                reverse: reverse,
+                waitTime: waitTime
+            }, {
+                entity: entity
+            });
+        }
+        // @ts-ignore
+        return new _MessagesIter(this, limit, {
+            waitTime: waitTime,
+            reverse: reverse
+        }, {
+            entity: entity,
+            offsetId: offsetId,
+            minId: minId,
+            maxId: maxId,
+            fromUser: fromUser,
+            offsetDate: offsetDate,
+            addOffset: addOffset,
+            filter: filter,
+            search: search,
+            replyTo: replyTo
+        })
+    }
+
+    async getMessages(entity: EntityLike, params: IterMessagesParams) {
+        if (Object.keys(params).length == 1 && params.limit === undefined) {
+            if (params.minId === undefined && params.maxId === undefined) {
+                params.limit = undefined;
+            } else {
+                params.limit = 1;
+            }
+        }
+
+        const it = this.iterMessages(entity, params);
+        const ids = params.ids;
+        if (ids && !isArrayLike(ids)) {
+            for await (const message of it) {
+                return message;
+            }
+            return;
+
+        }
+        return await it.collect();
+    }
+
+    // region Message
+
+    async sendMessage(entity: EntityLike, {message, replyTo, parseMode, formattingEntities, linkPreview = true, file, forceDocument, clearDraft, buttons, silent, schedule}: SendMessageParams) {
+        if (file) {
+            throw new Error("Not Supported Yet");
+            //return this.sendFile();
+        }
+        entity = await this.getInputEntity(entity);
+        let markup, request;
+        if (message instanceof Api.Message) {
+            if (buttons == undefined) {
+                markup = message.replyMarkup;
+            } else {
+                markup = this.buildReplyMarkup(buttons);
+            }
+            if (silent == undefined) {
+                silent = message.silent;
+            }
+            if (message.media && !(message.media instanceof Api.MessageMediaWebPage)) {
+                throw new Error("Not Supported Yet");
+                /*
+                                return this.sendFile(entity, message.media, {
+                                    caption: message.message,
+                                    silent: silent,
+                                    replyTo: replyTo,
+                                    buttons: markup,
+                                    formattingEntities: message.entities,
+                                    schedule: schedule
+                                })
+
+                 */
+            }
+            request = new Api.messages.SendMessage({
+                peer: entity,
+                message: message.message || '',
+                silent: silent,
+                replyToMsgId: getMessageId(replyTo),
+                replyMarkup: markup,
+                entities: message.entities,
+                clearDraft: clearDraft,
+                noWebpage: !(message.media instanceof Api.MessageMediaWebPage),
+                scheduleDate: schedule
+            })
+            message = message.message;
+        } else {
+            if (formattingEntities == undefined) {
+                [message, formattingEntities] = await this._parseMessageText(message, parseMode);
+            }
+            if (!message) {
+                throw new Error("The message cannot be empty unless a file is provided");
+            }
+            request = new Api.messages.SendMessage({
+                peer: entity,
+                message: message.toString(),
+                entities: formattingEntities,
+                noWebpage: !linkPreview,
+                replyToMsgId: getMessageId(replyTo),
+                clearDraft: clearDraft,
+                silent: silent,
+                replyMarkup: this.buildReplyMarkup(buttons),
+                scheduleDate: schedule
+            })
+        }
+        const result = await this.invoke(request);
+        if (result instanceof Api.UpdateShortSentMessage) {
+            const newMessage = new Message({
+                id: result.id,
+                peerId: await this._getPeer(entity),
+                message: message,
+                date: result.date,
+                out: result.out,
+                media: result.media,
+                entities: result.entities,
+                replyMarkup: request.replyMarkup,
+            })
+            // @ts-ignore
+            newMessage._finishInit(this, {}, entity);
+            return newMessage;
+        }
+        return this._getResponseMessage(request, result, entity);
+    }
+
+    // TODO do the rest
+}
+
+export interface MessageMethods extends MessageParseMethods, ButtonMethods {
+
+}

+ 367 - 0
gramjs/client/telegramBaseClient.ts

@@ -0,0 +1,367 @@
+import {version} from "../index";
+import {IS_NODE} from "../Helpers";
+import {ConnectionTCPFull, ConnectionTCPObfuscated} from "../network/connection";
+import {Session} from "../sessions/Abstract";
+import {Logger} from "../extensions";
+import {StringSession} from "../sessions";
+import {Api} from "../tl";
+
+
+import os from 'os';
+import {LAYER} from "../tl/AllTLObjects";
+import {AuthKey} from "../crypto/AuthKey";
+import {EntityCache} from "../entityCache";
+import {UpdateMethods} from "./updates";
+import {MTProtoSender, UpdateConnectionState} from "../network";
+import {UserMethods} from "./users";
+
+const DEFAULT_DC_ID = 1;
+const DEFAULT_IPV4_IP = IS_NODE ? '149.154.167.51' : 'pluto.web.telegram.org';
+const DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]';
+
+export interface TelegramClientParams {
+    connection?: any,
+    useIPV6?: false,
+    timeout?: number,
+    requestRetries?: number,
+    connectionRetries?: number,
+    retryDelay?: number,
+    autoReconnect?: boolean,
+    sequentialUpdates?: boolean,
+    floodSleepThreshold?: number,
+    deviceModel?: string,
+    systemVersion?: string,
+    appVersion?: string,
+    langCode?: 'en',
+    systemLangCode?: 'en',
+    baseLogger?: string | any,
+    useWSS?: false,
+}
+
+export class TelegramBaseClient {
+
+
+    __version__ = version;
+    _config ?: Api.Config;
+    public _log: Logger;
+    public _floodSleepThreshold: number;
+    public session: StringSession;
+    public apiHash: string;
+    public apiId: number;
+    public _requestRetries: number;
+    public _connectionRetries: number;
+    public _retryDelay: number;
+    public _timeout: number;
+    public _autoReconnect: boolean;
+    public _connection: any;
+    public _initRequest: Api.InitConnection;
+    public _sender?: any;
+    public _floodWaitedRequests: any;
+    public _borrowedSenderPromises: any;
+    public _bot?: boolean;
+    public _selfInputPeer?: Api.InputPeerUser;
+    public useWSS: boolean;
+    public _eventBuilders: any[];
+    public _entityCache: EntityCache;
+
+    downloadMedia(...args: any) {
+        return undefined;
+    }
+
+    constructor(session: string | Session, apiId: number, apiHash: string, {
+        connection = IS_NODE ? ConnectionTCPFull : ConnectionTCPObfuscated,
+        useIPV6 = false,
+        timeout = 10,
+        requestRetries = 5,
+        connectionRetries = Infinity,
+        retryDelay = 1000,
+        autoReconnect = true,
+        sequentialUpdates = false,
+        floodSleepThreshold = 60,
+        deviceModel = '',
+        systemVersion = '',
+        appVersion = '',
+        langCode = 'en',
+        systemLangCode = 'en',
+        baseLogger = 'gramjs',
+        useWSS = false,
+    }: TelegramClientParams) {
+        if (!apiId || !apiHash) {
+            throw new Error("Your API ID or Hash cannot be empty or undefined");
+        }
+        if (typeof baseLogger == 'string') {
+            this._log = new Logger()
+        } else {
+            this._log = baseLogger
+        }
+        if (!(session instanceof StringSession)) {
+            throw new Error("Only StringSession is supported currently :( ");
+        }
+        this._floodSleepThreshold = floodSleepThreshold;
+        this.session = session;
+        this.apiId = apiId;
+        this.apiHash = apiHash;
+        this._requestRetries = requestRetries;
+        this._connectionRetries = connectionRetries;
+        this._retryDelay = retryDelay || 0;
+        this._timeout = timeout;
+        this._autoReconnect = autoReconnect;
+        if (!(connection instanceof Function)) {
+            throw new Error("Connection should be a class not an instance");
+        }
+        this._connection = connection;
+        this._initRequest = new Api.InitConnection({
+            apiId: this.apiId,
+            deviceModel: deviceModel || os.type()
+                .toString() || 'Unknown',
+            systemVersion: systemVersion || os.release()
+                .toString() || '1.0',
+            appVersion: appVersion || '1.0',
+            langCode: langCode,
+            langPack: '', // this should be left empty.
+            systemLangCode: systemLangCode,
+            proxy: undefined, // no proxies yet.
+        });
+        this._eventBuilders = [];
+
+        this._floodWaitedRequests = {};
+        this._borrowedSenderPromises = {};
+        this._bot = undefined;
+        this._selfInputPeer = undefined;
+        this.useWSS = useWSS;
+        this._entityCache = new EntityCache()
+
+    }
+
+
+    get floodSleepThreshold() {
+        return this._floodSleepThreshold;
+    }
+
+    set floodSleepThreshold(value: number) {
+        this._floodSleepThreshold = Math.min(value || 0, 24 * 60 * 60);
+    }
+
+    // region connecting
+    async _initSession() {
+        await this.session.load();
+
+        if (!this.session.serverAddress) {
+            this.session.setDC(DEFAULT_DC_ID, DEFAULT_IPV4_IP, this.useWSS ? 443 : 80)
+        }
+    }
+
+
+    async connect() {
+        await this._initSession();
+
+        this._sender = new MTProtoSender(this.session.getAuthKey(), {
+            logger: this._log,
+            dcId: this.session.dcId,
+            retries: this._connectionRetries,
+            delay: this._retryDelay,
+            autoReconnect: this._autoReconnect,
+            connectTimeout: this._timeout,
+            authKeyCallback: this._authKeyCallback.bind(this),
+            updateCallback: this._handleUpdate.bind(this),
+            isMainSender: true,
+        });
+
+        const connection = new this._connection(this.session.serverAddress
+            , this.session.port, this.session.dcId, this._log);
+        if (!await this._sender.connect(connection, this._dispatchUpdate.bind(this))) {
+            return
+        }
+        this.session.setAuthKey(this._sender.authKey);
+        await this.session.save();
+        this._initRequest.query = new Api.help.GetConfig();
+        await this._sender.send(new Api.InvokeWithLayer(
+            {
+                layer: LAYER,
+                query: this._initRequest
+            }
+        ));
+
+        this._dispatchUpdate({update: new UpdateConnectionState(1)});
+        this._updateLoop()
+    }
+
+    get connected() {
+        return this._sender && this._sender.isConnected();
+    }
+
+    async disconnect() {
+        if (this._sender) {
+            await this._sender.disconnect()
+        }
+    }
+
+    get disconnected() {
+        return !this._sender || this._sender.disconnected;
+    }
+
+    async destroy() {
+        await Promise.all([
+            this.disconnect(),
+            this.session.delete(),
+            ...Object.values(this._borrowedSenderPromises).map((promise: any) => {
+                return promise
+                    .then((sender: any) => sender.disconnect())
+            }),
+        ]);
+
+        this._eventBuilders = []
+    }
+
+    async _switchDC(newDc: number) {
+        this._log.info(`Reconnecting to new data center ${newDc}`);
+        const DC = await this.getDC(newDc);
+        this.session.setDC(newDc, DC.ipAddress, DC.port);
+        // authKey's are associated with a server, which has now changed
+        // so it's not valid anymore. Set to None to force recreating it.
+        await this._sender.authKey.setKey();
+        this.session.setAuthKey();
+        await this.disconnect();
+        return this.connect()
+    }
+
+    async _authKeyCallback(authKey: AuthKey, dcId: number) {
+        this.session.setAuthKey(authKey, dcId);
+        await this.session.save();
+    }
+
+    // endregion
+
+    // region Working with different connections/Data Centers
+
+    removeSender(dcId: number) {
+        delete this._borrowedSenderPromises[dcId]
+    }
+
+    async _borrowExportedSender(dcId: number, retries = 5) {
+        let senderPromise = this._borrowedSenderPromises[dcId];
+        if (!senderPromise) {
+            senderPromise = this._createExportedSender(dcId, retries);
+            this._borrowedSenderPromises[dcId] = senderPromise;
+
+            senderPromise.then((sender: any) => {
+                if (!sender) {
+                    delete this._borrowedSenderPromises[dcId]
+                }
+            })
+        }
+        return senderPromise
+    }
+
+    async _createExportedSender(dcId: number, retries: number) {
+        const dc = await this.getDC(dcId);
+        const sender = new MTProtoSender(this.session.getAuthKey(dcId),
+            {
+                logger: this._log,
+                dcId: dcId,
+                retries: this._connectionRetries,
+                delay: this._retryDelay,
+                autoReconnect: this._autoReconnect,
+                connectTimeout: this._timeout,
+                authKeyCallback: this._authKeyCallback.bind(this),
+                isMainSender: dcId === this.session.dcId,
+                senderCallback: this.removeSender.bind(this),
+            });
+        for (let i = 0; i < retries; i++) {
+            try {
+                await sender.connect(new this._connection(
+                    dc.ipAddress,
+                    dc.port,
+                    dcId,
+                    this._log,
+                ));
+                if (this.session.dcId !== dcId) {
+                    this._log.info(`Exporting authorization for data center ${dc.ipAddress}`);
+                    const auth = await this.invoke(new Api.auth.ExportAuthorization({dcId: dcId}));
+                    this._initRequest.query = new Api.auth.ImportAuthorization({
+                            id: auth.id,
+                            bytes: auth.bytes,
+                        },
+                    )
+                    const req = new Api.InvokeWithLayer({
+                        layer: LAYER,
+                        query: this._initRequest
+                    });
+                    await sender.send(req)
+                }
+                sender.dcId = dcId;
+                return sender
+            } catch (e) {
+                console.log(e);
+                await sender.disconnect()
+            }
+        }
+        return null
+    }
+
+    async getDC(dcId: number): Promise<{ id: number, ipAddress: string, port: number }> {
+        if (!IS_NODE) {
+            switch (dcId) {
+                case 1:
+                    return {
+                        id: 1,
+                        ipAddress: 'pluto.web.telegram.org',
+                        port: 443,
+                    };
+                case 2:
+                    return {
+                        id: 2,
+                        ipAddress: 'venus.web.telegram.org',
+                        port: 443,
+                    };
+                case 3:
+                    return {
+                        id: 3,
+                        ipAddress: 'aurora.web.telegram.org',
+                        port: 443,
+                    };
+                case 4:
+                    return {
+                        id: 4,
+                        ipAddress: 'vesta.web.telegram.org',
+                        port: 443,
+                    };
+                case 5:
+                    return {
+                        id: 5,
+                        ipAddress: 'flora.web.telegram.org',
+                        port: 443,
+                    };
+                default:
+                    throw new Error(`Cannot find the DC with the ID of ${dcId}`)
+            }
+        }
+        if (!this._config) {
+            this._config = await this.invoke(new Api.help.GetConfig())
+        }
+        for (const DC of this._config.dcOptions) {
+            if (DC.id === dcId) {
+                return {
+                    id: DC.id,
+                    ipAddress: DC.ipAddress,
+                    port: 443,
+                }
+            }
+        }
+        throw new Error(`Cannot find the DC with the ID of ${dcId}`)
+    }
+
+    // endregion
+
+
+}
+
+export interface TelegramBaseClient {
+    invoke<R extends Api.AnyRequest>(request: R): Promise<R['__response']>;
+
+    _dispatchUpdate(args: { update: UpdateConnectionState | any }): Promise<void> ;
+
+    _updateLoop(): Promise<void>;
+
+    _handleUpdate(update: Api.TypeUpdate | number): void;
+}

+ 143 - 0
gramjs/client/updates.ts

@@ -0,0 +1,143 @@
+import {EventBuilder} from "../events/common";
+import {Api} from "../tl";
+import {UserMethods} from "./users";
+import {TelegramBaseClient} from "./telegramBaseClient";
+import {helpers} from "../index";
+import bigInt from 'big-integer';
+import {UpdateConnectionState} from "../network";
+import {Message} from "../tl/custom/message";
+import {getMessageId, getPeerId} from "../Utils";
+
+export class UpdateMethods {
+    on(event: any) {
+        return (f: CallableFunction) => {
+            this.addEventHandler(f, event);
+            return f;
+        }
+    }
+
+    addEventHandler(callback: CallableFunction, event?: EventBuilder) {
+        // TODO; add actual handling
+        this._eventBuilders.push([event, callback])
+    }
+
+    removeEventHandler(callback: CallableFunction, event: EventBuilder) {
+        this._eventBuilders = this._eventBuilders.filter(function (item) {
+            return item !== [event, callback]
+        })
+    }
+
+    listEventHandlers() {
+        return this._eventBuilders;
+    }
+
+    catchUp() {
+        // TODO
+    }
+
+    _handleUpdate(update: Api.TypeUpdate | number):void {
+        if (typeof update === 'number') {
+            if ([-1, 0, 1].includes(update)) {
+                this._dispatchUpdate({update: new UpdateConnectionState(update)})
+                return
+            }
+        }
+
+        //this.session.processEntities(update)
+        this._entityCache.add(update);
+        this.session.processEntities(update);
+
+        if (update instanceof Api.Updates || update instanceof Api.UpdatesCombined) {
+            // TODO deal with entities
+            const entities = []
+            for (const x of [...update.users, ...update.chats]) {
+                entities.push(x)
+            }
+            for (const u of update.updates) {
+                this._processUpdate(u, update.updates, entities)
+            }
+        } else if (update instanceof Api.UpdateShort) {
+            this._processUpdate(update.update, null)
+        } else {
+            this._processUpdate(update, null)
+        }
+        // TODO add caching
+        // this._stateCache.update(update)
+    }
+
+    _processUpdate(update: any, others: any, entities?: any) {
+        update._entities = entities || {};
+        const args = {
+            update: update,
+            others: others,
+        }
+        this._dispatchUpdate(args)
+    }
+
+    async _dispatchUpdate(args: { update: UpdateConnectionState | any }):Promise<void> {
+        for (const [builder, callback] of this._eventBuilders) {
+            let event = args.update;
+            if (event) {
+                if (!this._selfInputPeer) {
+                    await this.getMe(true)
+                }
+                if (!(event instanceof UpdateConnectionState)) {
+                    if ('message' in event) {
+                        event = new Message({
+                            id: event.message.id,
+                            peerId: await this._getPeer(event.message.peerId),
+                            message: event.message,
+                            date: event.message.date,
+                            out: event.message.out,
+                            media: event.message.media,
+                            entities: event.message.entities,
+                            replyMarkup: event.message.replyMarkup,
+                            _entities: event._entities,
+                        });
+                        const entities = new Map();
+                        for (const entity of event._entities) {
+                            entities.set(getPeerId(entity), entity)
+                        }
+                        event._finishInit(this, entities);
+                    }
+                }
+                await callback(event)
+            }
+        }
+    }
+
+    async _updateLoop():Promise<void> {
+        while (this.connected) {
+            const rnd = helpers.getRandomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)
+            await helpers.sleep(1000 * 60)
+            // We don't care about the result we just want to send it every
+            // 60 seconds so telegram doesn't stop the connection
+            try {
+                this._sender.send(new Api.Ping({
+                    pingId: bigInt(rnd),
+                }))
+            } catch (e) {
+
+            }
+
+            // We need to send some content-related request at least hourly
+            // for Telegram to keep delivering updates, otherwise they will
+            // just stop even if we're connected. Do so every 30 minutes.
+
+            // TODO Call getDifference instead since it's more relevant
+            if (!this._lastRequest  || new Date().getTime() - this._lastRequest > 30 * 60 * 1000) {
+                try {
+                    await this.invoke(new Api.updates.GetState())
+                } catch (e) {
+
+                }
+            }
+        }
+    }
+
+
+}
+
+export interface UpdateMethods extends UserMethods, TelegramBaseClient {
+}
+

+ 1 - 5
gramjs/client/uploadFile.ts

@@ -1,6 +1,6 @@
 import { default as Api } from '../tl/api';
 
-import TelegramClient from './TelegramClient';
+import {TelegramClient} from './TelegramClient';
 // @ts-ignore
 import { generateRandomBytes, readBigIntFromBuffer, sleep } from '../Helpers';
 // @ts-ignore
@@ -120,10 +120,6 @@ export async function uploadFile(
         });
 }
 
-function generateRandomBigInt() {
-    return readBigIntFromBuffer(generateRandomBytes(8), false);
-}
-
 function fileToBuffer(file: File) {
     return new Response(file).arrayBuffer();
 }

+ 3 - 0
gramjs/client/uploads.ts

@@ -0,0 +1,3 @@
+export class UploadMethods {
+    //TODO imepleent
+}

+ 380 - 0
gramjs/client/users.ts

@@ -0,0 +1,380 @@
+import {Api} from "../tl";
+import {Entity, EntityLike} from "../define";
+import {getPeerId, isArrayLike} from "../Utils";
+import {_entityType, _EntityType, sleep} from "../Helpers";
+import {errors, utils} from "../index";
+import {DownloadMethods} from "./downloads";
+import {DialogMethods} from "./dialogs";
+import {BotMethods} from "./bots";
+import {MessageMethods} from "./messages";
+import {ButtonMethods} from "./buttons";
+import {UpdateMethods} from "./updates";
+import {MessageParseMethods} from "./messageParse";
+import {TelegramBaseClient} from "./telegramBaseClient";
+import bigInt from 'big-integer';
+
+export class UserMethods {
+    // region Invoking Telegram request
+    public _lastRequest?: number;
+
+    /**
+     * Invokes a MTProtoRequest (sends and receives it) and returns its result
+     * @param request
+     * @returns {Promise}
+     */
+    async invoke<R extends Api.AnyRequest>(request: R): Promise<R['__response']> {
+        if (request.classType !== 'request') {
+            throw new Error('You can only invoke MTProtoRequests')
+        }
+        // This causes issues for now because not enough utils
+        // await request.resolve(this, utils)
+
+        await request.resolve(this, utils);
+        this._lastRequest = new Date().getTime();
+        let attempt = 0;
+        for (attempt = 0; attempt < this._requestRetries; attempt++) {
+            try {
+                const promise = this._sender.send(request);
+                const result = await promise;
+                this.session.processEntities(result)
+                this._entityCache.add(result);
+                return result
+            } catch (e) {
+                if (e instanceof errors.ServerError || e.message === 'RPC_CALL_FAIL' ||
+                    e.message === 'RPC_MCGET_FAIL') {
+                    this._log.warn(`Telegram is having internal issues ${e.constructor.name}`);
+                    await sleep(2000)
+                } else if (e instanceof errors.FloodWaitError || e instanceof errors.FloodTestPhoneWaitError) {
+                    if (e.seconds <= this.floodSleepThreshold) {
+                        this._log.info(`Sleeping for ${e.seconds}s on flood wait`);
+                        await sleep(e.seconds * 1000)
+                    } else {
+                        throw e
+                    }
+                } else if (e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError ||
+                    e instanceof errors.UserMigrateError) {
+                    this._log.info(`Phone migrated to ${e.newDc}`);
+                    const shouldRaise = e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError;
+                    if (shouldRaise && await this.isUserAuthorized()) {
+                        throw e
+                    }
+                    await this._switchDC(e.newDc)
+                } else {
+                    throw e
+                }
+            }
+        }
+        throw new Error(`Request was unsuccessful ${attempt} time(s)`)
+    }
+
+    async getMe(inputPeer = false): Promise<Api.InputPeerUser | Api.User | undefined> {
+        if (inputPeer && this._selfInputPeer) {
+            return this._selfInputPeer;
+        }
+        try {
+            const me = (await this.invoke(new Api.users
+                .GetUsers({id: [new Api.InputUserSelf()]})))[0] as Api.User;
+            this._bot = me.bot;
+
+            if (!this._selfInputPeer) {
+                this._selfInputPeer = utils.getInputPeer(me, true) as Api.InputPeerUser;
+            }
+            return inputPeer ? this._selfInputPeer : me;
+        } catch (e) {
+        }
+    }
+
+    async isBot() {
+        if (this._bot === undefined) {
+            const me = await this.getMe();
+            if (me) {
+                return !(me instanceof Api.InputPeerUser) ? me.bot : undefined;
+            }
+        }
+        return this._bot;
+    }
+
+    async isUserAuthorized() {
+        try {
+            await this.invoke(new Api.updates.GetState());
+            return true;
+        } catch (e) {
+            return false;
+        }
+    }
+
+    async getEntity(entity: any): Promise<Entity> {
+        const single = !isArrayLike(entity);
+
+        if (single) {
+            // TODO fix this
+            // @ts-ignore
+            entity = [entity];
+        }
+        const inputs = [];
+        for (const x of entity) {
+            if (typeof x === 'string') {
+                inputs.push(x);
+            } else {
+                inputs.push(await this.getInputEntity(x));
+            }
+        }
+        const lists = new Map<number, any[]>([
+            [_EntityType.USER, []],
+            [_EntityType.CHAT, []],
+            [_EntityType.CHANNEL, []],
+        ]);
+        for (const x of inputs) {
+            try {
+                lists.get(_entityType(x))?.push(x);
+            } catch (e) {
+
+            }
+        }
+        let users = lists.get(_EntityType.USER);
+        let chats = lists.get(_EntityType.CHAT);
+        let channels = lists.get(_EntityType.CHANNEL);
+        if (users) {
+            const tmp = [];
+            while (users) {
+                let curr;
+                [curr, users] = [users.slice(0, 200), users.slice(200)];
+                tmp.push([...await this.invoke(new Api.users.GetUsers({
+                    id: curr
+                }))])
+            }
+            users = tmp;
+        }
+        if (chats) {
+            const chatIds = chats.map((x) => x.chatId);
+            chats = (await this.invoke(new Api.messages.GetChats({id: chatIds}))).chats;
+        }
+        if (channels) {
+            channels = (await this.invoke(new Api.channels.GetChannels(({id: channels})))).chats;
+        }
+        const idEntity = new Map<number, any>();
+
+        const res = [];
+        if (users) {
+            for (const user of users) {
+                res.push([getPeerId(user), user])
+            }
+        }
+        if (channels) {
+            for (const channel of channels) {
+                res.push([getPeerId(channel), channel])
+            }
+        }
+        if (chats) {
+            for (const chat of chats) {
+                res.push([getPeerId(chat), chat])
+            }
+        }
+        for (const x of res) {
+            idEntity.set(x[0], x[1]);
+        }
+        const result = [];
+        for (const x of inputs) {
+            if (typeof x === 'string') {
+                result.push(await this._getEntityFromString(x));
+            } else if (x instanceof Api.InputPeerSelf) {
+                result.push(idEntity.get(getPeerId(x)))
+            } else {
+                for (const [key, u] of idEntity.entries()) {
+                    if (u instanceof Api.User && u.self) {
+                        result.push(u);
+                        break
+                    }
+                }
+            }
+        }
+        return single ? result[0] : result;
+    }
+
+    async getInputEntity(peer: EntityLike): Promise<Api.TypeInputPeer> {
+        // Short-circuit if the input parameter directly maps to an InputPeer
+        try {
+            return utils.getInputPeer(peer)
+            // eslint-disable-next-line no-empty
+        } catch (e) {
+        }
+        // Next in priority is having a peer (or its ID) cached in-memory
+        try {
+            // 0x2d45687 == crc32(b'Peer')
+            if (typeof peer !== "string" && (typeof peer === 'number' || peer.SUBCLASS_OF_ID === 0x2d45687)) {
+                const res = this._entityCache.get(peer);
+                if (res) {
+                    return res;
+                }
+            }
+            // eslint-disable-next-line no-empty
+        } catch (e) {
+        }
+        // Then come known strings that take precedence
+        if (typeof peer == 'string') {
+            if (['me', 'this', 'self'].includes(peer)) {
+                return new Api.InputPeerSelf();
+            }
+        }
+
+        // No InputPeer, cached peer, or known string. Fetch from disk cache
+        try {
+            return this.session.getInputEntity(peer)
+            // eslint-disable-next-line no-empty
+        } catch (e) {
+        }
+        // Only network left to try
+        if (typeof peer === 'string') {
+            return utils.getInputPeer(await this._getEntityFromString(peer))
+        }
+        // If we're a bot and the user has messaged us privately users.getUsers
+        // will work with accessHash = 0. Similar for channels.getChannels.
+        // If we're not a bot but the user is in our contacts, it seems to work
+        // regardless. These are the only two special-cased requests.
+        peer = utils.getPeer(peer);
+        if (peer instanceof Api.PeerUser) {
+            const users = await this.invoke(new Api.users.GetUsers({
+                id: [new Api.InputUser({
+                    userId: peer.userId,
+                    accessHash: bigInt.zero,
+                })],
+            }));
+            if (users && !(users[0] instanceof Api.UserEmpty)) {
+                // If the user passed a valid ID they expect to work for
+                // channels but would be valid for users, we get UserEmpty.
+                // Avoid returning the invalid empty input peer for that.
+                //
+                // We *could* try to guess if it's a channel first, and if
+                // it's not, work as a chat and try to validate it through
+                // another request, but that becomes too much work.
+                return utils.getInputPeer(users[0])
+            }
+        } else if (peer instanceof Api.PeerChat) {
+            return new Api.InputPeerChat({
+                chatId: peer.chatId,
+            })
+        } else if (peer instanceof Api.PeerChannel) {
+            try {
+                const channels = await this.invoke(new Api.channels.GetChannels({
+                    id: [new Api.InputChannel({
+                        channelId: peer.channelId,
+                        accessHash: bigInt.zero,
+                    })],
+                }));
+
+                return utils.getInputPeer(channels.chats[0])
+                // eslint-disable-next-line no-empty
+            } catch (e) {
+                console.log(e)
+            }
+        }
+        throw new Error(`Could not find the input entity for ${peer}.
+         Please read https://` +
+            'docs.telethon.dev/en/latest/concepts/entities.html to' +
+            ' find out more details.',
+        )
+    }
+
+    async _getEntityFromString(string: string) {
+        const phone = utils.parsePhone(string);
+        if (phone) {
+            try {
+                const result = await this.invoke(
+                    new Api.contacts.GetContacts({
+                        hash: 0
+                    }));
+                if (!(result instanceof Api.contacts.ContactsNotModified)) {
+                    for (const user of result.users) {
+                        if (!(user instanceof Api.User) || user.phone === phone) {
+                            return user
+                        }
+                    }
+                }
+
+            } catch (e) {
+                if (e.message === 'BOT_METHOD_INVALID') {
+                    throw new Error('Cannot get entity by phone number as a ' +
+                        'bot (try using integer IDs, not strings)')
+                }
+                throw e
+            }
+        } else if (['me', 'this'].includes(string.toLowerCase())) {
+            return this.getMe()
+        } else {
+            const {username, isInvite} = utils.parseUsername(string);
+            if (isInvite) {
+                const invite = await this.invoke(new Api.messages.CheckChatInvite({
+                    'hash': username,
+                }));
+                if (invite instanceof Api.ChatInvite) {
+                    throw new Error('Cannot get entity from a channel (or group) ' +
+                        'that you are not part of. Join the group and retry',
+                    )
+                } else if (invite instanceof Api.ChatInviteAlready) {
+                    return invite.chat
+                }
+            } else if (username) {
+                try {
+                    const result = await this.invoke(
+                        new Api.contacts.ResolveUsername({username: username}));
+                    const pid = utils.getPeerId(result.peer, false);
+                    if (result.peer instanceof Api.PeerUser) {
+                        for (const x of result.users) {
+                            if (x.id === pid) {
+                                return x
+                            }
+                        }
+                    } else {
+                        for (const x of result.chats) {
+                            if (x.id === pid) {
+                                return x
+                            }
+                        }
+                    }
+                } catch (e) {
+                    if (e.message === 'USERNAME_NOT_OCCUPIED') {
+                        throw new Error(`No user has "${username}" as username`)
+                    }
+                    throw e
+                }
+            }
+        }
+        throw new Error(`Cannot find any entity corresponding to "${string}"`)
+    }
+
+    async getPeerId(peer: EntityLike, addMark = false) {
+        if (typeof peer == 'number') {
+            return utils.getPeerId(peer, addMark);
+        }
+        if (typeof peer == 'string') {
+            peer = await this.getInputEntity(peer);
+        }
+
+        if (peer.SUBCLASS_OF_ID == 0x2d45687 || peer.SUBCLASS_OF_ID == 0xc91c90b6) {
+            peer = await this.getInputEntity(peer);
+        }
+        if (peer instanceof Api.InputPeerSelf) {
+            // @ts-ignore
+            peer = await this.getMe(true);
+        }
+        return utils.getPeerId(peer, addMark);
+
+
+    }
+
+    async _getPeer(peer: EntityLike) {
+        if (!peer) {
+            return undefined;
+        }
+        const [i, cls] = utils.resolveId(await this.getPeerId(peer));
+        return new cls({
+            userId: i,
+            channelId: i,
+            chatId: i
+        });
+    }
+}
+
+export interface UserMethods extends TelegramBaseClient {
+
+}

+ 0 - 74
gramjs/crypto/AuthKey.js

@@ -1,74 +0,0 @@
-const { sha1, toSignedLittleBuffer,readBufferFromBigInt, readBigIntFromBuffer } = require('../Helpers')
-const BinaryReader = require('../extensions/BinaryReader')
-const { sleep } = require('../Helpers')
-
-class AuthKey {
-
-    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 = 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
-        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)
-    }
-
-    async waitForKey() {
-        while (!this.keyId) {
-            await sleep(20)
-        }
-    }
-
-    getKey() {
-        return this._key
-    }
-
-    // TODO : This doesn't really fit here, it's only used in authentication
-
-    /**
-     * Calculates the new nonce hash based on the current class fields' values
-     * @param newNonce
-     * @param number
-     * @returns {bigint}
-     */
-    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 = (await sha1(data)).slice(4, 20)
-        return readBigIntFromBuffer(shaData, true, true)
-    }
-
-    equals(other) {
-        return other instanceof this.constructor && this._key && other.getKey() && other.getKey().equals(this._key)
-    }
-}
-
-module.exports = AuthKey

+ 80 - 0
gramjs/crypto/AuthKey.ts

@@ -0,0 +1,80 @@
+import {sha1, toSignedLittleBuffer, readBufferFromBigInt, readBigIntFromBuffer} from '../Helpers';
+import {BinaryReader} from '../extensions';
+import {sleep} from '../Helpers';
+
+export class AuthKey {
+    private _key?: Buffer;
+    private _hash?: Buffer;
+    private auxHash?: bigInt.BigInteger;
+    keyId?: bigInt.BigInteger;
+
+    constructor(value?: Buffer, hash?: Buffer) {
+        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?: Buffer | AuthKey) {
+        if (!value) {
+            this._key = this.auxHash = this.keyId = this._hash = undefined;
+            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;
+        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)
+    }
+
+    async waitForKey() {
+        while (!this.keyId) {
+            await sleep(20)
+        }
+    }
+
+    getKey() {
+        return this._key
+    }
+
+    // TODO : This doesn't really fit here, it's only used in authentication
+
+    /**
+     * Calculates the new nonce hash based on the current class fields' values
+     * @param newNonce
+     * @param number
+     * @returns {bigInt.BigInteger}
+     */
+    async calcNewNonceHash(newNonce: bigInt.BigInteger, number: number): Promise<bigInt.BigInteger> {
+        if (this.auxHash) {
+
+            const nonce = toSignedLittleBuffer(newNonce, 32);
+            const n = Buffer.alloc(1);
+            n.writeUInt8(number, 0);
+            const data = Buffer.concat([nonce,
+                Buffer.concat([n, readBufferFromBigInt(this.auxHash, 8, true)])]);
+
+            // Calculates the message key from the given data
+            const shaData = (await sha1(data)).slice(4, 20);
+            return readBigIntFromBuffer(shaData, true, true)
+        }
+        throw new Error("Auth key not set");
+    }
+
+    equals(other: AuthKey) {
+        return other instanceof this.constructor && this._key && Buffer.isBuffer(other.getKey()) && other.getKey()?.equals(this._key);
+    }
+}

+ 6 - 6
gramjs/crypto/CTR.js → gramjs/crypto/CTR.ts

@@ -1,7 +1,9 @@
-const crypto = require('./crypto')
+import * as crypto from './crypto';
 
-class CTR {
-    constructor(key, iv) {
+export class CTR {
+    private cipher: any;
+
+    constructor(key: Buffer, iv: Buffer) {
         if (!Buffer.isBuffer(key) || !Buffer.isBuffer(iv) || iv.length !== 16) {
             throw new Error('Key and iv need to be a buffer')
         }
@@ -9,9 +11,7 @@ class CTR {
         this.cipher = crypto.createCipheriv('AES-256-CTR', key, iv)
     }
 
-    encrypt(data) {
+    encrypt(data: any) {
         return Buffer.from(this.cipher.update(data))
     }
 }
-
-module.exports = CTR

+ 0 - 79
gramjs/crypto/Factorizator.js

@@ -1,79 +0,0 @@
-const BigInt = require('big-integer')
-const { modExp } = require('../Helpers')
-
-class Factorizator {
-    /**
-     * Calculates the greatest common divisor
-     * @param a {BigInteger}
-     * @param b {BigInteger}
-     * @returns {BigInteger}
-     */
-    static gcd(a, b) {
-        while (b.neq(BigInt.zero)) {
-            const temp = b
-            b = a.remainder(b)
-            a = temp
-        }
-        return a
-    }
-
-    /**
-     * 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 g = BigInt.one
-        let r = BigInt.one
-        let q = BigInt.one
-        let x = BigInt.zero
-        let ys = BigInt.zero
-        let k
-
-        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
-
-            while (k.lesser(r) && g.eq(BigInt.one)) {
-
-                ys = y
-                const 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)
-            }
-
-            r = r.multiply(2)
-        }
-
-
-        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
-                }
-            }
-        }
-        const p = g
-        q = pq.divide(g)
-        return p < q ? { p: p, q: q } : { p: q, q: p }
-    }
-}
-
-module.exports = Factorizator

+ 78 - 0
gramjs/crypto/Factorizator.ts

@@ -0,0 +1,78 @@
+import bigInt from "big-integer";
+import {modExp} from "../Helpers";
+
+export class Factorizator {
+    /**
+     * Calculates the greatest common divisor
+     * @param a {BigInteger}
+     * @param b {BigInteger}
+     * @returns {BigInteger}
+     */
+    static gcd(a:bigInt.BigInteger, b:bigInt.BigInteger) {
+        while (b.neq(bigInt.zero)) {
+            const temp = b;
+            b = a.remainder(b);
+            a = temp
+        }
+        return a
+    }
+
+    /**
+     * 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:bigInt.BigInteger) {
+        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 g = bigInt.one;
+        let r = bigInt.one;
+        let q = bigInt.one;
+        let x = bigInt.zero;
+        let ys = bigInt.zero;
+        let k;
+
+        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;
+
+            while (k.lesser(r) && g.eq(bigInt.one)) {
+
+                ys = y;
+                const 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)
+            }
+
+            r = r.multiply(2)
+        }
+
+
+        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
+                }
+            }
+        }
+        const p = g;
+        q = pq.divide(g);
+        return p < q ? { p: p, q: q } : { p: q, q: p }
+    }
+}
+

+ 10 - 8
gramjs/crypto/IGE.js → gramjs/crypto/IGE.ts

@@ -1,10 +1,12 @@
-const Helpers = require('../Helpers')
+const Helpers = require('../Helpers');
 
-const {IGE:aes_ige} = require('@cryptography/aes')
+const {IGE: aes_ige} = require('@cryptography/aes');
 
 class IGENEW {
-    constructor(key, iv) {
-        this.ige = new aes_ige(key,iv)
+    private ige: any;
+
+    constructor(key: Buffer, iv: Buffer) {
+        this.ige = new aes_ige(key, iv)
     }
 
     /**
@@ -12,7 +14,7 @@ class IGENEW {
      * @param cipherText {Buffer}
      * @returns {Buffer}
      */
-    decryptIge(cipherText) {
+    decryptIge(cipherText: Buffer) {
         return Helpers.convertToLittle(this.ige.decrypt(cipherText))
     }
 
@@ -21,8 +23,8 @@ class IGENEW {
      * @param plainText {Buffer}
      * @returns {Buffer}
      */
-    encryptIge(plainText) {
-        const padding = plainText.length % 16
+    encryptIge(plainText: Buffer) {
+        const padding = plainText.length % 16;
         if (padding) {
             plainText = Buffer.concat([plainText, Helpers.generateRandomBytes(16 - padding)])
         }
@@ -34,4 +36,4 @@ class IGENEW {
 
 }
 
-module.exports = IGENEW
+export {IGENEW as IGE}

+ 17 - 21
gramjs/crypto/RSA.js → gramjs/crypto/RSA.ts

@@ -1,29 +1,29 @@
-const BigInt = require('big-integer')
-const { readBigIntFromBuffer, readBufferFromBigInt, getByteArray, sha1, generateRandomBytes, modExp } = require('../Helpers')
+import {generateRandomBytes, modExp, readBigIntFromBuffer, readBufferFromBigInt, sha1} from "../Helpers";
+import bigInt from "big-integer";
 
 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'),
+    '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'),
+    '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'),
+    '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'),
+    'n': bigInt('24573455207957565047870011785254215390918912369814947541785386299516827003508659346069416840622922416779652050319196701077275060353178142796963682024347858398319926119639265555410256455471016400261630917813337515247954638555325280392998950756512879748873422896798579889820248358636937659872379948616822902110696986481638776226860777480684653756042166610633513404129518040549077551227082262066602286208338952016035637334787564972991208252928951876463555456715923743181359826124083963758009484867346318483872552977652588089928761806897223231500970500186019991032176060579816348322451864584743414550721639495547636008351'),
     'e': 65537,
-}]
+}];
 
-const _serverKeys = {}
+const _serverKeys = new Map();
 
-PUBLIC_KEYS.forEach(({ fingerprint, ...keyInfo }) => {
-    _serverKeys[readBigIntFromBuffer(fingerprint.slice(-8), true, true)] = keyInfo
-})
+PUBLIC_KEYS.forEach(({fingerprint, ...keyInfo}) => {
+    _serverKeys.set(readBigIntFromBuffer(Buffer.from(fingerprint.slice(-8)), true, true).toString(), keyInfo);
+});
 
 /**
  * Encrypts the given data known the fingerprint to be used
@@ -33,25 +33,21 @@ PUBLIC_KEYS.forEach(({ fingerprint, ...keyInfo }) => {
  * @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]
+export async function encrypt(fingerprint: bigInt.BigInteger, data: Buffer) {
+    const key = _serverKeys.get(fingerprint.toString());
     if (!key) {
         return undefined
     }
 
     // len(sha1.digest) is always 20, so we're left with 255 - 20 - x padding
-    const rand = generateRandomBytes(235 - data.length)
+    const rand = generateRandomBytes(235 - data.length);
 
-    const toEncrypt = Buffer.concat([await sha1(data), data, rand])
+    const toEncrypt = Buffer.concat([await sha1(data), data, rand]);
 
     // 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)
+    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,
-}

+ 0 - 123
gramjs/crypto/crypto.js

@@ -1,123 +0,0 @@
-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,
-}

+ 128 - 0
gramjs/crypto/crypto.ts

@@ -0,0 +1,128 @@
+import {default as AES} from '@cryptography/aes';
+import {i2ab, ab2i} from './converters';
+import {getWords} from './words';
+
+export class Counter {
+    _counter: Buffer;
+
+    constructor(initialValue: any) {
+        this._counter = Buffer.from(initialValue)
+
+    }
+
+    increment() {
+        for (let i = 15; i >= 0; i--) {
+            if (this._counter[i] === 255) {
+                this._counter[i] = 0
+            } else {
+                this._counter[i]++;
+                break
+            }
+        }
+    }
+}
+
+export class CTR {
+    private _counter: Counter;
+    private _remainingCounter?: Buffer;
+    private _remainingCounterIndex: number;
+    private _aes: AES;
+
+    constructor(key: Buffer, counter: any) {
+
+        if (!(counter instanceof Counter)) {
+            counter = new Counter(counter)
+        }
+
+        this._counter = counter;
+
+        this._remainingCounter = undefined;
+        this._remainingCounterIndex = 16;
+
+        this._aes = new AES(getWords(key))
+    }
+
+    update(plainText: any) {
+        return this.encrypt(plainText)
+    }
+
+    encrypt(plainText: any) {
+        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()
+            }
+            if (this._remainingCounter) {
+                encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++]
+            }
+        }
+
+        return encrypted
+    }
+}
+
+// endregion
+export function createDecipheriv(algorithm: string, key: Buffer, iv: Buffer) {
+    if (algorithm.includes('ECB')) {
+        throw new Error('Not supported')
+    } else {
+        return new CTR(key, iv)
+    }
+}
+
+export function createCipheriv(algorithm: string, key: Buffer, iv: Buffer) {
+    if (algorithm.includes('ECB')) {
+        throw new Error('Not supported')
+    } else {
+        return new CTR(key, iv)
+    }
+}
+
+export function randomBytes(count: Buffer) {
+    const bytes = new Uint8Array(count);
+    crypto.getRandomValues(bytes);
+    return bytes
+}
+
+export class Hash {
+    private algorithm: string;
+    private data?: Uint8Array;
+
+    constructor(algorithm: string) {
+        this.algorithm = algorithm
+
+    }
+
+    update(data: Buffer) {
+        //We shouldn't be needing new Uint8Array but it doesn't
+        //work without it
+        this.data = new Uint8Array(data)
+    }
+
+    async digest() {
+        if (this.data) {
+            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))
+            }
+        }
+
+    }
+}
+
+export async function pbkdf2(password:any, salt:any, iterations:any) {
+    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))
+}
+
+export function createHash(algorithm:string) {
+    return new Hash(algorithm)
+}

+ 36 - 0
gramjs/define.d.ts

@@ -0,0 +1,36 @@
+import {Button} from "./tl/custom/button";
+import {Api} from "./tl/api";
+
+type ValueOf<T> = T[keyof T];
+type Phone = string;
+type Username = string;
+type PeerID = number;
+type Entity = Api.User | Api.Chat | Api.Channel;
+type FullEntity = Api.UserFull | Api.messages.ChatFull | Api.ChatFull | Api.ChannelFull;
+type PeerLike = Api.TypePeer | Api.TypeInputPeer | Entity | FullEntity
+type EntityLike = Phone | Username | PeerID | Api.TypePeer | Api.TypeInputPeer | Entity | FullEntity ;
+
+type EntitiesLike = EntityLike[];
+type MessageIDLike = number | types.Message | types.TypeInputMessage;
+type MessageLike = string | Api.Message;
+
+type LocalPath = string;
+type ExternalUrl = string;
+type BotFileID = string;
+
+type FileLike =
+    LocalPath |
+    ExternalUrl |
+    BotFileID |
+    Buffer |
+    Api.TypeMessageMedia |
+    Api.TypeInputFile |
+    Api.TypeInputFileLocation
+type ProgressCallback = (total: number, downloaded: number) => void;
+type ButtonLike = Api.TypeKeyboardButton | Button;
+
+type MarkupLike = types.TypeReplyMarkup |
+    ButtonLike |
+    ButtonLike[] |
+    ButtonLike[][];
+type DateLike = number;

+ 72 - 0
gramjs/entityCache.ts

@@ -0,0 +1,72 @@
+// Which updates have the following fields?
+
+import {getInputPeer, getPeerId, isArrayLike} from "./Utils";
+import {Api} from "./tl/api";
+
+export class EntityCache {
+    private cacheMap: Map<number, any>;
+
+    constructor() {
+        this.cacheMap = new Map();
+    }
+
+    add(entities: any) {
+
+        const temp = [];
+        if (!isArrayLike(entities)) {
+
+            if ('chats' in entities) {
+                temp.push(...entities.chats)
+            }
+            if ('users' in entities) {
+                temp.push(...entities.users)
+            }
+            if ('user' in entities) {
+                temp.push(entities.user)
+            }
+            if (temp.length) {
+                entities = temp;
+            } else {
+                return;
+            }
+        }
+        for (const entity of entities) {
+            try {
+                const pid = getPeerId(entity);
+                if (!this.cacheMap.has(pid)) {
+                    this.cacheMap.set(pid, getInputPeer(entity));
+                }
+            } catch (e) {
+
+            }
+        }
+    }
+
+    get(item: any) {
+        if (!(typeof item === 'number') || item < 0) {
+            let res;
+            try {
+                res = this.cacheMap.get(getPeerId(item));
+                if (res) {
+                    return res;
+                }
+            } catch (e) {
+                throw new Error('Invalid key will not have entity')
+            }
+        }
+        for (const cls of [Api.PeerUser, Api.PeerChat, Api.PeerChannel]) {
+            // TODO remove these "as"
+            const result = this.cacheMap.get(getPeerId(new cls({
+                userId: item as number,
+                chatId: item as number,
+                channelId: item as number
+            })));
+            if (result) {
+                return result;
+            }
+        }
+        throw new Error('No cached entity for the given key');
+    }
+
+
+}

+ 43 - 42
gramjs/errors/Common.js → gramjs/errors/Common.ts

@@ -3,10 +3,12 @@
  */
 
 
+import {Api} from "../tl/api";
+
 /**
  * Occurs when a read operation was cancelled.
  */
-class ReadCancelledError extends Error {
+export class ReadCancelledError extends Error {
     constructor() {
         super('The read operation was cancelled.')
     }
@@ -16,15 +18,18 @@ class ReadCancelledError extends Error {
  * Occurs when a type is not found, for example,
  * when trying to read a TLObject with an invalid constructor code.
  */
-class TypeNotFoundError extends Error {
-    constructor(invalidConstructorId, remaining) {
+export class TypeNotFoundError extends Error {
+    invalidConstructorId: number;
+    remaining: Buffer;
+
+    constructor(invalidConstructorId: number, remaining: Buffer) {
         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.length}`)
+         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}`)
+            alert(`Missing MTProto Entity: Please, make sure to add TL definition for ID ${invalidConstructorId}`);
         }
-        this.invalidConstructorId = invalidConstructorId
+        this.invalidConstructorId = invalidConstructorId;
         this.remaining = remaining
     }
 }
@@ -33,11 +38,14 @@ class TypeNotFoundError extends Error {
  * Occurs when using the TCP full mode and the checksum of a received
  * packet doesn't match the expected checksum.
  */
-class InvalidChecksumError extends Error {
-    constructor(checksum, validChecksum) {
-        super(`Invalid checksum (${checksum} when ${validChecksum} was expected). This packet should be skipped.`)
-        this.checksum = checksum
-        this.validChecksum = validChecksum
+export class InvalidChecksumError extends Error {
+    private checksum: number;
+    private validChecksum: number;
+
+    constructor(checksum: number, validChecksum: number) {
+        super(`Invalid checksum (${checksum} when ${validChecksum} was expected). This packet should be skipped.`);
+        this.checksum = checksum;
+        this.validChecksum = validChecksum;
     }
 }
 
@@ -45,25 +53,28 @@ class InvalidChecksumError extends Error {
  * Occurs when the buffer is invalid, and may contain an HTTP error code.
  * For instance, 404 means "forgotten/broken authorization key", while
  */
-class InvalidBufferError extends Error {
-    constructor(payload) {
-        let code = null
+export class InvalidBufferError extends Error {
+    private code?: number;
+    private payload: Buffer;
+
+    constructor(payload: Buffer) {
+        let code = undefined;
         if (payload.length === 4) {
-            code = -payload.readInt32LE(0)
+            code = -payload.readInt32LE(0);
             super(`Invalid response buffer (HTTP code ${code})`)
         } else {
             super(`Invalid response buffer (too short ${payload})`)
         }
-        this.code = code
-        this.payload = payload
+        this.code = code;
+        this.payload = payload;
     }
 }
 
 /**
  * Generic security error, mostly used when generating a new AuthKey.
  */
-class SecurityError extends Error {
-    constructor(...args) {
+export class SecurityError extends Error {
+    constructor(...args: any) {
         if (!args.length) {
             args = ['A security check failed.']
         }
@@ -75,7 +86,7 @@ class SecurityError extends Error {
  * Occurs when there's a hash mismatch between the decrypted CDN file
  * and its expected hash.
  */
-class CdnFileTamperedError extends SecurityError {
+export class CdnFileTamperedError extends SecurityError {
     constructor() {
         super('The CDN file has been altered and its download cancelled.')
     }
@@ -84,8 +95,9 @@ class CdnFileTamperedError extends SecurityError {
 /**
  * Occurs when handling a badMessageNotification
  */
-class BadMessageError extends Error {
+export class BadMessageError extends Error {
     static ErrorMessages = {
+
         16:
             'msg_id too low (most likely, client time is wrong it would be worthwhile to ' +
             'synchronize it using msg_id notifications and re-send the original message ' +
@@ -123,26 +135,15 @@ class BadMessageError extends Error {
             'the correct salt, and the message is to be re-sent with it).',
 
         64: 'Invalid container.',
+    };
+    private code: number;
+
+    constructor(request: Api.AnyRequest, code: number) {
+        let errorMessage = (BadMessageError.ErrorMessages as any)[code] ||
+            `Unknown error code (this should not happen): ${code}.`;
+        errorMessage += `  Caused by ${request.className}`;
+        super(errorMessage);
+        this.message = errorMessage;
+        this.code = 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
-    }
-}
-
-// TODO : Support multi errors.
-
-module.exports = {
-    ReadCancelledError,
-    TypeNotFoundError,
-    InvalidChecksumError,
-    InvalidBufferError,
-    SecurityError,
-    CdnFileTamperedError,
-    BadMessageError,
 }

+ 31 - 40
gramjs/errors/RPCBaseErrors.js → gramjs/errors/RPCBaseErrors.ts

@@ -1,19 +1,23 @@
 /**
  * Base class for all Remote Procedure Call errors.
  */
-class RPCError extends Error {
-    constructor(message, request, code = null) {
+import {Api} from "../tl";
+
+export class RPCError extends Error {
+    protected code: number | undefined;
+
+    constructor(message: string, request: Api.AnyRequest, code?: number) {
         super(
             'RPCError {0}: {1}{2}'
-                .replace('{0}', code)
-                .replace('{1}', message)
+                .replace('{0}', code?.toString() || '')
+                .replace('{1}', message||'')
                 .replace('{2}', RPCError._fmtRequest(request)),
-        )
-        this.code = code
+        );
+        this.code = code;
         this.message = message
     }
 
-    static _fmtRequest(request) {
+    static _fmtRequest(request: Api.AnyRequest) {
         // TODO fix this
         if (request) {
             return ` (caused by ${request.className})`
@@ -26,10 +30,10 @@ class RPCError extends Error {
 /**
  * The request must be repeated, but directed to a different data center.
  */
-class InvalidDCError extends RPCError {
-    constructor(request, message, code) {
-        super(message, request, code)
-        this.code = code || 303
+export class InvalidDCError extends RPCError {
+    constructor(message:string,request:Api.AnyRequest, code?: number) {
+        super(message, request, code);
+        this.code = code || 303;
         this.message = message || 'ERROR_SEE_OTHER'
     }
 }
@@ -39,8 +43,8 @@ class InvalidDCError extends RPCError {
  * using a form and contains user generated data, the user should be
  * notified that the data must be corrected before the query is repeated.
  */
-class BadRequestError extends RPCError {
-    code = 400
+export class BadRequestError extends RPCError {
+    code = 400;
     message = 'BAD_REQUEST'
 }
 
@@ -48,8 +52,8 @@ class BadRequestError extends RPCError {
  * There was an unauthorized attempt to use functionality available only
  * to authorized users.
  */
-class UnauthorizedError extends RPCError {
-    code = 401
+export class UnauthorizedError extends RPCError {
+    code = 401;
     message = 'UNAUTHORIZED'
 }
 
@@ -57,16 +61,16 @@ class UnauthorizedError extends RPCError {
  * Privacy violation. For example, an attempt to write a message to
  * someone who has blacklisted the current user.
  */
-class ForbiddenError extends RPCError {
-    code = 403
+export class ForbiddenError extends RPCError {
+    code = 403;
     message = 'FORBIDDEN'
 }
 
 /**
  * An attempt to invoke a non-existent object, such as a method.
  */
-class NotFoundError extends RPCError {
-    code = 404
+export class NotFoundError extends RPCError {
+    code = 404;
     message = 'NOT_FOUND'
 }
 
@@ -74,8 +78,8 @@ class NotFoundError extends RPCError {
  * Errors related to invalid authorization key, like
  * AUTH_KEY_DUPLICATED which can cause the connection to fail.
  */
-class AuthKeyError extends RPCError {
-    code = 406
+export class AuthKeyError extends RPCError {
+    code = 406;
     message = 'AUTH_KEY'
 }
 
@@ -85,8 +89,8 @@ class AuthKeyError extends RPCError {
  * attempt to request a large number of text messages (SMS) for the same
  * phone number.
  */
-class FloodError extends RPCError {
-    code = 420
+export class FloodError extends RPCError {
+    code = 420;
     message = 'FLOOD'
 }
 
@@ -95,8 +99,8 @@ class FloodError extends RPCError {
  * for example, there was a disruption while accessing a database or file
  * storage
  */
-class ServerError extends RPCError {
-    code = 500 // Also witnessed as -500
+export class ServerError extends RPCError {
+    code = 500; // Also witnessed as -500
     message = 'INTERNAL'
 }
 
@@ -104,20 +108,7 @@ class ServerError extends RPCError {
  * Clicking the inline buttons of bots that never (or take to long to)
  * call ``answerCallbackQuery`` will result in this "special" RPCError.
  */
-class TimedOutError extends RPCError {
-    code = 503 // Only witnessed as -503
+export class TimedOutError extends RPCError {
+    code = 503; // Only witnessed as -503
     message = 'Timeout'
 }
-
-module.exports = {
-    RPCError,
-    InvalidDCError,
-    BadRequestError,
-    UnauthorizedError,
-    ForbiddenError,
-    NotFoundError,
-    AuthKeyError,
-    FloodError,
-    ServerError,
-    TimedOutError,
-}

+ 52 - 48
gramjs/errors/RPCErrorList.js → gramjs/errors/RPCErrorList.ts

@@ -1,71 +1,85 @@
-const { RPCError, InvalidDCError, FloodError } = require('./RPCBaseErrors')
+import {RPCError, InvalidDCError, FloodError} from './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)
+export class UserMigrateError extends InvalidDCError {
+    public newDc: number;
+
+    constructor(args: any) {
+        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), 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)
+export class PhoneMigrateError extends InvalidDCError {
+    public newDc: number;
+
+    constructor(args: any) {
+        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), 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)
+export class SlowModeWaitError extends FloodError {
+    public seconds: number;
+
+    constructor(args: any) {
+        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), 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)
+export class FloodWaitError extends FloodError {
+    public seconds: number;
+
+    constructor(args: any) {
+        const seconds = Number(args.capture || 0);
+        super(`A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request), 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)
+export class FloodTestPhoneWaitError extends FloodError {
+    public seconds: number;
+
+    constructor(args: any) {
+        const seconds = Number(args.capture || 0);
+        super(`A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request), 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)
+export class FileMigrateError extends InvalidDCError {
+    public newDc: number;
+
+    constructor(args: any) {
+        const newDc = Number(args.capture || 0);
+        super(`The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request), 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)
+export class NetworkMigrateError extends InvalidDCError {
+    public newDc: number;
+
+    constructor(args: any) {
+        const newDc = Number(args.capture || 0);
+        super(`The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request), args.request);
+        this.message = `The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request);
         this.newDc = newDc
     }
 }
 
-const rpcErrorRe = [
+export const rpcErrorRe = new Map<RegExp, any>([
     [/FILE_MIGRATE_(\d+)/, FileMigrateError],
     [/FLOOD_TEST_PHONE_WAIT_(\d+)/, FloodTestPhoneWaitError],
     [/FLOOD_WAIT_(\d+)/, FloodWaitError],
@@ -74,14 +88,4 @@ const rpcErrorRe = [
     [/USER_MIGRATE_(\d+)/, UserMigrateError],
     [/NETWORK_MIGRATE_(\d+)/, NetworkMigrateError],
 
-]
-module.exports = {
-    rpcErrorRe,
-    FileMigrateError,
-    FloodTestPhoneWaitError,
-    FloodWaitError,
-    PhoneMigrateError,
-    SlowModeWaitError,
-    UserMigrateError,
-    NetworkMigrateError
-}
+]);

+ 11 - 12
gramjs/errors/index.js → gramjs/errors/index.ts

@@ -4,24 +4,23 @@
  * @param request the request that caused this error
  * @constructor the RPCError as a Python exception that represents this error
  */
-const { RPCError } = require('./RPCBaseErrors')
-const { rpcErrorRe } = require('./RPCErrorList')
+import {Api} from "../tl";
 
-function RPCMessageToError(rpcError, request) {
+import {RPCError} from './RPCBaseErrors';
+import {rpcErrorRe} from './RPCErrorList';
+
+export function RPCMessageToError(rpcError: Api.RpcError, request: Api.AnyRequest) {
     for (const [msgRegex, Cls] of rpcErrorRe) {
-        const m = rpcError.errorMessage.match(msgRegex)
+        const m = rpcError.errorMessage.match(msgRegex);
         if (m) {
-            const capture = m.length === 2 ? parseInt(m[1]) : null
-            return new Cls({ request: request, capture: capture })
+            const capture = m.length === 2 ? parseInt(m[1]) : null;
+            return new Cls({request: request, capture: capture})
         }
     }
 
     return new RPCError(rpcError.errorMessage, request)
 }
 
-module.exports = {
-    RPCMessageToError,
-    ...require('./Common'),
-    ...require('./RPCBaseErrors'),
-    ...require('./RPCErrorList'),
-}
+export * from './Common';
+export * from './RPCBaseErrors';
+export * from './RPCErrorList';

+ 0 - 22
gramjs/events/common.js

@@ -1,22 +0,0 @@
-class EventBuilder {
-    constructor(args = {
-        chats: null, blacklistChats: null, func: null,
-    },
-    ) {
-        this.chats = args.chats
-        this.blacklistChats = Boolean(args.blacklistChats)
-        this.resolved = false
-        this.func = args.func
-    }
-
-    build(update, others = null) {
-
-    }
-}
-
-class EventCommon {
-
-}
-
-
-module.exports = { EventBuilder, EventCommon }

+ 27 - 0
gramjs/events/common.ts

@@ -0,0 +1,27 @@
+import {Api} from "../tl";
+
+export class EventBuilder {
+    private chats: undefined;
+    private blacklistChats: boolean;
+    private resolved: boolean;
+    private func: undefined;
+
+    constructor({
+                    chats = undefined, blacklistChats = false, func = undefined,
+                },
+    ) {
+        this.chats = chats;
+        this.blacklistChats = blacklistChats;
+        this.resolved = false;
+        this.func = func
+    }
+
+    build(update: Api.Updates, others = null) {
+
+    }
+}
+
+export class EventCommon {
+
+}
+

+ 0 - 30
gramjs/extensions/AsyncQueue.js

@@ -1,30 +0,0 @@
-class AsyncQueue {
-    constructor() {
-        this._queue = []
-        this.canGet = new Promise(resolve => {
-            this.resolveGet = resolve
-        })
-        this.canPush = true
-    }
-
-    async push(value) {
-        await this.canPush
-        this._queue.push(value)
-        this.resolveGet(true)
-        this.canPush = new Promise(resolve => {
-            this.resolvePush = resolve
-        })
-    }
-
-    async pop() {
-        await this.canGet
-        const returned = this._queue.pop()
-        this.resolvePush(true)
-        this.canGet = new Promise(resolve => {
-            this.resolveGet = resolve
-        })
-        return returned
-    }
-}
-
-module.exports = AsyncQueue

+ 37 - 0
gramjs/extensions/AsyncQueue.ts

@@ -0,0 +1,37 @@
+export class AsyncQueue {
+    public _queue: any[];
+    private canGet: Promise<unknown>;
+    private resolveGet: ((value?: any) => void);
+    private canPush: boolean | Promise<boolean>;
+    private resolvePush: ((value?: any) => void);
+
+    constructor() {
+        this._queue = []
+        this.canPush = true
+        this.resolvePush =value => {};
+        this.resolveGet =value => {};
+        this.canGet = new Promise(resolve => {
+            this.resolveGet = resolve
+        })
+
+    }
+
+    async push(value: any) {
+        await this.canPush;
+        this._queue.push(value);
+        this.resolveGet(true);
+        this.canPush = new Promise(resolve => {
+            this.resolvePush = resolve
+        })
+    }
+
+    async pop() {
+        await this.canGet;
+        const returned = this._queue.pop();
+        this.resolvePush(true)
+        this.canGet = new Promise(resolve => {
+            this.resolveGet = resolve
+        });
+        return returned
+    }
+}

+ 47 - 48
gramjs/extensions/BinaryReader.js → gramjs/extensions/BinaryReader.ts

@@ -1,16 +1,23 @@
-const { TypeNotFoundError } = require('../errors/Common')
-const { coreObjects } = require('../tl/core')
-const { tlobjects } = require('../tl/AllTLObjects')
-const { readBigIntFromBuffer } = require('../Helpers')
+import {TypeNotFoundError} from '../errors';
+import {coreObjects} from '../tl/core';
+
+import {tlobjects} from '../tl/AllTLObjects';
+import {Api} from "../tl";
+import {readBigIntFromBuffer} from "../Helpers";
+
+
+export class BinaryReader {
+    private stream: Buffer;
+    private _last?: Buffer;
+    private offset: number;
 
-class BinaryReader {
     /**
      * Small utility class to read binary data.
      * @param data {Buffer}
      */
-    constructor(data) {
-        this.stream = data
-        this._last = null
+    constructor(data: Buffer) {
+        this.stream = data;
+        this._last = undefined;
         this.offset = 0
     }
 
@@ -30,13 +37,13 @@ class BinaryReader {
      * @param signed {Boolean}
      */
     readInt(signed = true) {
-        let res
+        let res;
         if (signed) {
             res = this.stream.readInt32LE(this.offset)
         } else {
             res = this.stream.readUInt32LE(this.offset)
         }
-        this.offset += 4
+        this.offset += 4;
         return res
     }
 
@@ -71,8 +78,8 @@ class BinaryReader {
      * @param bits
      * @param signed {Boolean}
      */
-    readLargeInt(bits, signed = true) {
-        const buffer = this.read(Math.floor(bits / 8))
+    readLargeInt(bits: number, signed = true) {
+        const buffer = this.read(Math.floor(bits / 8));
         return readBigIntFromBuffer(buffer, true, signed)
     }
 
@@ -84,14 +91,14 @@ class BinaryReader {
         if (length === -1) {
             length = this.stream.length - this.offset
         }
-        const result = this.stream.slice(this.offset, this.offset + length)
-        this.offset += length
+        const result = this.stream.slice(this.offset, this.offset + length);
+        this.offset += length;
         if (result.length !== length) {
             throw Error(
                 `No more data left to read (need ${length}, got ${result.length}: ${result}); last read ${this._last}`,
             )
         }
-        this._last = result
+        this._last = result;
         return result
     }
 
@@ -112,20 +119,20 @@ class BinaryReader {
      * @returns {Buffer}
      */
     tgReadBytes() {
-        const firstByte = this.readByte()
-        let padding
-        let length
+        const firstByte = this.readByte();
+        let padding;
+        let length;
         if (firstByte === 254) {
-            length = this.readByte() | (this.readByte() << 8) | (this.readByte() << 16)
+            length = this.readByte() | (this.readByte() << 8) | (this.readByte() << 16);
             padding = length % 4
         } else {
-            length = firstByte
+            length = firstByte;
             padding = (length + 1) % 4
         }
-        const data = this.read(length)
+        const data = this.read(length);
 
         if (padding > 0) {
-            padding = 4 - padding
+            padding = 4 - padding;
             this.read(padding)
         }
 
@@ -145,7 +152,7 @@ class BinaryReader {
      * @returns {boolean}
      */
     tgReadBool() {
-        const value = this.readInt(false)
+        const value = this.readInt(false);
         if (value === 0x997275b5) {
             // boolTrue
             return true
@@ -153,7 +160,7 @@ class BinaryReader {
             // boolFalse
             return false
         } else {
-            throw new Error(`Invalid boolean code ${value.toString('16')}`)
+            throw new Error(`Invalid boolean code ${value.toString(16)}`)
         }
     }
 
@@ -163,22 +170,22 @@ class BinaryReader {
      * @returns {Date}
      */
     tgReadDate() {
-        const value = this.readInt()
+        const value = this.readInt();
         return new Date(value * 1000)
     }
 
     /**
      * Reads a Telegram object.
      */
-    tgReadObject() {
-        const constructorId = this.readInt(false)
-        let clazz = tlobjects[constructorId]
+    tgReadObject(): any {
+        const constructorId = this.readInt(false);
+        let clazz = tlobjects[constructorId];
         if (clazz === undefined) {
             /**
              * The class was None, but there's still a
              * chance of it being a manually parsed value like bool!
              */
-            const value = constructorId
+            const value = constructorId;
             if (value === 0x997275b5) {
                 // boolTrue
                 return true
@@ -187,22 +194,22 @@ class BinaryReader {
                 return false
             } else if (value === 0x1cb5c415) {
                 // Vector
-                const temp = []
-                const length = this.readInt()
+                const temp = [];
+                const length = this.readInt();
                 for (let i = 0; i < length; i++) {
                     temp.push(this.tgReadObject())
                 }
                 return temp
             }
 
-            clazz = coreObjects[constructorId]
+            clazz = coreObjects.get(constructorId);
 
             if (clazz === undefined) {
                 // If there was still no luck, give up
-                this.seek(-4) // Go back
-                const pos = this.tellPosition()
-                const error = new TypeNotFoundError(constructorId, this.read())
-                this.setPosition(pos)
+                this.seek(-4); // Go back
+                const pos = this.tellPosition();
+                const error = new TypeNotFoundError(constructorId, this.read());
+                this.setPosition(pos);
                 throw error
             }
         }
@@ -217,8 +224,8 @@ class BinaryReader {
         if (this.readInt(false) !== 0x1cb5c415) {
             throw new Error('Invalid constructor code, vector was expected')
         }
-        const count = this.readInt()
-        const temp = []
+        const count = this.readInt();
+        const temp = [];
         for (let i = 0; i < count; i++) {
             temp.push(this.tgReadObject())
         }
@@ -227,12 +234,6 @@ class BinaryReader {
 
     // endregion
 
-    /**
-     * Closes the reader.
-     */
-    close() {
-        this.stream = null
-    }
 
     // region Position related
 
@@ -248,7 +249,7 @@ class BinaryReader {
      * Sets the current position on the stream.
      * @param position
      */
-    setPosition(position) {
+    setPosition(position: number) {
         this.offset = position
     }
 
@@ -257,11 +258,9 @@ class BinaryReader {
      * The offset may be negative.
      * @param offset
      */
-    seek(offset) {
+    seek(offset: number) {
         this.offset += offset
     }
 
     // endregion
 }
-
-module.exports = BinaryReader

+ 5 - 5
gramjs/extensions/BinaryWriter.js → gramjs/extensions/BinaryWriter.ts

@@ -1,9 +1,11 @@
-class BinaryWriter {
-    constructor(stream) {
+export class BinaryWriter {
+    private _stream: Buffer;
+
+    constructor(stream: Buffer) {
         this._stream = stream
     }
 
-    write(buffer) {
+    write(buffer: Buffer) {
         this._stream = Buffer.concat([this._stream, buffer])
     }
 
@@ -11,5 +13,3 @@ class BinaryWriter {
         return this._stream
     }
 }
-
-module.exports = BinaryWriter

+ 20 - 21
gramjs/extensions/Logger.js → gramjs/extensions/Logger.ts

@@ -1,17 +1,18 @@
-let _level = null
+import {IS_NODE} from "../Helpers";
 
-class Logger {
-    static levels = ['error', 'warn', 'info', 'debug']
+let _level: string | undefined = undefined;
 
-    constructor(level) {
+export class Logger {
+    static levels = ['error', 'warn', 'info', 'debug'];
+    private isBrowser: boolean;
+    private colors: { warn: string; debug: string; start: string; end: string; error: string; info: string };
+    private messageFormat: string;
+
+    constructor(level?: string) {
         if (!_level) {
             _level = level || 'debug'
         }
-
-        this.isBrowser = typeof process === 'undefined' ||
-            process.type === 'renderer' ||
-            process.browser === true ||
-            process.__nwjs
+        this.isBrowser = !IS_NODE;
         if (!this.isBrowser) {
             this.colors = {
                 start: '\x1b[2m',
@@ -39,45 +40,45 @@ class Logger {
      * @param level {string}
      * @returns {boolean}
      */
-    canSend(level) {
-        return (Logger.levels.indexOf(_level) >= Logger.levels.indexOf(level))
+    canSend(level: string) {
+        return _level ? (Logger.levels.indexOf(_level) >= Logger.levels.indexOf(level)) : false;
     }
 
     /**
      * @param message {string}
      */
-    warn(message) {
+    warn(message: string) {
         this._log('warn', message, this.colors.warn)
     }
 
     /**
      * @param message {string}
      */
-    info(message) {
+    info(message: string) {
         this._log('info', message, this.colors.info)
     }
 
     /**
      * @param message {string}
      */
-    debug(message) {
+    debug(message: string) {
         this._log('debug', message, this.colors.debug)
     }
 
     /**
      * @param message {string}
      */
-    error(message) {
+    error(message: string) {
         this._log('error', message, this.colors.error)
     }
 
-    format(message, level) {
+    format(message: string, level: string) {
         return this.messageFormat.replace('%t', new Date().toISOString())
             .replace('%l', level.toUpperCase())
             .replace('%m', message)
     }
 
-    static setLevel(level) {
+    static setLevel(level: string) {
         _level = level
     }
 
@@ -86,8 +87,8 @@ class Logger {
      * @param message {string}
      * @param color {string}
      */
-    _log(level, message, color) {
-        if (!_level){
+    _log(level: string, message: string, color: string) {
+        if (!_level) {
             return
         }
         if (this.canSend(level)) {
@@ -101,5 +102,3 @@ class Logger {
         }
     }
 }
-
-module.exports = Logger

+ 0 - 96
gramjs/extensions/MessagePacker.js

@@ -1,96 +0,0 @@
-const MessageContainer = require('../tl/core/MessageContainer')
-const TLMessage = require('../tl/core/TLMessage')
-const BinaryWriter = require('../extensions/BinaryWriter')
-
-class MessagePacker {
-    constructor(state, logger) {
-        this._state = state
-        this._queue = []
-        this._ready = new Promise((resolve => {
-            this.setReady = resolve
-        }))
-        this._log = logger
-    }
-
-    values() {
-        return this._queue
-    }
-
-    append(state) {
-        this._queue.push(state)
-        this.setReady(true)
-    }
-
-    extend(states) {
-        for (const state of states) {
-            this._queue.push(state)
-        }
-        this.setReady(true)
-    }
-
-    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
-        let buffer = new BinaryWriter(Buffer.alloc(0))
-
-        const batch = []
-        let size = 0
-
-        while (this._queue.length && batch.length <= MessageContainer.MAXIMUM_LENGTH) {
-            const state = this._queue.shift()
-            size += state.data.length + TLMessage.SIZE_OVERHEAD
-            if (size <= MessageContainer.MAXIMUM_SIZE) {
-                let afterId
-                if (state.after) {
-                    afterId = state.after.msgId
-                }
-                state.msgId = await this._state.writeDataAsMessage(
-                    buffer, state.data, state.request.classType === 'request',
-                    afterId,
-                )
-                this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.className || state.request.constructor.name}`)
-                batch.push(state)
-                continue
-            }
-            if (batch.length) {
-                this._queue.unshift(state)
-                break
-            }
-            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
-        }
-        if (!batch.length) {
-            return null
-        }
-        if (batch.length > 1) {
-            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,
-            )
-            for (const s of batch) {
-                s.containerId = containerId
-            }
-        }
-
-        data = buffer.getValue()
-        return { batch, data }
-    }
-}
-
-module.exports = MessagePacker

+ 107 - 0
gramjs/extensions/MessagePacker.ts

@@ -0,0 +1,107 @@
+import {MessageContainer} from '../tl/core';
+import {TLMessage} from '../tl/core';
+import {BinaryWriter} from './BinaryWriter';
+import {Api} from "../tl";
+import {MTProtoState} from "../network/MTProtoState";
+import {RequestState} from "../network/RequestState";
+
+export class MessagePacker {
+    private _state: any;
+    private _queue: any[];
+    private _ready: Promise<unknown>;
+    private setReady: ((value?: any) => void) | undefined;
+    private _log: any;
+
+    constructor(state: MTProtoState, logger: any) {
+        this._state = state;
+        this._queue = [];
+        this._ready = new Promise((resolve => {
+            this.setReady = resolve
+        }));
+        this._log = logger
+    }
+
+    values() {
+        return this._queue
+    }
+
+    append(state: RequestState) {
+        this._queue.push(state);
+        if (this.setReady) {
+            this.setReady(true);
+        }
+    }
+
+    extend(states: any[]) {
+        for (const state of states) {
+            this._queue.push(state)
+        }
+        if (this.setReady) {
+            this.setReady(true);
+        }
+    }
+
+    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;
+        let buffer = new BinaryWriter(Buffer.alloc(0));
+
+        const batch = [];
+        let size = 0;
+
+        while (this._queue.length && batch.length <= MessageContainer.MAXIMUM_LENGTH) {
+            const state = this._queue.shift();
+            size += state.data.length + TLMessage.SIZE_OVERHEAD;
+            if (size <= MessageContainer.MAXIMUM_SIZE) {
+                let afterId;
+                if (state.after) {
+                    afterId = state.after.msgId
+                }
+                state.msgId = await this._state.writeDataAsMessage(
+                    buffer, state.data, state.request.classType === 'request',
+                    afterId,
+                );
+                this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.className || state.request.constructor.name}`);
+                batch.push(state);
+                continue
+            }
+            if (batch.length) {
+                this._queue.unshift(state);
+                break
+            }
+            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;
+
+        }
+        if (!batch.length) {
+            return null
+        }
+        if (batch.length > 1) {
+            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,
+            );
+            for (const s of batch) {
+                s.containerId = containerId
+            }
+        }
+
+        data = buffer.getValue();
+        return {batch, data}
+    }
+}

+ 0 - 117
gramjs/extensions/PromisedNetSockets.js

@@ -1,117 +0,0 @@
-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) {
-            throw closeError
-        }
-        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) {
-            this.canRead = new Promise(resolve => {
-                this.resolveRead = resolve
-            })
-        }
-
-        return toReturn
-    }
-
-    async readAll() {
-        if (this.closed || !await this.canRead) {
-            throw closeError
-        }
-        const toReturn = this.stream
-        this.stream = Buffer.alloc(0)
-        this.canRead = new Promise(resolve => {
-            this.resolveRead = resolve
-        })
-        return toReturn
-    }
-
-    /**
-     * Creates a new connection
-     * @param port
-     * @param ip
-     * @returns {Promise<void>}
-     */
-    async connect(port, ip) {
-        this.stream = Buffer.alloc(0)
-
-        this.client = new Socket()
-        this.canRead = new Promise(resolve => {
-            this.resolveRead = resolve
-        })
-        this.closed = false
-        return new Promise((resolve, reject) => {
-            this.client.connect(port, ip, () => {
-                this.receive()
-                resolve(this)
-            })
-            this.client.on('error', reject)
-            this.client.on('close', () => {
-                if (this.client.closed) {
-                    this.resolveRead(false)
-                    this.closed = true
-                }
-            })
-        })
-    }
-
-    write(data) {
-        if (this.closed) {
-            throw closeError
-        }
-        this.client.write(data)
-    }
-
-
-
-    async close() {
-        await this.client.destroy()
-        this.client.unref()
-        this.closed = true
-    }
-
-    async receive() {
-        this.client.on('data', async message => {
-
-            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
-

+ 135 - 0
gramjs/extensions/PromisedNetSockets.ts

@@ -0,0 +1,135 @@
+import {Socket} from 'net';
+import {Mutex} from 'async-mutex';
+
+const mutex = new Mutex();
+
+const closeError = new Error('NetSocket was closed');
+
+export class PromisedNetSockets {
+    private client?: Socket;
+    private closed: boolean;
+    private stream: Buffer;
+    private canRead?: boolean | Promise<boolean>;
+    private resolveRead: ((value?: any) => void) | undefined;
+
+    constructor() {
+        this.client = undefined;
+        this.closed = true
+        this.stream = Buffer.alloc(0);
+    }
+
+    async readExactly(number: 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: number) {
+        if (this.closed) {
+            throw closeError
+        }
+        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) {
+            this.canRead = new Promise(resolve => {
+                this.resolveRead = resolve
+            })
+        }
+
+        return toReturn
+    }
+
+    async readAll() {
+        if (this.closed || !await this.canRead) {
+            throw closeError
+        }
+        const toReturn = this.stream;
+        this.stream = Buffer.alloc(0);
+        this.canRead = new Promise(resolve => {
+            this.resolveRead = resolve
+        });
+        return toReturn
+    }
+
+    /**
+     * Creates a new connection
+     * @param port
+     * @param ip
+     * @returns {Promise<void>}
+     */
+    async connect(port: number, ip: string) {
+        this.stream = Buffer.alloc(0);
+
+        this.client = new Socket();
+        this.canRead = new Promise(resolve => {
+            this.resolveRead = resolve
+        });
+        this.closed = false;
+        return new Promise((resolve, reject) => {
+            if (this.client) {
+                this.client.connect(port, ip, () => {
+                    this.receive();
+                    resolve(this)
+                });
+                this.client.on('error', reject);
+                this.client.on('close', () => {
+                    if (this.client && this.client.destroyed) {
+                        if (this.resolveRead) {
+                            this.resolveRead(false);
+                        }
+                        this.closed = true
+                    }
+                })
+            }
+
+        })
+    }
+
+    write(data: Buffer) {
+        if (this.closed) {
+            throw closeError
+        }
+        if (this.client) {
+            this.client.write(data)
+        }
+    }
+
+
+    async close() {
+        if (this.client) {
+            await this.client.destroy();
+            this.client.unref();
+        }
+        this.closed = true;
+    }
+
+    async receive() {
+        if (this.client) {
+            this.client.on('data', async message => {
+
+                const release = await mutex.acquire();
+                try {
+                    let data;
+                    //CONTEST BROWSER
+                    this.stream = Buffer.concat([this.stream, message]);
+                    if (this.resolveRead) {
+                        this.resolveRead(true)
+                    }
+                } finally {
+                    release()
+                }
+            });
+        }
+
+    }
+}

+ 0 - 130
gramjs/extensions/PromisedWebSockets.js

@@ -1,130 +0,0 @@
-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) {
-            throw closeError
-        }
-        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) {
-            this.canRead = new Promise(resolve => {
-                this.resolveRead = resolve
-            })
-        }
-
-        return toReturn
-    }
-
-    async readAll() {
-        if (this.closed || !await this.canRead) {
-            throw closeError
-        }
-        const toReturn = this.stream
-        this.stream = Buffer.alloc(0)
-        this.canRead = new Promise(resolve => {
-            this.resolveRead = resolve
-        })
-        return toReturn
-    }
-
-    getWebSocketLink(ip, port) {
-        if (port === 443) {
-            return `wss://${ip}:${port}/apiws`
-        } else {
-            return `ws://${ip}:${port}/apiws`
-        }
-    }
-
-    async connect(port, ip) {
-        this.stream = Buffer.alloc(0)
-        this.canRead = new Promise(resolve => {
-            this.resolveRead = resolve
-        })
-        this.closed = false
-        this.website = this.getWebSocketLink(ip, port)
-        this.client = new WebSocketClient(this.website, 'binary')
-        return new Promise((resolve, reject) => {
-            this.client.onopen = () => {
-                this.receive()
-                resolve(this)
-            }
-            this.client.onerror = error => {
-                reject(error)
-            }
-            this.client.onclose = () => {
-                this.resolveRead(false)
-                this.closed = true
-            }
-            //CONTEST
-            if (typeof window !== 'undefined'){
-                window.addEventListener('offline', async () => {
-                    await this.close()
-                    this.resolveRead(false)
-                })
-            }
-        })
-    }
-
-    write(data) {
-        if (this.closed) {
-            throw closeError
-        }
-        this.client.send(data)
-    }
-
-    async close() {
-        await this.client.close()
-        this.closed = true
-    }
-
-    async receive() {
-        this.client.onmessage = async message => {
-            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()
-            }
-        }
-    }
-}
-
-module.exports = PromisedWebSockets

+ 149 - 0
gramjs/extensions/PromisedWebSockets.ts

@@ -0,0 +1,149 @@
+import {w3cwebsocket} from 'websocket';
+import {Mutex} from 'async-mutex';
+
+const mutex = new Mutex();
+
+const closeError = new Error('WebSocket was closed');
+
+export class PromisedWebSockets {
+    private closed: boolean;
+    private stream: Buffer;
+    private canRead?: boolean | Promise<boolean>;
+    private resolveRead: ((value?: any) => void) | undefined;
+    private client: w3cwebsocket | undefined;
+    private website?: string;
+
+    constructor() {
+        this.client = undefined;
+        this.stream = Buffer.alloc(0);
+
+        this.closed = true
+    }
+
+    async readExactly(number: 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: number) {
+        if (this.closed) {
+            throw closeError
+        }
+        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) {
+            this.canRead = new Promise(resolve => {
+                this.resolveRead = resolve
+            })
+        }
+
+        return toReturn
+    }
+
+    async readAll() {
+        if (this.closed || !await this.canRead) {
+            throw closeError
+        }
+        const toReturn = this.stream;
+        this.stream = Buffer.alloc(0);
+        this.canRead = new Promise(resolve => {
+            this.resolveRead = resolve
+        });
+        return toReturn
+    }
+
+    getWebSocketLink(ip: string, port: number) {
+        if (port === 443) {
+            return `wss://${ip}:${port}/apiws`
+        } else {
+            return `ws://${ip}:${port}/apiws`
+        }
+    }
+
+    async connect(port: number, ip: string) {
+        this.stream = Buffer.alloc(0);
+        this.canRead = new Promise(resolve => {
+            this.resolveRead = resolve
+        });
+        this.closed = false;
+        this.website = this.getWebSocketLink(ip, port);
+        this.client = new w3cwebsocket(this.website, 'binary');
+        return new Promise((resolve, reject) => {
+            if (this.client) {
+
+
+                this.client.onopen = () => {
+                    this.receive();
+                    resolve(this)
+                };
+                this.client.onerror = (error: any) => {
+                    reject(error)
+                };
+                this.client.onclose = () => {
+                    if (this.resolveRead) {
+                        this.resolveRead(false);
+                    }
+                    this.closed = true
+                };
+                //CONTEST
+                if (typeof window !== 'undefined') {
+                    window.addEventListener('offline', async () => {
+                        await this.close();
+                        if (this.resolveRead) {
+                            this.resolveRead(false)
+                        }
+                    })
+                }
+            }
+        })
+    }
+
+    write(data: Buffer) {
+        if (this.closed) {
+            throw closeError
+        }
+        if(this.client){
+            this.client.send(data)
+        }
+    }
+
+    async close() {
+        if(this.client) {
+
+            await this.client.close();
+        }
+        this.closed = true
+    }
+
+    async receive() {
+        if (this.client) {
+
+            this.client.onmessage = async (message: any) => {
+                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]);
+                    if (this.resolveRead) {
+                        this.resolveRead(true)
+                    }
+                } finally {
+                    release()
+                }
+            }
+        }
+    }
+}
+

+ 0 - 14
gramjs/extensions/index.js

@@ -1,14 +0,0 @@
-const Logger = require('./Logger')
-const BinaryWriter = require('./BinaryWriter')
-const BinaryReader = require('./BinaryReader')
-const PromisedWebSockets = require('./PromisedWebSockets')
-const MessagePacker = require('./MessagePacker')
-const AsyncQueue = require('./AsyncQueue')
-module.exports = {
-    BinaryWriter,
-    BinaryReader,
-    MessagePacker,
-    AsyncQueue,
-    Logger,
-    PromisedWebSockets,
-}

+ 7 - 0
gramjs/extensions/index.ts

@@ -0,0 +1,7 @@
+export { Logger } from './Logger'
+export { BinaryWriter } from './BinaryWriter'
+export { BinaryReader } from './BinaryReader'
+export { PromisedWebSockets } from './PromisedWebSockets'
+export { PromisedNetSockets } from './PromisedNetSockets'
+export { MessagePacker } from './MessagePacker'
+export { AsyncQueue } from './AsyncQueue'

+ 87 - 0
gramjs/extensions/markdown.ts

@@ -0,0 +1,87 @@
+import {Api} from "../tl";
+import {ValueOf} from "../define";
+
+const DEFAULT_DELIMITERS: {
+    [key: string]: typeof Api.MessageEntityBold | typeof Api.MessageEntityItalic |
+        typeof Api.MessageEntityStrike | typeof Api.MessageEntityCode | typeof Api.MessageEntityPre
+} = {
+    '**': Api.MessageEntityBold,
+    '__': Api.MessageEntityItalic,
+    '~~': Api.MessageEntityStrike,
+    '`': Api.MessageEntityCode,
+    '```': Api.MessageEntityPre
+};
+
+
+// TODO maybe there is a better way :shrug:
+export function parse(message: string, delimiters = DEFAULT_DELIMITERS): [string, ValueOf<typeof DEFAULT_DELIMITERS>[]] {
+    let i = 0;
+    const keys: { [key: string]: boolean } = {};
+    for (const k in DEFAULT_DELIMITERS) {
+        keys[k] = false;
+    }
+    const entities = [];
+    const tempEntities: { [key: string]: any } = {};
+    while (i < message.length) {
+        let foundIndex = -1;
+        let foundDelim = undefined;
+        for (const key of Object.keys(DEFAULT_DELIMITERS)) {
+            const index = message.indexOf(key, i);
+            if (index > -1 && (foundIndex === -1 || index < foundIndex)) {
+                foundIndex = index;
+                foundDelim = key;
+            }
+
+        }
+
+        if (foundIndex === -1 || foundDelim == undefined) {
+            break;
+        }
+        if (!keys[foundDelim]) {
+            tempEntities[foundDelim] = new DEFAULT_DELIMITERS[foundDelim]({
+                offset: foundIndex,
+                length: -1,
+                language: ""
+            });
+            keys[foundDelim] = true;
+        } else {
+            keys[foundDelim] = false;
+            tempEntities[foundDelim].length = foundIndex - tempEntities[foundDelim].offset;
+            entities.push(tempEntities[foundDelim])
+        }
+        message = message.replace(foundDelim, "");
+        i = foundIndex;
+
+    }
+    return [message, entities];
+}
+
+export function unparse(text: string, entities: Api.TypeMessageEntity[] | undefined, delimiters = DEFAULT_DELIMITERS) {
+    if (!text || !entities) {
+        return text;
+    }
+    let insertAt: [number, string][] = [];
+
+    const tempDelimiters: Map<string, string> = new Map();
+    Object.keys(delimiters).forEach(key => {
+        tempDelimiters.set(delimiters[key].className, key);
+    });
+    for (const entity of entities) {
+
+        const s = entity.offset;
+        const e = entity.offset + entity.length;
+        const delimiter = tempDelimiters.get(entity.className);
+        if (delimiter) {
+            insertAt.push([s, delimiter]);
+            insertAt.push([e, delimiter]);
+        }
+    }
+    insertAt = insertAt.sort((a: [number, string], b: [number, string]) => {
+        return a[0] - b[0];
+    });
+    while (insertAt.length) {
+        const [at, what] = insertAt.pop() as [number, string];
+        text = text.slice(0, at) + what + text.slice(at);
+    }
+    return text;
+}

+ 1 - 1
gramjs/index.d.ts

@@ -1,4 +1,4 @@
-export { default as Api } from './tl/api';
+export  * as Api from './tl/api';
 export { default as TelegramClient } from './client/TelegramClient';
 export { default as connection } from './network';
 export { default as tl } from './tl';

+ 0 - 16
gramjs/index.js

@@ -1,16 +0,0 @@
-const Api = require('./tl/api')
-const TelegramClient = require('./client/TelegramClient')
-const connection = require('./network')
-const tl = require('./tl')
-const version = require('./Version')
-const events = require('./events')
-const utils = require('./Utils')
-const errors = require('./errors')
-const sessions = require('./sessions')
-const extensions = require('./extensions')
-const helpers = require('./Helpers')
-
-module.exports = {
-    Api, TelegramClient, sessions, connection, extensions,
-    tl, version, events, utils, errors, helpers,
-}

+ 11 - 0
gramjs/index.ts

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

+ 0 - 179
gramjs/network/Authenticator.js

@@ -1,179 +0,0 @@
-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 { constructors, requests } = require('../tl')
-const BinaryReader = require('../extensions/BinaryReader')
-const { SecurityError } = require('../errors/Common')
-
-/**
- * Executes the authentication process with the Telegram servers.
- * @param sender a connected {MTProtoPlainSender}.
- * @param log
- * @returns {Promise<{authKey: *, timeOffset: *}>}
- */
-async function doAuthentication(sender, log) {
-    // Step 1 sending: PQ Request, endianness doesn't matter since it's random
-    let bytes = Helpers.generateRandomBytes(16)
-
-    const nonce = Helpers.readBigIntFromBuffer(bytes, false, true)
-    const resPQ = await sender.send(new requests.ReqPqMulti({ nonce: nonce }))
-    log.debug('Starting authKey generation step 1')
-
-    if (!(resPQ instanceof constructors.ResPQ)) {
-        throw new Error(`Step 1 answer was ${resPQ}`)
-    }
-    if (resPQ.nonce.neq(nonce)) {
-        throw new SecurityError('Step 1 invalid nonce from server')
-    }
-    const pq = Helpers.readBigIntFromBuffer(resPQ.pq, false, true)
-    log.debug('Finished authKey generation step 1')
-    log.debug('Starting authKey generation step 2')
-    // Step 2 sending: DH Exchange
-    let { p, q } = Factorizator.factorize(pq)
-
-    // 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 constructors.PQInnerData({
-        pq: Helpers.getByteArray(pq), // unsigned
-        p: p,
-        q: q,
-        nonce: resPQ.nonce,
-        serverNonce: resPQ.serverNonce,
-        newNonce: newNonce,
-    })
-
-    // sha_digest + data + random_bytes
-    let cipherText = null
-    let targetFingerprint = null
-    for (const fingerprint of resPQ.serverPublicKeyFingerprints) {
-        cipherText = await RSA.encrypt(fingerprint.toString(), pqInnerData.getBytes())
-        if (cipherText !== null && cipherText !== undefined) {
-            targetFingerprint = fingerprint
-            break
-        }
-    }
-    if (cipherText === null || cipherText === undefined) {
-        throw new SecurityError('Step 2 could not find a valid key for fingerprints')
-    }
-
-    const serverDhParams = await sender.send(
-        new requests.ReqDHParams({
-            nonce: resPQ.nonce,
-            serverNonce: resPQ.serverNonce,
-            p: p,
-            q: q,
-            publicKeyFingerprint: targetFingerprint,
-            encryptedData: cipherText,
-        }),
-    )
-    if (!(serverDhParams instanceof constructors.ServerDHParamsOk || serverDhParams instanceof constructors.ServerDHParamsFail)) {
-        throw new Error(`Step 2.1 answer was ${serverDhParams}`)
-    }
-    if (serverDhParams.nonce.neq(resPQ.nonce)) {
-        throw new SecurityError('Step 2 invalid nonce from server')
-    }
-
-    if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) {
-        throw new SecurityError('Step 2 invalid server nonce from server')
-    }
-
-    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.neq(nnh)) {
-            throw new SecurityError('Step 2 invalid DH fail nonce from server')
-        }
-    }
-    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 } = 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 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 constructors.ServerDHInnerData)) {
-        throw new Error(`Step 3 answer was ${serverDhInner}`)
-    }
-
-    if (serverDhInner.nonce.neq(resPQ.nonce)) {
-        throw new SecurityError('Step 3 Invalid nonce in encrypted answer')
-    }
-    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 constructors.ClientDHInnerData({
-        nonce: resPQ.nonce,
-        serverNonce: resPQ.serverNonce,
-        retryId: 0, // TODO Actual retry ID
-        gB: Helpers.getByteArray(gb, false),
-    }).getBytes()
-
-    const clientDdhInnerHashed = Buffer.concat([await Helpers.sha1(clientDhInner), clientDhInner])
-    // Encryption
-
-    const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed)
-    const dhGen = await sender.send(
-        new requests.SetClientDHParams({
-            nonce: resPQ.nonce,
-            serverNonce: resPQ.serverNonce,
-            encryptedData: clientDhEncrypted,
-        }),
-    )
-    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.neq(resPQ.nonce)) {
-        throw new SecurityError(`Step 3 invalid ${name} nonce from server`)
-    }
-    if (dhGen.serverNonce.neq(resPQ.serverNonce)) {
-        throw new SecurityError(`Step 3 invalid ${name} server nonce from server`)
-    }
-    const authKey = new AuthKey()
-    await authKey.setKey(Helpers.getByteArray(gab))
-
-    const nonceNumber = 1 + nonceTypes.indexOf(dhGen.constructor)
-
-    const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber)
-    const dhHash = dhGen[`newNonceHash${nonceNumber}`]
-
-    if (dhHash.neq(newNonceHash)) {
-        throw new SecurityError('Step 3 invalid new nonce hash')
-    }
-
-    if (!(dhGen instanceof constructors.DhGenOk)) {
-        throw new Error(`Step 3.2 answer was ${dhGen}`)
-    }
-    log.debug('Finished authKey generation step 3')
-
-    return { authKey, timeOffset }
-}
-
-
-module.exports = doAuthentication

+ 181 - 0
gramjs/network/Authenticator.ts

@@ -0,0 +1,181 @@
+/**
+ * Executes the authentication process with the Telegram servers.
+ * @param sender a connected {MTProtoPlainSender}.
+ * @param log
+ * @returns {Promise<{authKey: *, timeOffset: *}>}
+ */
+import {Api} from "../tl";
+import {SecurityError} from "../errors";
+import {Factorizator} from "../crypto/Factorizator";
+import {IGE} from "../crypto/IGE";
+import {BinaryReader} from "../extensions";
+import {AuthKey} from "../crypto/AuthKey";
+import {helpers} from "../index";
+import {encrypt} from "../crypto/RSA";
+import bigInt from 'big-integer';
+import {MTProtoPlainSender} from "./MTProtoPlainSender";
+
+export async function doAuthentication(sender: MTProtoPlainSender, log: any) {
+    // Step 1 sending: PQ Request, endianness doesn't matter since it's random
+    let bytes = helpers.generateRandomBytes(16);
+
+    const nonce = helpers.readBigIntFromBuffer(bytes, false, true);
+    const resPQ = await sender.send(new Api.ReqPqMulti({nonce: nonce}));
+    log.debug('Starting authKey generation step 1');
+
+    if (!(resPQ instanceof Api.ResPQ)) {
+        throw new Error(`Step 1 answer was ${resPQ}`)
+    }
+    if (resPQ.nonce.neq(nonce)) {
+        throw new SecurityError('Step 1 invalid nonce from server')
+    }
+    const pq = helpers.readBigIntFromBuffer(resPQ.pq, false, true);
+    log.debug('Finished authKey generation step 1');
+    log.debug('Starting authKey generation step 2');
+    // Step 2 sending: DH Exchange
+    let {p, q} = Factorizator.factorize(pq);
+
+    const pBuffer = helpers.getByteArray(p);
+    const qBuffer = helpers.getByteArray(q);
+
+    bytes = helpers.generateRandomBytes(32);
+    const newNonce = helpers.readBigIntFromBuffer(bytes, true, true);
+    const pqInnerData = new Api.PQInnerData({
+        pq: helpers.getByteArray(pq), // unsigned
+        p: pBuffer,
+        q: qBuffer,
+        nonce: resPQ.nonce,
+        serverNonce: resPQ.serverNonce,
+        newNonce: newNonce,
+    });
+
+    // sha_digest + data + random_bytes
+    let cipherText = undefined;
+    let targetFingerprint = undefined;
+    for (const fingerprint of resPQ.serverPublicKeyFingerprints) {
+        cipherText = await encrypt(fingerprint, pqInnerData.getBytes());
+        if (cipherText !== undefined) {
+            targetFingerprint = fingerprint;
+            break
+        }
+    }
+    if (cipherText === undefined) {
+        throw new SecurityError('Step 2 could not find a valid key for fingerprints')
+    }
+    const serverDhParams = await sender.send(
+        new Api.ReqDHParams({
+            nonce: resPQ.nonce,
+            serverNonce: resPQ.serverNonce,
+            p: pBuffer,
+            q: qBuffer,
+            publicKeyFingerprint: targetFingerprint,
+            encryptedData: cipherText,
+        }),
+    );
+
+    if (!(serverDhParams instanceof Api.ServerDHParamsOk || serverDhParams instanceof Api.ServerDHParamsFail)) {
+        throw new Error(`Step 2.1 answer was ${serverDhParams}`)
+    }
+    if (serverDhParams.nonce.neq(resPQ.nonce)) {
+        throw new SecurityError('Step 2 invalid nonce from server')
+    }
+
+    if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) {
+        throw new SecurityError('Step 2 invalid server nonce from server')
+    }
+
+    if (serverDhParams instanceof Api.ServerDHParamsFail) {
+        const sh = await helpers.sha1(helpers.toSignedLittleBuffer(newNonce, 32).slice(4, 20));
+        const nnh = helpers.readBigIntFromBuffer(sh, true, true);
+        if (serverDhParams.newNonceHash.neq(nnh)) {
+            throw new SecurityError('Step 2 invalid DH fail nonce from server')
+        }
+    }
+    if (!(serverDhParams instanceof Api.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} = 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 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 Api.ServerDHInnerData)) {
+        throw new Error(`Step 3 answer was ${serverDhInner}`)
+    }
+
+    if (serverDhInner.nonce.neq(resPQ.nonce)) {
+        throw new SecurityError('Step 3 Invalid nonce in encrypted answer')
+    }
+    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 Api.ClientDHInnerData({
+        nonce: resPQ.nonce,
+        serverNonce: resPQ.serverNonce,
+        retryId: bigInt.zero, // TODO Actual retry ID
+        gB: helpers.getByteArray(gb, false),
+    }).getBytes();
+
+    const clientDdhInnerHashed = Buffer.concat([await helpers.sha1(clientDhInner), clientDhInner]);
+    // Encryption
+
+    const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed);
+    const dhGen = await sender.send(
+        new Api.SetClientDHParams({
+            nonce: resPQ.nonce,
+            serverNonce: resPQ.serverNonce,
+            encryptedData: clientDhEncrypted,
+        }),
+    );
+    const nonceTypes = [Api.DhGenOk, Api.DhGenRetry, Api.DhGenFail];
+    // TS being weird again.
+    const nonceTypesString = ['DhGenOk', 'DhGenRetry', '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.neq(resPQ.nonce)) {
+        throw new SecurityError(`Step 3 invalid ${name} nonce from server`)
+    }
+    if (dhGen.serverNonce.neq(resPQ.serverNonce)) {
+        throw new SecurityError(`Step 3 invalid ${name} server nonce from server`)
+    }
+    const authKey = new AuthKey();
+    await authKey.setKey(helpers.getByteArray(gab));
+
+    const nonceNumber = 1 + nonceTypesString.indexOf(dhGen.className);
+
+    const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber);
+    // @ts-ignore
+    const dhHash = dhGen[`newNonceHash${nonceNumber}`];
+
+    if (dhHash.neq(newNonceHash)) {
+        throw new SecurityError('Step 3 invalid new nonce hash')
+    }
+
+    if (!(dhGen instanceof Api.DhGenOk)) {
+        throw new Error(`Step 3.2 answer was ${dhGen}`)
+    }
+    log.debug('Finished authKey generation step 3');
+
+    return {authKey, timeOffset}
+}
+
+

+ 14 - 14
gramjs/network/MTProtoPlainSender.js → gramjs/network/MTProtoPlainSender.ts

@@ -2,25 +2,29 @@
  *  This module contains the class used to communicate with Telegram's servers
  *  in plain text, when no authorization key has been created yet.
  */
-const Helpers = require('../Helpers')
-const MTProtoState = require('./MTProtoState')
-const BinaryReader = require('../extensions/BinaryReader')
-const { InvalidBufferError } = require('../errors/Common')
-const BigInt = require('big-integer')
-const { toSignedLittleBuffer } = require('../Helpers')
+import bigInt from 'big-integer';
+import {MTProtoState} from "./MTProtoState";
+import {Api} from "../tl";
+import {toSignedLittleBuffer} from "../Helpers";
+import {InvalidBufferError} from "../errors";
+import {BinaryReader} from "../extensions";
+import {Connection} from "./connection";
 
 /**
  * MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)
  */
 
-class MTProtoPlainSender {
+export class MTProtoPlainSender {
+    private _state: MTProtoState;
+    private _connection: Connection;
+
     /**
      * Initializes the MTProto plain sender.
      * @param connection connection: the Connection to be used.
      * @param loggers
      */
-    constructor(connection, loggers) {
-        this._state = new MTProtoState(connection, loggers)
+    constructor(connection: any, loggers: any) {
+        this._state = new MTProtoState(undefined, loggers)
         this._connection = connection
     }
 
@@ -28,8 +32,7 @@ class MTProtoPlainSender {
      * Sends and receives the result for the given request.
      * @param request
      */
-    async send(request) {
-
+    async send(request: Api.AnyRequest) {
         let body = request.getBytes()
 
         let msgId = this._state._getNewMsgId()
@@ -41,7 +44,6 @@ class MTProtoPlainSender {
 
         await this._connection.send(res)
         body = await this._connection.recv()
-
         if (body.length < 8) {
             throw new InvalidBufferError(body)
         }
@@ -73,5 +75,3 @@ class MTProtoPlainSender {
     }
 
 }
-
-module.exports = MTProtoPlainSender

+ 274 - 237
gramjs/network/MTProtoSender.js → gramjs/network/MTProtoSender.ts

@@ -1,35 +1,3 @@
-const MtProtoPlainSender = require('./MTProtoPlainSender')
-const MTProtoState = require('./MTProtoState')
-const Helpers = require('../Helpers')
-const AuthKey = require('../crypto/AuthKey')
-const doAuthentication = require('./Authenticator')
-const RPCResult = require('../tl/core/RPCResult')
-const MessageContainer = require('../tl/core/MessageContainer')
-const GZIPPacked = require('../tl/core/GZIPPacked')
-const RequestState = require('./RequestState')
-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,
-    MsgDetailedInfo,
-    MsgNewDetailedInfo,
-    NewSessionCreated,
-    FutureSalts,
-    MsgsStateReq,
-    MsgResendReq,
-    MsgsAllInfo,
-} = require('../tl').constructors
-const { SecurityError } = require('../errors/Common')
-const { InvalidBufferError } = require('../errors/Common')
-const { LogOut } = require('../tl').requests.auth
-const { RPCMessageToError } = require('../errors')
-const { TypeNotFoundError } = require('../errors/Common')
-
-
 /**
  * MTProto Mobile Protocol sender
  * (https://core.telegram.org/mtproto/description)
@@ -43,7 +11,38 @@ const { TypeNotFoundError } = require('../errors/Common')
  * A new authorization key will be generated on connection if no other
  * key exists yet.
  */
-class MTProtoSender {
+import {AuthKey} from "../crypto/AuthKey";
+import {MTProtoState} from "./MTProtoState";
+import {BinaryReader, MessagePacker} from "../extensions";
+import {GZIPPacked, MessageContainer, RPCResult, TLMessage} from "../tl/core";
+import {Api} from "../tl";
+import bigInt from 'big-integer'
+import {sleep} from "../Helpers";
+import {RequestState} from "./RequestState";
+import {doAuthentication} from "./Authenticator";
+import {MTProtoPlainSender} from "./MTProtoPlainSender";
+import {BadMessageError, InvalidBufferError, RPCMessageToError, SecurityError, TypeNotFoundError} from "../errors";
+import {UpdateConnectionState} from "./index";
+
+interface DEFAULT_OPTIONS {
+    logger: any,
+    retries: number,
+    delay: number,
+    autoReconnect: boolean,
+    connectTimeout: any,
+    authKeyCallback: any,
+    updateCallback?: any,
+    autoReconnectCallback?: any,
+    isMainSender: boolean,
+    dcId: number,
+    senderCallback?: any,
+}
+
+{
+
+}
+
+export class MTProtoSender {
     static DEFAULT_OPTIONS = {
         logger: null,
         retries: Infinity,
@@ -55,26 +54,50 @@ class MTProtoSender {
         autoReconnectCallback: null,
         isMainSender: null,
         senderCallback: null,
-    }
+    };
+    private _connection: any;
+    private _log: any;
+    private _dcId: number;
+    private _retries: number;
+    private _delay: number;
+    private _connectTimeout: null;
+    private _autoReconnect: boolean;
+    private _authKeyCallback: any;
+    private _updateCallback: any;
+    private _autoReconnectCallback?: any;
+    private _senderCallback: any;
+    private _isMainSender: boolean;
+    private _userConnected: boolean;
+    private _reconnecting: boolean;
+    private _disconnected: boolean;
+    private _sendLoopHandle: any;
+    private _recvLoopHandle: any;
+    private authKey: AuthKey;
+    private _state: MTProtoState;
+    private _sendQueue: MessagePacker;
+    private _pendingState: Map<string, RequestState>;
+    private _pendingAck: Set<any>;
+    private _lastAcks: any[];
+    private _handlers: any;
 
     /**
      * @param authKey
      * @param opts
      */
-    constructor(authKey, opts) {
-        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
-        this._connectTimeout = args.connectTimeout
-        this._authKeyCallback = args.authKeyCallback
-        this._updateCallback = args.updateCallback
-        this._autoReconnectCallback = args.autoReconnectCallback
-        this._isMainSender = args.isMainSender
-        this._senderCallback = args.senderCallback
+    constructor(authKey: undefined | AuthKey, opts: DEFAULT_OPTIONS) {
+        const args = {...MTProtoSender.DEFAULT_OPTIONS, ...opts};
+        this._connection = undefined;
+        this._log = args.logger;
+        this._dcId = args.dcId;
+        this._retries = args.retries;
+        this._delay = args.delay;
+        this._autoReconnect = args.autoReconnect;
+        this._connectTimeout = args.connectTimeout;
+        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.
@@ -84,67 +107,75 @@ class MTProtoSender {
          * be cleared but on explicit user disconnection all the
          * pending futures should be cancelled.
          */
-        this._user_connected = false
-        this._reconnecting = false
-        this._disconnected = true
+        this._userConnected = false;
+        this._reconnecting = false;
+        this._disconnected = true;
 
         /**
          * We need to join the loops upon disconnection
          */
-        this._send_loop_handle = null
-        this._recv_loop_handle = null
+        this._sendLoopHandle = null;
+        this._recvLoopHandle = null;
 
         /**
          * Preserving the references of the AuthKey and state is important
          */
-        this.authKey = authKey || new AuthKey()
-        this._state = new MTProtoState(this.authKey, this._log)
+        this.authKey = authKey || new AuthKey();
+        this._state = new MTProtoState(this.authKey, this._log);
 
         /**
          * Outgoing messages are put in a queue and sent in a batch.
          * Note that here we're also storing their ``_RequestState``.
          */
-        this._send_queue = new MessagePacker(this._state, this._log)
+        this._sendQueue = new MessagePacker(this._state, this._log);
 
         /**
          * Sent states are remembered until a response is received.
          */
-        this._pending_state = {}
+        this._pendingState = new Map<string, any>();
 
         /**
          * Responses must be acknowledged, and we can also batch these.
          */
-        this._pending_ack = new Set()
+        this._pendingAck = new Set();
 
         /**
          * Similar to pending_messages but only for the last acknowledges.
          * These can't go in pending_messages because no acknowledge for them
          * is received, but we may still need to resend their state on bad salts.
          */
-        this._last_acks = []
+        this._lastAcks = [];
 
         /**
          * Jump table from response ID to method that handles it
          */
 
         this._handlers = {
-            [RPCResult.CONSTRUCTOR_ID]: this._handleRPCResult.bind(this),
-            [MessageContainer.CONSTRUCTOR_ID]: this._handleContainer.bind(this),
-            [GZIPPacked.CONSTRUCTOR_ID]: this._handleGzipPacked.bind(this),
-            [Pong.CONSTRUCTOR_ID]: this._handlePong.bind(this),
-            [BadServerSalt.CONSTRUCTOR_ID]: this._handleBadServerSalt.bind(this),
-            [BadMsgNotification.CONSTRUCTOR_ID]: this._handleBadNotification.bind(this),
-            [MsgDetailedInfo.CONSTRUCTOR_ID]: this._handleDetailedInfo.bind(this),
-            [MsgNewDetailedInfo.CONSTRUCTOR_ID]: this._handleNewDetailedInfo.bind(this),
-            [NewSessionCreated.CONSTRUCTOR_ID]: this._handleNewSessionCreated.bind(this),
-            [MsgsAck.CONSTRUCTOR_ID]: this._handleAck.bind(this),
-            [FutureSalts.CONSTRUCTOR_ID]: this._handleFutureSalts.bind(this),
-            [MsgsStateReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this),
-            [MsgResendReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this),
-            [MsgsAllInfo.CONSTRUCTOR_ID]: this._handleMsgAll.bind(this),
+            [RPCResult.CONSTRUCTOR_ID.toString()]: this._handleRPCResult.bind(this),
+            [MessageContainer.CONSTRUCTOR_ID.toString()]: this._handleContainer.bind(this),
+            [GZIPPacked.CONSTRUCTOR_ID.toString()]: this._handleGzipPacked.bind(this),
+            [Api.Pong.CONSTRUCTOR_ID.toString()]: this._handlePong.bind(this),
+            [Api.BadServerSalt.CONSTRUCTOR_ID.toString()]: this._handleBadServerSalt.bind(this),
+            [Api.BadMsgNotification.CONSTRUCTOR_ID.toString()]: this._handleBadNotification.bind(this),
+            [Api.MsgDetailedInfo.CONSTRUCTOR_ID.toString()]: this._handleDetailedInfo.bind(this),
+            [Api.MsgNewDetailedInfo.CONSTRUCTOR_ID.toString()]: this._handleNewDetailedInfo.bind(this),
+            [Api.NewSessionCreated.CONSTRUCTOR_ID.toString()]: this._handleNewSessionCreated.bind(this),
+            [Api.MsgsAck.CONSTRUCTOR_ID.toString()]: this._handleAck.bind(this),
+            [Api.FutureSalts.CONSTRUCTOR_ID.toString()]: this._handleFutureSalts.bind(this),
+            [Api.MsgsStateReq.CONSTRUCTOR_ID.toString()]: this._handleStateForgotten.bind(this),
+            [Api.MsgResendReq.CONSTRUCTOR_ID.toString()]: this._handleStateForgotten.bind(this),
+            [Api.MsgsAllInfo.CONSTRUCTOR_ID.toString()]: this._handleMsgAll.bind(this),
         }
     }
 
+    set dcId(dcId: number) {
+        this._dcId = dcId;
+    }
+
+    get dcId() {
+        return this._dcId;
+    }
+
     // Public API
 
     /**
@@ -153,34 +184,34 @@ class MTProtoSender {
      * @param eventDispatch {function}
      * @returns {Promise<boolean>}
      */
-    async connect(connection, eventDispatch=null) {
-        if (this._user_connected) {
-            this._log.info('User is already connected!')
+    async connect(connection: any, eventDispatch?: any) {
+        if (this._userConnected) {
+            this._log.info('User is already connected!');
             return false
         }
-        this._connection = connection
+        this._connection = connection;
 
-        const retries = this._retries
+        const retries = this._retries;
 
         for (let attempt = 0; attempt < retries; attempt++) {
             try {
-                await this._connect()
+                await this._connect();
                 break
             } catch (e) {
-                if (attempt===0 && eventDispatch!==null){
-                    eventDispatch({ update: new UpdateConnectionState(-1) })
+                if (attempt === 0 && eventDispatch) {
+                    eventDispatch({update: new UpdateConnectionState(-1)})
                 }
-                console.dir(e)
+                console.dir(e);
 
-                this._log.error('WebSocket connection failed attempt : '+(attempt+1))
-                await Helpers.sleep(this._delay)
+                this._log.error('WebSocket connection failed attempt : ' + (attempt + 1));
+                await sleep(this._delay)
             }
         }
         return true
     }
 
     isConnected() {
-        return this._user_connected
+        return this._userConnected
     }
 
     /**
@@ -217,14 +248,14 @@ class MTProtoSender {
      * @param request
      * @returns {RequestState}
      */
-    send(request) {
-        if (!this._user_connected) {
+    send(request: Api.AnyRequest) {
+        if (!this._userConnected) {
             throw new Error('Cannot send requests while disconnected')
         }
         //CONTEST
-        const state = new RequestState(request)
-        this._send_queue.append(state)
-        return state.promise
+        const state = new RequestState(request);
+        this._sendQueue.append(state);
+        return state.promise;
         /*
         if (!Helpers.isArrayLike(request)) {
             const state = new RequestState(request)
@@ -243,18 +274,19 @@ class MTProtoSender {
      * @private
      */
     async _connect() {
-        this._log.info('Connecting to {0}...'.replace('{0}', this._connection))
-        await this._connection.connect()
-        this._log.debug('Connection success!')
+
+        this._log.info('Connecting to {0}...'.replace('{0}', this._connection.toString()));
+        await this._connection.connect();
+        this._log.debug('Connection success!');
         //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')
-            await this.authKey.setKey(res.authKey)
+            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');
+            await this.authKey.setKey(res.authKey);
 
-            this._state.time_offset = res.timeOffset
+            this._state.timeOffset = res.timeOffset;
 
             /**
              * This is *EXTREMELY* important since we don't control
@@ -268,14 +300,14 @@ class MTProtoSender {
         } else {
             this._log.debug('Already have an auth key ...')
         }
-        this._user_connected = true
-        this._reconnecting = false
+        this._userConnected = true;
+        this._reconnecting = false;
 
-        this._log.debug('Starting send loop')
-        this._send_loop_handle = this._sendLoop()
+        this._log.debug('Starting send loop');
+        this._sendLoopHandle = this._sendLoop();
 
-        this._log.debug('Starting receive loop')
-        this._recv_loop_handle = this._recvLoop()
+        this._log.debug('Starting receive loop');
+        this._recvLoopHandle = this._recvLoop();
 
         // _disconnected only completes after manual disconnection
         // or errors after which the sender cannot continue such
@@ -286,15 +318,15 @@ class MTProtoSender {
 
     async _disconnect(error = null) {
         if (this._connection === null) {
-            this._log.info('Not disconnecting (already have no connection)')
+            this._log.info('Not disconnecting (already have no connection)');
             return
         }
-        if (this._updateCallback){
+        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...')
+        this._log.info('Disconnecting from %s...'.replace('%s', this._connection.toString()));
+        this._userConnected = false;
+        this._log.debug('Closing current connection...');
         await this._connection.disconnect()
     }
 
@@ -306,20 +338,20 @@ 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) }))
-                this._send_queue.append(ack)
-                this._last_acks.push(ack)
-                this._pending_ack.clear()
+        this._sendQueue = new MessagePacker(this._state, this._log);
+
+        while (this._userConnected && !this._reconnecting) {
+            if (this._pendingAck.size) {
+                const ack = new RequestState(new Api.MsgsAck({msgIds: Array(...this._pendingAck)}));
+                this._sendQueue.append(ack);
+                this._lastAcks.push(ack);
+                this._pendingAck.clear()
             }
-            this._log.debug('Waiting for messages to send...'+this._reconnecting)
+            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()
+            const res = await this._sendQueue.get();
 
             if (this._reconnecting) {
                 return
@@ -328,28 +360,28 @@ class MTProtoSender {
             if (!res) {
                 continue
             }
-            let data = res.data
-            const batch = res.batch
-            this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`)
+            let data = res.data;
+            const batch = res.batch;
+            this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`);
 
-            data = await this._state.encryptMessageData(data)
+            data = await this._state.encryptMessageData(data);
 
             try {
                 await this._connection.send(data)
             } catch (e) {
-                this._log.error(e)
-                this._log.info('Connection closed while sending data')
+                this._log.error(e);
+                this._log.info('Connection closed while sending data');
                 return
             }
             for (const state of batch) {
                 if (!Array.isArray(state)) {
                     if (state.request.classType === 'request') {
-                        this._pending_state[state.msgId] = state
+                        this._pendingState.set(state.msgId.toString(), state)
                     }
                 } else {
                     for (const s of state) {
                         if (s.request.classType === 'request') {
-                            this._pending_state[s.msgId] = s
+                            this._pendingState.set(s.msgId.toString(), s)
                         }
                     }
                 }
@@ -359,18 +391,18 @@ class MTProtoSender {
     }
 
     async _recvLoop() {
-        let body
-        let message
+        let body;
+        let message;
 
-        while (this._user_connected && !this._reconnecting) {
+        while (this._userConnected && !this._reconnecting) {
             // this._log.debug('Receiving items from the network...');
-            this._log.debug('Receiving items from the network...')
+            this._log.debug('Receiving items from the network...');
             try {
                 body = await this._connection.recv()
             } catch (e) {
                 // this._log.info('Connection closed while receiving data');
-                this._log.warn('Connection closed while receiving data')
-                this._startReconnect()
+                this._log.warn('Connection closed while receiving data');
+                this._startReconnect();
                 return
             }
             try {
@@ -378,19 +410,19 @@ class MTProtoSender {
             } catch (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}`)
+                    this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`);
                     continue
                 } else if (e instanceof SecurityError) {
                     // A step while decoding had the incorrect data. This message
                     // should not be considered safe and it should be ignored.
-                    this._log.warn(`Security error while unpacking a received message: ${e}`)
+                    this._log.warn(`Security error while unpacking a received message: ${e}`);
                     continue
                 } else if (e instanceof InvalidBufferError) {
-                    this._log.info('Broken authorization key; resetting')
-                    if (this._updateCallback && this._isMainSender){
+                    this._log.info('Broken authorization key; resetting');
+                    if (this._updateCallback && this._isMainSender) {
                         // 0 == broken
                         this._updateCallback(0)
-                    } else if (this._senderCallback && !this._isMainSender){
+                    } else if (this._senderCallback && !this._isMainSender) {
                         // Deletes the current sender from the object
                         this._senderCallback(this._dcId)
                     }
@@ -406,18 +438,18 @@ class MTProtoSender {
                     */
                     return
                 } else {
-                    this._log.error('Unhandled error while receiving data')
-                    this._log.error(e)
-                    console.log(e)
-                    this._startReconnect()
+                    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) {
-                this._log.error('Unhandled error while receiving data')
-                console.log(e)
+                this._log.error('Unhandled error while receiving data');
+                console.log(e);
                 this._log.error(e)
             }
         }
@@ -433,11 +465,11 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _processMessage(message) {
-        this._pending_ack.add(message.msgId)
+    async _processMessage(message: TLMessage) {
+        this._pendingAck.add(message.msgId);
         // eslint-disable-next-line require-atomic-updates
-        message.obj = await message.obj
-        let handler = this._handlers[message.obj.CONSTRUCTOR_ID]
+        message.obj = await message.obj;
+        let handler = this._handlers[message.obj.CONSTRUCTOR_ID.toString()];
         if (!handler) {
             handler = this._handleUpdate.bind(this)
         }
@@ -452,31 +484,31 @@ class MTProtoSender {
      * @returns {*[]}
      * @private
      */
-    _popStates(msgId) {
-        let state = this._pending_state[msgId]
+    _popStates(msgId: bigInt.BigInteger) {
+        let state = this._pendingState.get(msgId.toString());
         if (state) {
-            delete this._pending_state[msgId]
+            this._pendingState.delete(msgId.toString());
             return [state]
         }
 
-        const toPop = []
+        const toPop = [];
 
-        for (state of Object.values(this._pending_state)) {
+        for (const state of Object.values(this._pendingState)) {
             if (state.containerId && state.containerId.equals(msgId)) {
                 toPop.push(state.msgId)
             }
         }
 
         if (toPop.length) {
-            const temp = []
+            const temp = [];
             for (const x of toPop) {
-                temp.push(this._pending_state[x])
-                delete this._pending_state[x]
+                temp.push(this._pendingState.get(x));
+                this._pendingState.delete(x);
             }
             return temp
         }
 
-        for (const ack of this._last_acks) {
+        for (const ack of this._lastAcks) {
             if (ack.msgId === msgId) {
                 return [ack]
             }
@@ -493,13 +525,13 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    _handleRPCResult(message) {
-        const RPCResult = message.obj
-        const state = this._pending_state[RPCResult.reqMsgId]
+    _handleRPCResult(message: TLMessage) {
+        const RPCResult = message.obj;
+        const state = this._pendingState.get(RPCResult.reqMsgId.toString());
         if (state) {
-            delete this._pending_state[RPCResult.reqMsgId]
+            this._pendingState.delete(RPCResult.reqMsgId.toString())
         }
-        this._log.debug(`Handling RPC result for message ${RPCResult.reqMsgId}`)
+        this._log.debug(`Handling RPC result for message ${RPCResult.reqMsgId}`);
 
         if (!state) {
             // TODO We should not get responses to things we never sent
@@ -507,28 +539,30 @@ class MTProtoSender {
             // See #658, #759 and #958. They seem to happen in a container
             // which contain the real response right after.
             try {
-                const reader = new BinaryReader(RPCResult.body)
-                if (!(reader.tgReadObject() instanceof upload.File)) {
-                    throw new TypeNotFoundError('Not an upload.File')
+                const reader = new BinaryReader(RPCResult.body);
+                if (!(reader.tgReadObject() instanceof Api.upload.File)) {
+                    throw new Error('Not an upload.File')
                 }
             } catch (e) {
-                this._log.error(e)
+                this._log.error(e);
                 if (e instanceof TypeNotFoundError) {
-                    this._log.info(`Received response without parent request: ${RPCResult.body}`)
+                    this._log.info(`Received response without parent request: ${RPCResult.body}`);
                     return
                 } else {
                     throw e
                 }
             }
+            return;
         }
-        if (RPCResult.error) {
+        if (RPCResult.error && state.msgId) {
             // eslint-disable-next-line new-cap
-            const error = RPCMessageToError(RPCResult.error, state.request)
-            this._send_queue.append(new RequestState(new MsgsAck({ msgIds: [state.msgId] })))
+            const error = RPCMessageToError(RPCResult.error, state.request);
+            this._sendQueue.append(new RequestState(new Api.MsgsAck({msgIds: [state.msgId]})));
             state.reject(error)
         } else {
-            const reader = new BinaryReader(RPCResult.body)
-            const read = state.request.readResult(reader)
+            const reader = new BinaryReader(RPCResult.body);
+            const read = state.request.readResult(reader);
+            //console.log("patcfh goes here ?", read);
             state.resolve(read)
         }
     }
@@ -540,8 +574,8 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleContainer(message) {
-        this._log.debug('Handling container')
+    async _handleContainer(message: TLMessage) {
+        this._log.debug('Handling container');
         for (const innerMessage of message.obj.messages) {
             await this._processMessage(innerMessage)
         }
@@ -554,20 +588,20 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleGzipPacked(message) {
-        this._log.debug('Handling gzipped data')
-        const reader = new BinaryReader(message.obj.data)
-        message.obj = reader.tgReadObject()
+    async _handleGzipPacked(message: TLMessage) {
+        this._log.debug('Handling gzipped data');
+        const reader = new BinaryReader(message.obj.data);
+        message.obj = reader.tgReadObject();
         await this._processMessage(message)
     }
 
-    async _handleUpdate(message) {
+    async _handleUpdate(message: TLMessage) {
         if (message.obj.SUBCLASS_OF_ID !== 0x8af52aac) {
             // crc32(b'Updates')
-            this._log.warn(`Note: ${message.obj.className} 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.className)
+        this._log.debug('Handling update ' + message.obj.className);
         if (this._updateCallback) {
             this._updateCallback(message.obj)
         }
@@ -581,11 +615,11 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handlePong(message) {
-        const pong = message.obj
-        this._log.debug(`Handling pong for message ${pong.msgId}`)
-        const state = this._pending_state[pong.msgId]
-        delete this._pending_state[pong.msgId]
+    async _handlePong(message: TLMessage) {
+        const pong = message.obj;
+        this._log.debug(`Handling pong for message ${pong.msgId}`);
+        const state = this._pendingState.get(pong.msgId);
+        this._pendingState.delete(pong.msgId);
 
         // Todo Check result
         if (state) {
@@ -602,12 +636,12 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleBadServerSalt(message) {
-        const badSalt = message.obj
-        this._log.debug(`Handling bad salt for message ${badSalt.badMsgId}`)
-        this._state.salt = badSalt.newServerSalt
-        const states = this._popStates(badSalt.badMsgId)
-        this._send_queue.extend(states)
+    async _handleBadServerSalt(message: TLMessage) {
+        const badSalt = message.obj;
+        this._log.debug(`Handling bad salt for message ${badSalt.badMsgId}`);
+        this._state.salt = badSalt.newServerSalt;
+        const states = this._popStates(badSalt.badMsgId);
+        this._sendQueue.extend(states);
         this._log.debug(`${states.length} message(s) will be resent`)
     }
 
@@ -620,14 +654,14 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleBadNotification(message) {
-        const badMsg = message.obj
-        const states = this._popStates(badMsg.badMsgId)
-        this._log.debug(`Handling bad msg ${JSON.stringify(badMsg)}`)
+    async _handleBadNotification(message: TLMessage) {
+        const badMsg = message.obj;
+        const states = this._popStates(badMsg.badMsgId);
+        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)
+            const to = this._state.updateTimeOffset(bigInt(message.msgId));
             this._log.info(`System clock is wrong, set time offset to ${to}s`)
         } else if (badMsg.errorCode === 32) {
             // msg_seqno too low, so just pump it up by some "large" amount
@@ -645,7 +679,7 @@ class MTProtoSender {
             return
         }
         // Messages are to be re-sent once we've corrected the issue
-        this._send_queue.extend(states)
+        this._sendQueue.extend(states);
         this._log.debug(`${states.length} messages will be resent due to bad msg`)
     }
 
@@ -657,11 +691,11 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleDetailedInfo(message) {
+    async _handleDetailedInfo(message: TLMessage) {
         // TODO https://goo.gl/VvpCC6
-        const msgId = message.obj.answerMsgId
-        this._log.debug(`Handling detailed info for message ${msgId}`)
-        this._pending_ack.add(msgId)
+        const msgId = message.obj.answerMsgId;
+        this._log.debug(`Handling detailed info for message ${msgId}`);
+        this._pendingAck.add(msgId)
     }
 
     /**
@@ -672,11 +706,11 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleNewDetailedInfo(message) {
+    async _handleNewDetailedInfo(message: TLMessage) {
         // TODO https://goo.gl/VvpCC6
-        const msgId = message.obj.answerMsgId
-        this._log.debug(`Handling new detailed info for message ${msgId}`)
-        this._pending_ack.add(msgId)
+        const msgId = message.obj.answerMsgId;
+        this._log.debug(`Handling new detailed info for message ${msgId}`);
+        this._pendingAck.add(msgId)
     }
 
     /**
@@ -687,9 +721,9 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleNewSessionCreated(message) {
+    async _handleNewSessionCreated(message: TLMessage) {
         // TODO https://goo.gl/LMyN7A
-        this._log.debug('Handling new session created')
+        this._log.debug('Handling new session created');
         this._state.salt = message.obj.serverSalt
     }
 
@@ -711,13 +745,13 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleAck(message) {
-        const ack = message.obj
-        this._log.debug(`Handling acknowledge for ${ack.msgIds}`)
+    async _handleAck(message: TLMessage) {
+        const ack = message.obj;
+        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 LogOut) {
-                delete this._pending_state[msgId]
+            const state = this._pendingState.get(msgId);
+            if (state && state.request instanceof Api.auth.LogOut) {
+                this._pendingState.delete(msgId);
                 state.resolve(true)
             }
         }
@@ -732,14 +766,14 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleFutureSalts(message) {
+    async _handleFutureSalts(message: TLMessage) {
         // TODO save these salts and automatically adjust to the
         // correct one whenever the salt in use expires.
-        this._log.debug(`Handling future salts for message ${message.msgId}`)
-        const state = this._pending_state[message.msgId]
+        this._log.debug(`Handling future salts for message ${message.msgId}`);
+        const state = this._pendingState.get(message.msgId.toString());
 
         if (state) {
-            delete this._pending_state[message]
+            this._pendingState.delete(message.msgId.toString());
             state.resolve(message.obj)
         }
     }
@@ -751,9 +785,12 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleStateForgotten(message) {
-        this._send_queue.append(
-            new RequestState(new MsgsStateInfo(message.msgId, String.fromCharCode(1).repeat(message.obj.msgIds))),
+    async _handleStateForgotten(message: TLMessage) {
+        this._sendQueue.append(
+            new RequestState(new Api.MsgsStateInfo({
+                reqMsgId: message.msgId,
+                info: String.fromCharCode(1).repeat(message.obj.msgIds)
+            }))
         )
     }
 
@@ -763,53 +800,53 @@ class MTProtoSender {
      * @returns {Promise<void>}
      * @private
      */
-    async _handleMsgAll(message) {
+    async _handleMsgAll(message: TLMessage) {
     }
 
     async _startReconnect() {
-        if (this._user_connected && !this._reconnecting) {
-            this._reconnecting = true
+        if (this._userConnected && !this._reconnecting) {
+            this._reconnecting = true;
             // TODO Should we set this?
             // this._user_connected = false
-            this._log.info('Started reconnecting')
+            this._log.info('Started reconnecting');
             this._reconnect()
         }
     }
 
     async _reconnect() {
-        this._log.debug('Closing current connection...')
+        this._log.debug('Closing current connection...');
         try {
             await this.disconnect()
         } catch (err) {
             this._log.warn(err)
         }
-        this._send_queue.append(null)
+        // @ts-ignore
+        this._sendQueue.append(null);
 
-        this._state.reset()
-        const retries = this._retries
+        this._state.reset();
+        const retries = this._retries;
 
 
         for (let attempt = 0; attempt < retries; attempt++) {
             try {
-                await this._connect()
+                await this._connect();
                 // uncomment this if you want to resend
                 //this._send_queue.extend(Object.values(this._pending_state))
-                this._pending_state = {}
+                this._pendingState = new Map<string, RequestState>();
                 if (this._autoReconnectCallback) {
                     await this._autoReconnectCallback()
                 }
-                if (this._updateCallback){
+                if (this._updateCallback) {
                     this._updateCallback(1)
                 }
 
                 break
             } catch (e) {
-                this._log.error('WebSocket connection failed attempt : '+(attempt+1))
-                console.log(e)
-                await Helpers.sleep(this._delay)
+                this._log.error('WebSocket connection failed attempt : ' + (attempt + 1));
+                console.log(e);
+                await sleep(this._delay)
             }
         }
     }
 }
 
-module.exports = MTProtoSender

+ 115 - 87
gramjs/network/MTProtoState.js → gramjs/network/MTProtoState.ts

@@ -1,15 +1,22 @@
-const Helpers = require('../Helpers')
-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 { InvokeAfterMsg } = require('../tl').requests
-const BigInt = require('big-integer')
-const { toSignedLittleBuffer,readBufferFromBigInt } = require('../Helpers')
-const { readBigIntFromBuffer } = require('../Helpers')
-
-class MTProtoState {
+import bigInt from 'big-integer';
+import {AuthKey} from "../crypto/AuthKey";
+import {helpers} from "../index";
+import {Api} from '../tl';
+import {sha256, toSignedLittleBuffer} from "../Helpers";
+import {GZIPPacked, TLMessage} from "../tl/core";
+import {BinaryReader, BinaryWriter} from "../extensions";
+import {IGE} from "../crypto/IGE";
+import {InvalidBufferError, SecurityError} from "../errors";
+
+export class MTProtoState {
+    private authKey?: AuthKey;
+    private _log: any;
+    timeOffset: number;
+    salt: bigInt.BigInteger;
+    private id: bigInt.BigInteger;
+    _sequence: number;
+    private _lastMsgId: bigInt.BigInteger;
+
     /**
      *
      `telethon.network.mtprotosender.MTProtoSender` needs to hold a state
@@ -35,13 +42,13 @@ class MTProtoState {
      * @param authKey
      * @param loggers
      */
-    constructor(authKey, loggers) {
-        this.authKey = authKey
-        this._log = loggers
-        this.timeOffset = 0
-        this.salt = 0
-
-        this.id = this._sequence = this._lastMsgId = null
+    constructor(authKey?: AuthKey, loggers?: any) {
+        this.authKey = authKey;
+        this._log = loggers;
+        this.timeOffset = 0;
+        this.salt = bigInt.zero;
+        this._sequence = 0;
+        this.id = this._lastMsgId = bigInt.zero;
         this.reset()
     }
 
@@ -50,9 +57,9 @@ class MTProtoState {
      */
     reset() {
         // Session IDs can be random on every connection
-        this.id = Helpers.generateRandomLong(true)
-        this._sequence = 0
-        this._lastMsgId = BigInt(0)
+        this.id = helpers.generateRandomLong(true);
+        this._sequence = 0;
+        this._lastMsgId = bigInt.zero
     }
 
     /**
@@ -60,7 +67,7 @@ class MTProtoState {
      * used when the time offset changed.
      * @param message
      */
-    updateMessageId(message) {
+    updateMessageId(message: any) {
         message.msgId = this._getNewMsgId()
     }
 
@@ -71,15 +78,15 @@ class MTProtoState {
      * @param client
      * @returns {{iv: Buffer, key: Buffer}}
      */
-    async _calcKey(authKey, msgKey, client) {
-        const x = client === true ? 0 : 8
-        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 }
+    async _calcKey(authKey: Buffer, msgKey: Buffer, client: boolean) {
+        const x = client ? 0 : 8;
+        const [sha256a, sha256b] = await Promise.all([
+            sha256(Buffer.concat([msgKey, authKey.slice(x, x + 36)])),
+            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}
     }
 
     /**
@@ -90,22 +97,25 @@ class MTProtoState {
      * @param contentRelated
      * @param afterId
      */
-    async writeDataAsMessage(buffer, data, contentRelated, afterId) {
-        const msgId = this._getNewMsgId()
-        const seqNo = this._getSeqNo(contentRelated)
-        let body
+    async writeDataAsMessage(buffer: BinaryWriter, data: Buffer, contentRelated: boolean, afterId: bigInt.BigInteger) {
+        const msgId = this._getNewMsgId();
+        const seqNo = this._getSeqNo(contentRelated);
+        let body;
         if (!afterId) {
             body = await GZIPPacked.gzipIfSmaller(contentRelated, data)
         } else {
-            body = await GZIPPacked.gzipIfSmaller(contentRelated, new InvokeAfterMsg(afterId, data).getBytes())
+            body = await GZIPPacked.gzipIfSmaller(contentRelated, new Api.InvokeAfterMsg({
+                msgId: afterId,
+                query: data
+            }).getBytes())
         }
-        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)
+        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
     }
 
@@ -114,67 +124,87 @@ class MTProtoState {
      * following MTProto 2.0 guidelines core.telegram.org/mtproto/description.
      * @param 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)
+    async encryptMessageData(data: Buffer) {
+        if (!this.authKey) {
+            throw new Error("Auth key unset");
+        }
+
+        await this.authKey.waitForKey();
+        const authKey = this.authKey.getKey();
+        if (!authKey) {
+            throw new Error("Auth key unset");
+        }
+
+        if (!this.salt || !this.id || !authKey || !this.authKey.keyId) {
+            throw new Error("Unset params");
+        }
+        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 = await Helpers.sha256(Buffer.concat([this.authKey.getKey().slice(88, 88 + 32), data, padding]))
+        const msgKeyLarge = await sha256(Buffer.concat([authKey.slice(88, 88 + 32), data, padding]));
         // "msg_key = substr (msg_key_large, 8, 16)"
-        const msgKey = msgKeyLarge.slice(8, 24)
+        const msgKey = msgKeyLarge.slice(8, 24);
 
-        const { iv, key } = await this._calcKey(this.authKey.getKey(), msgKey, true)
+        const {iv, key} = await this._calcKey(authKey, msgKey, true);
 
-        const keyId = Helpers.readBufferFromBigInt(this.authKey.keyId, 8)
-        return Buffer.concat([keyId, msgKey, new IGE(key,iv).encryptIge(Buffer.concat([data, padding]))])
+        const keyId = helpers.readBufferFromBigInt(this.authKey.keyId, 8);
+        return Buffer.concat([keyId, msgKey, new IGE(key, iv).encryptIge(Buffer.concat([data, padding]))])
     }
 
     /**
      * Inverse of `encrypt_message_data` for incoming server messages.
      * @param body
      */
-    async decryptMessageData(body) {
+    async decryptMessageData(body: Buffer) {
+        if (!this.authKey) {
+            throw new Error("Auth key unset");
+        }
+
         if (body.length < 8) {
             throw new InvalidBufferError(body)
         }
 
         // TODO Check salt,sessionId, and sequenceNumber
-        const keyId = Helpers.readBigIntFromBuffer(body.slice(0, 8))
-        if (keyId.neq(this.authKey.keyId)) {
-            throw new SecurityError('Server replied with an invalid auth key')
+        const keyId = helpers.readBigIntFromBuffer(body.slice(0, 8));
+        if (!this.authKey.keyId || keyId.neq(this.authKey.keyId)) {
+            throw new SecurityError('Server replied with an invalid auth key');
         }
+        const authKey = this.authKey.getKey();
+        if (!authKey) {
+            throw new SecurityError('Unset AuthKey');
 
-        const msgKey = body.slice(8, 24)
-        const { iv, key } = await this._calcKey(this.authKey.getKey(), msgKey, false)
-        body = new IGE(key,iv).decryptIge(body.slice(24))
+        }
+        const msgKey = body.slice(8, 24);
+        const {iv, key} = await this._calcKey(authKey, 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 = await Helpers.sha256(Buffer.concat([this.authKey.getKey().slice(96, 96 + 32), body]))
+        const ourKey = await sha256(Buffer.concat([authKey.slice(96, 96 + 32), body]));
 
         if (!msgKey.equals(ourKey.slice(8, 24))) {
             throw new SecurityError('Received msg_key doesn\'t match with expected one')
         }
 
-        const reader = new BinaryReader(body)
-        reader.readLong() // removeSalt
-        const serverId = reader.readLong()
+        const reader = new BinaryReader(body);
+        reader.readLong(); // removeSalt
+        const serverId = reader.readLong();
         if (serverId !== this.id) {
             // throw new SecurityError('Server replied with a wrong session ID');
         }
 
-        const remoteMsgId = reader.readLong()
-        const remoteSequence = reader.readInt()
-        reader.readInt() // msgLen for the inner object, padding ignored
+        const remoteMsgId = reader.readLong();
+        const remoteSequence = reader.readInt();
+        reader.readInt(); // msgLen for the inner object, padding ignored
 
         // 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 = reader.tgReadObject()
+        const obj = reader.tgReadObject();
 
         return new TLMessage(remoteMsgId, remoteSequence, obj)
     }
@@ -185,13 +215,13 @@ class MTProtoState {
      * @private
      */
     _getNewMsgId() {
-        const now = new Date().getTime() / 1000 + this.timeOffset
-        const nanoseconds = Math.floor((now - Math.floor(now)) * 1e9)
-        let newMsgId = (BigInt(Math.floor(now)).shiftLeft(BigInt(32))).or(BigInt(nanoseconds).shiftLeft(BigInt(2)))
+        const now = new Date().getTime() / 1000 + this.timeOffset;
+        const nanoseconds = Math.floor((now - Math.floor(now)) * 1e9);
+        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))
+            newMsgId = this._lastMsgId.add(bigInt(4))
         }
-        this._lastMsgId = newMsgId
+        this._lastMsgId = newMsgId;
         return newMsgId
     }
 
@@ -200,15 +230,15 @@ class MTProtoState {
      * one given a known valid message ID.
      * @param correctMsgId {BigInteger}
      */
-    updateTimeOffset(correctMsgId) {
-        const bad = this._getNewMsgId()
-        const old = this.timeOffset
-        const now = Math.floor(new Date().getTime() / 1000)
-        const correct = correctMsgId.shiftRight(BigInt(32))
-        this.timeOffset = correct - now
+    updateTimeOffset(correctMsgId: bigInt.BigInteger) {
+        const bad = this._getNewMsgId();
+        const old = this.timeOffset;
+        const now = Math.floor(new Date().getTime() / 1000);
+        const correct = correctMsgId.shiftRight(BigInt(32)).toJSNumber();
+        this.timeOffset = correct - now;
 
         if (this.timeOffset !== old) {
-            this._lastMsgId = BigInt(0)
+            this._lastMsgId = bigInt.zero;
             this._log.debug(
                 `Updated time offset (old offset ${old}, bad ${bad}, good ${correctMsgId}, new ${this.timeOffset})`,
             )
@@ -223,15 +253,13 @@ class MTProtoState {
      * @param contentRelated
      * @private
      */
-    _getSeqNo(contentRelated) {
+    _getSeqNo(contentRelated: boolean) {
         if (contentRelated) {
-            const result = this._sequence * 2 + 1
-            this._sequence += 1
+            const result = this._sequence * 2 + 1;
+            this._sequence += 1;
             return result
         } else {
             return this._sequence * 2
         }
     }
 }
-
-module.exports = MTProtoState

+ 0 - 16
gramjs/network/RequestState.js

@@ -1,16 +0,0 @@
-class RequestState {
-    constructor(request, after = null) {
-        this.containerId = null
-        this.msgId = null
-        this.request = request
-        this.data = request.getBytes()
-        this.after = after
-        this.result = null
-        this.promise = new Promise((resolve, reject) => {
-            this.resolve = resolve
-            this.reject = reject
-        })
-    }
-}
-
-module.exports = RequestState

+ 30 - 0
gramjs/network/RequestState.ts

@@ -0,0 +1,30 @@
+import {Api} from "../tl";
+
+export class RequestState {
+    public containerId: undefined;
+    public msgId?: bigInt.BigInteger;
+    public request: any;
+    public data: Buffer;
+    public after: any;
+    public result: undefined;
+    promise: Promise<unknown>;
+    // @ts-ignore
+    public resolve: (value?: any) => void;
+    // @ts-ignore
+    public reject: (reason?: any) => void;
+
+    constructor(request: any, after = undefined) {
+        this.containerId = undefined;
+        this.msgId = undefined;
+        this.request = request;
+        this.data = request.getBytes();
+        this.after = after;
+        this.result = undefined;
+        this.promise = new Promise((resolve, reject) => {
+            this.resolve = resolve;
+            this.reject = reject
+        })
+    }
+}
+
+

+ 62 - 47
gramjs/network/connection/Connection.js → gramjs/network/connection/Connection.ts

@@ -1,7 +1,7 @@
-const PromisedWebSockets = require('../../extensions/PromisedWebSockets')
-const PromisedNetSockets = require('../../extensions/PromisedNetSockets')
-const AsyncQueue = require('../../extensions/AsyncQueue')
-const {IS_NODE} = require('../../Helpers')
+import {BinaryReader, PromisedWebSockets} from '../../extensions'
+import {PromisedNetSockets} from '../../extensions'
+import {AsyncQueue} from '../../extensions'
+import {IS_NODE} from '../../Helpers'
 
 /**
  * The `Connection` class is a wrapper around ``asyncio.open_connection``.
@@ -15,37 +15,50 @@ const {IS_NODE} = require('../../Helpers')
  * the client is disconnected (includes remote disconnections).
  */
 class Connection {
-    PacketCodecClass = null
-
-    constructor(ip, port, dcId, loggers) {
-        this._ip = ip
-        this._port = port
-        this._dcId = dcId
-        this._log = loggers
-        this._connected = false
-        this._sendTask = null
-        this._recvTask = null
-        this._codec = null
-        this._obfuscation = null // TcpObfuscated and MTProxy
-        this._sendArray = new AsyncQueue()
-        this._recvArray = new AsyncQueue()
+    // @ts-ignore
+    PacketCodecClass : any; //"typeof AbridgedPacketCodec|typeof FullPacketCodec|typeof ObfuscatedConnection as "
+    private _ip: string;
+    private _port: number;
+    private _dcId: number;
+    private _log: any;
+    private _connected: boolean;
+    private _sendTask?: Promise<void>;
+    private _recvTask?: Promise<void>;
+    protected _codec: any;
+    protected _obfuscation: any;
+    private _sendArray: AsyncQueue;
+    private _recvArray: AsyncQueue;
+    protected socket: PromisedNetSockets | PromisedWebSockets;
+
+    constructor(ip: string, port: number, dcId: number, loggers: any) {
+        this._ip = ip;
+        this._port = port;
+        this._dcId = dcId;
+        this._log = loggers;
+        this._connected = false;
+        this._sendTask = undefined;
+        this._recvTask = undefined;
+        this._codec = undefined;
+        this._obfuscation = undefined; // TcpObfuscated and MTProxy
+        this._sendArray = new AsyncQueue();
+        this._recvArray = new AsyncQueue();
         this.socket = IS_NODE ? new PromisedNetSockets() : new PromisedWebSockets()
 
         //this.socket = new PromisedWebSockets()
     }
 
     async _connect() {
-        this._log.debug('Connecting')
-        this._codec = new this.PacketCodecClass(this)
-        await this.socket.connect(this._port, this._ip, this)
-        this._log.debug('Finished connecting')
+        this._log.debug('Connecting');
+        this._codec = new this.PacketCodecClass(this);
+        await this.socket.connect(this._port, this._ip);
+        this._log.debug('Finished connecting');
         // await this.socket.connect({host: this._ip, port: this._port});
         await this._initConn()
     }
 
     async connect() {
-        await this._connect()
-        this._connected = true
+        await this._connect();
+        this._connected = true;
 
         if (!this._sendTask) {
             this._sendTask = this._sendLoop()
@@ -54,12 +67,12 @@ class Connection {
     }
 
     async disconnect() {
-        this._connected = false
-        await this._recvArray.push(null)
+        this._connected = false;
+        await this._recvArray.push(undefined);
         await this.socket.close()
     }
 
-    async send(data) {
+    async send(data: Buffer) {
         if (!this._connected) {
             throw new Error('Not connected')
         }
@@ -68,10 +81,10 @@ class Connection {
 
     async recv() {
         while (this._connected) {
-            const result = await this._recvArray.pop()
+            const result = await this._recvArray.pop();
 
-            // null = sentinel value = keep trying
-            if (result) {
+            // undefined = sentinel value = keep trying
+            if (result && result.length) {
                 return result
             }
         }
@@ -82,9 +95,9 @@ class Connection {
         // TODO handle errors
         try {
             while (this._connected) {
-                const data = await this._sendArray.pop()
+                const data = await this._sendArray.pop();
                 if (!data) {
-                    this._sendTask = null
+                    this._sendTask = undefined;
                     return
                 }
                 await this._send(data)
@@ -95,18 +108,18 @@ class Connection {
     }
 
     async _recvLoop() {
-        let data
+        let data;
         while (this._connected) {
             try {
-                data = await this._recv()
+                data = await this._recv();
                 if (!data) {
                     throw new Error('no data received')
                 }
             } catch (e) {
-                this._log.info('connection closed')
+                this._log.info('connection closed');
                 //await this._recvArray.push()
-                console.log(e)
-                this.disconnect()
+                console.log(e);
+                this.disconnect();
                 return
             }
             await this._recvArray.push(data)
@@ -119,8 +132,8 @@ class Connection {
         }
     }
 
-    async _send(data) {
-        const encodedPacket = this._codec.encodePacket(data)
+    async _send(data: Buffer) {
+        const encodedPacket = this._codec.encodePacket(data);
         this.socket.write(encodedPacket)
     }
 
@@ -134,14 +147,14 @@ class Connection {
 }
 
 class ObfuscatedConnection extends Connection {
-    ObfuscatedIO = null
+    ObfuscatedIO: any = undefined;
 
     async _initConn() {
-        this._obfuscation = new this.ObfuscatedIO(this)
+        this._obfuscation = new this.ObfuscatedIO(this);
         this.socket.write(this._obfuscation.header)
     }
 
-    _send(data) {
+    async _send(data: Buffer) {
         this._obfuscation.write(this._codec.encodePacket(data))
     }
 
@@ -152,24 +165,26 @@ class ObfuscatedConnection extends Connection {
 }
 
 class PacketCodec {
-    constructor(connection) {
+    private _conn: Buffer;
+
+    constructor(connection: Buffer) {
         this._conn = connection
     }
 
-    encodePacket(data) {
+    encodePacket(data: Buffer) {
         throw new Error('Not Implemented')
 
         // Override
     }
 
-    async readPacket(reader) {
+    async readPacket(reader: BinaryReader) :Promise<Buffer>{
         // override
         throw new Error('Not Implemented')
     }
 }
 
-module.exports = {
+export {
     Connection,
     PacketCodec,
     ObfuscatedConnection,
-}
+};

+ 0 - 51
gramjs/network/connection/TCPAbridged.js

@@ -1,51 +0,0 @@
-const { readBufferFromBigInt } = require('../../Helpers')
-const { Connection, PacketCodec } = require('./Connection')
-const BigInt = require('big-integer')
-
-class AbridgedPacketCodec extends PacketCodec {
-    static tag = Buffer.from('ef', 'hex')
-    static obfuscateTag = Buffer.from('efefefef', 'hex')
-
-    constructor(props) {
-        super(props)
-        this.tag = AbridgedPacketCodec.tag
-        this.obfuscateTag = AbridgedPacketCodec.obfuscateTag
-    }
-
-    encodePacket(data) {
-        let length = data.length >> 2
-        if (length < 127) {
-            const b = Buffer.alloc(1)
-            b.writeUInt8(length, 0)
-            length = b
-        } else {
-            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 = readData[0]
-        if (length >= 127) {
-            length = Buffer.concat([await reader.read(3), Buffer.alloc(1)])
-                .readInt32LE(0)
-        }
-
-        return await reader.read(length << 2)
-    }
-}
-
-/**
- * This is the mode with the lowest overhead, as it will
- * only require 1 byte if the packet length is less than
- * 508 bytes (127 << 2, which is very common).
- */
-class ConnectionTCPAbridged extends Connection {
-    PacketCodecClass = AbridgedPacketCodec
-}
-
-module.exports = {
-    ConnectionTCPAbridged,
-    AbridgedPacketCodec,
-}

+ 51 - 0
gramjs/network/connection/TCPAbridged.ts

@@ -0,0 +1,51 @@
+import {readBufferFromBigInt} from '../../Helpers';
+import {Connection, PacketCodec} from './Connection';
+import {BinaryReader} from "../../extensions";
+
+import bigInt from "big-integer";
+
+export class AbridgedPacketCodec extends PacketCodec {
+    static tag = Buffer.from('ef', 'hex');
+    static obfuscateTag = Buffer.from('efefefef', 'hex');
+    private tag: Buffer;
+    private obfuscateTag: Buffer;
+
+    constructor(props: any) {
+        super(props);
+        this.tag = AbridgedPacketCodec.tag;
+        this.obfuscateTag = AbridgedPacketCodec.obfuscateTag;
+    }
+
+    encodePacket(data: Buffer) {
+        let length = data.length >> 2;
+        let temp;
+        if (length < 127) {
+            const b = Buffer.alloc(1);
+            b.writeUInt8(length, 0);
+            temp = b
+        } else {
+            temp = Buffer.concat([Buffer.from('7f', 'hex'), readBufferFromBigInt(bigInt(length), 3)])
+        }
+        return Buffer.concat([temp, data])
+    }
+
+    async readPacket(reader: BinaryReader): Promise<Buffer> {
+        const readData = await reader.read(1);
+        let length = readData[0];
+        if (length >= 127) {
+            length = Buffer.concat([await reader.read(3), Buffer.alloc(1)])
+                .readInt32LE(0)
+        }
+
+        return reader.read(length << 2);
+    }
+}
+
+/**
+ * This is the mode with the lowest overhead, as it will
+ * only require 1 byte if the packet length is less than
+ * 508 bytes (127 << 2, which is very common).
+ */
+export class ConnectionTCPAbridged extends Connection {
+    PacketCodecClass = AbridgedPacketCodec
+}

+ 0 - 56
gramjs/network/connection/TCPFull.js

@@ -1,56 +0,0 @@
-const { Connection, PacketCodec } = require('./Connection')
-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
-    }
-
-    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
-        }
-        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 validChecksum = crc32(Buffer.concat([packetLenSeq, body]))
-        if (!(validChecksum === checksum)) {
-            throw new InvalidChecksumError(checksum, validChecksum)
-        }
-        return body
-    }
-}
-
-class ConnectionTCPFull extends Connection {
-     PacketCodecClass = FullPacketCodec;
-}
-
-module.exports = {
-    FullPacketCodec,
-    ConnectionTCPFull,
-}

+ 55 - 0
gramjs/network/connection/TCPFull.ts

@@ -0,0 +1,55 @@
+import {Connection, PacketCodec} from './Connection';
+import {crc32} from '../../Helpers';
+import {InvalidChecksumError} from '../../errors';
+import {BinaryReader} from "../../extensions";
+
+class FullPacketCodec extends PacketCodec {
+    private _sendCounter: number;
+
+    constructor(connection: any) {
+        super(connection);
+        this._sendCounter = 0 // Telegram will ignore us otherwise
+    }
+
+    encodePacket(data: Buffer) {
+        // 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: BinaryReader): Promise<Buffer> {
+        const packetLenSeq = await reader.read(8); // 4 and 4
+        // process.exit(0);
+        if (packetLenSeq === undefined) {
+            // Return empty buffer in case of issue
+            return Buffer.alloc(0);
+        }
+        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 validChecksum = crc32(Buffer.concat([packetLenSeq, body]));
+        if (!(validChecksum === checksum)) {
+            throw new InvalidChecksumError(checksum, validChecksum)
+        }
+        return body
+    }
+}
+
+export class ConnectionTCPFull extends Connection {
+    PacketCodecClass = FullPacketCodec;
+}

+ 37 - 38
gramjs/network/connection/TCPObfuscated.js → gramjs/network/connection/TCPObfuscated.ts

@@ -1,34 +1,38 @@
-const { generateRandomBytes } = require('../../Helpers')
-const { ObfuscatedConnection } = require('./Connection')
-const { AbridgedPacketCodec } = require('./TCPAbridged')
-const CTR = require('../../crypto/CTR')
+import {generateRandomBytes} from '../../Helpers'
+import {ObfuscatedConnection} from './Connection'
+import {AbridgedPacketCodec} from './TCPAbridged'
+import {CTR} from '../../crypto/CTR';
+import {PromisedNetSockets, PromisedWebSockets} from "../../extensions";
 
 class ObfuscatedIO {
-    header = null
+    header?: Buffer = undefined;
+    private connection: PromisedNetSockets | PromisedWebSockets;
+    private _encrypt: CTR;
+    private _decrypt: CTR;
 
-    constructor(connection) {
-        this.connection = connection.socket
-        const res = this.initHeader(connection.PacketCodecClass)
-        this.header = res.random
+    constructor(connection: any) {
+        this.connection = connection.socket;
+        const res = this.initHeader(connection.PacketCodecClass);
+        this.header = res.random;
 
-        this._encrypt = res.encryptor
+        this._encrypt = res.encryptor;
         this._decrypt = res.decryptor
     }
 
-    initHeader(packetCodec) {
+    initHeader(packetCodec: any) {
         // Obfuscated messages secrets cannot start with any of these
         const keywords = [Buffer.from('50567247', 'hex'), Buffer.from('474554', 'hex'),
-            Buffer.from('504f5354', 'hex'), Buffer.from('eeeeeeee', 'hex')]
-        let random
+            Buffer.from('504f5354', 'hex'), Buffer.from('eeeeeeee', 'hex')];
+        let random;
 
         // eslint-disable-next-line no-constant-condition
         while (true) {
-            random = generateRandomBytes(64)
+            random = generateRandomBytes(64);
             if (random[0] !== 0xef && !(random.slice(4, 8).equals(Buffer.alloc(4)))) {
-                let ok = true
+                let ok = true;
                 for (const key of keywords) {
                     if (key.equals(random.slice(0, 4))) {
-                        ok = false
+                        ok = false;
                         break
                     }
                 }
@@ -37,42 +41,37 @@ class ObfuscatedIO {
                 }
             }
         }
-        random = random.toJSON().data
+        random = random.toJSON().data;
 
-        const randomReversed = Buffer.from(random.slice(8, 56)).reverse()
+        const randomReversed = Buffer.from(random.slice(8, 56)).reverse();
         // Encryption has "continuous buffer" enabled
-        const encryptKey = Buffer.from(random.slice(8, 40))
-        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 CTR(encryptKey, encryptIv)
-        const decryptor = new CTR(decryptKey, decryptIv)
+        const encryptKey = Buffer.from(random.slice(8, 40));
+        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 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([
-            Buffer.from(random.slice(0, 56)), Buffer.from(encryptor.encrypt(random).slice(56, 64)),Buffer.from(random.slice(64)) ,
-        ])
-        return { random, encryptor, decryptor }
+            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.readExactly(n)
+    async read(n: number) {
+        const data = await this.connection.readExactly(n);
         return this._decrypt.encrypt(data)
     }
 
-    write(data) {
+    write(data: Buffer) {
         this.connection.write(this._encrypt.encrypt(data))
     }
 }
 
-class ConnectionTCPObfuscated extends ObfuscatedConnection {
-    ObfuscatedIO = ObfuscatedIO
+export class ConnectionTCPObfuscated extends ObfuscatedConnection {
+    ObfuscatedIO = ObfuscatedIO;
     PacketCodecClass = AbridgedPacketCodec
 }
-
-module.exports = {
-    ConnectionTCPObfuscated,
-}
-

+ 0 - 11
gramjs/network/connection/index.js

@@ -1,11 +0,0 @@
-const { Connection } = require('./Connection')
-const { ConnectionTCPFull } = require('./TCPFull')
-const { ConnectionTCPAbridged } = require('./TCPAbridged')
-const { ConnectionTCPObfuscated } = require('./TCPObfuscated')
-
-module.exports = {
-    Connection,
-    ConnectionTCPFull,
-    ConnectionTCPAbridged,
-    ConnectionTCPObfuscated,
-}

+ 4 - 0
gramjs/network/connection/index.ts

@@ -0,0 +1,4 @@
+export {Connection} from './Connection';
+export {ConnectionTCPFull} from './TCPFull';
+export {ConnectionTCPAbridged} from './TCPAbridged';
+export {ConnectionTCPObfuscated} from './TCPObfuscated';

+ 0 - 32
gramjs/network/index.js

@@ -1,32 +0,0 @@
-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,
-    ConnectionTCPAbridged,
-    ConnectionTCPObfuscated,
-} = require('./connection')
-module.exports = {
-    Connection,
-    ConnectionTCPFull,
-    ConnectionTCPAbridged,
-    ConnectionTCPObfuscated,
-    MTProtoPlainSender,
-    doAuthentication,
-    MTProtoSender,
-    UpdateConnectionState,
-}

+ 30 - 0
gramjs/network/index.ts

@@ -0,0 +1,30 @@
+export {MTProtoPlainSender} from './MTProtoPlainSender'
+export {doAuthentication} from './Authenticator'
+export {MTProtoSender} from './MTProtoSender'
+
+interface states {
+    disconnected: -1,
+    connected: 1,
+    broken: 0,
+}
+
+export class UpdateConnectionState {
+    static states = {
+        disconnected: -1,
+        connected: 1,
+        broken: 0,
+    };
+    private state: number;
+
+    constructor(state: number) {
+        this.state = state
+
+    }
+}
+
+export {
+    Connection,
+    ConnectionTCPFull,
+    ConnectionTCPAbridged,
+    ConnectionTCPObfuscated,
+} from './connection'

+ 114 - 0
gramjs/requestIter.ts

@@ -0,0 +1,114 @@
+import {TelegramClient} from "./client/TelegramClient";
+// @ts-ignore
+import {sleep} from '../Helpers';
+import {helpers} from "./index";
+
+interface BaseRequestIterInterface {
+    reverse?: boolean,
+    waitTime?: number,
+}
+export class RequestIter implements AsyncIterable<any> {
+
+    public client: TelegramClient;
+    public reverse: boolean | undefined;
+    public waitTime: number | undefined;
+    protected readonly limit: number;
+    protected left: number;
+    protected buffer: Array<any> | undefined;
+    private index: number;
+    protected total: number | undefined;
+    private lastLoad: number;
+    [key: string]: any;
+
+    constructor(client: TelegramClient, limit: number, params: BaseRequestIterInterface = {}, args = {}) {
+        this.client = client;
+        this.reverse = params.reverse;
+        this.waitTime = params.waitTime;
+        this.limit = Math.max(!limit ? Number.MAX_SAFE_INTEGER : limit, 0);
+        this.left = this.limit;
+        this.buffer = undefined;
+        for (const name in args) {
+            this[name as any] = (args as any)[name];
+        }
+        this.index = 0;
+        this.total = undefined;
+        this.lastLoad = 0
+    }
+
+
+    async _init(...args: any): Promise<boolean | void> {
+        // for overload
+    }
+
+    [Symbol.asyncIterator](): AsyncIterator<any, any, undefined> {
+        this.buffer = undefined;
+        this.index = 0;
+        this.lastLoad = 0;
+        this.left = this.limit;
+        return {
+            next: async () => {
+
+                if (this.buffer == undefined) {
+                    this.buffer = [];
+                    // @ts-ignore
+                    if (await this._init(this.args)) {
+                        this.left = this.buffer.length;
+                    }
+                }
+                if (this.left <= 0) {
+                    return {
+                        value: undefined,
+                        done: true,
+                    };
+                }
+                if (this.index == this.buffer.length) {
+                    if (this.waitTime) {
+                        await sleep(this.waitTime - ((new Date().getTime() / 1000) - this.lastLoad));
+                    }
+                    this.lastLoad = new Date().getTime() / 1000;
+                    this.index = 0;
+                    this.buffer = [];
+                    const nextChunk = await this._loadNextChunk();
+                    if (nextChunk === false) {
+                        // we exit;
+                        return {
+                            value: undefined,
+                            done: true,
+                        };
+                    }
+                    if (nextChunk) {
+                        this.left = this.buffer.length;
+                    }
+                }
+                if (!this.buffer) {
+                    return {
+                        value: undefined,
+                        done: true,
+                    };
+                }
+                const result = this.buffer[this.index];
+                this.left -= 1;
+                this.index += 1;
+                return {
+                    value: result,
+                    done: false,
+                };
+            }
+        }
+    }
+
+    async collect() {
+        const result = new helpers.TotalList();
+        for await (const message of this) {
+            result.push(message);
+        }
+        result.total = this.total;
+        return result;
+    }
+
+    async _loadNextChunk(): Promise<boolean | undefined> {
+        throw new Error("Not Implemented");
+    }
+
+}
+

+ 12 - 12
gramjs/sessions/Abstract.js → gramjs/sessions/Abstract.ts

@@ -1,4 +1,6 @@
-class Session {
+import {AuthKey} from "../crypto/AuthKey";
+
+export class Session {
     constructor() {
 
     }
@@ -8,6 +10,7 @@ class Session {
      * @param toInstance {Session|null}
      * @returns {Session}
      */
+
     /* CONTEST
     clone(toInstance = null) {
         return toInstance || new this.constructor()
@@ -21,28 +24,28 @@ class Session {
      * @param serverAddress {string}
      * @param port {number}
      */
-    setDC(dcId, serverAddress, port) {
+    setDC(dcId: number, serverAddress: string, port: number) {
         throw new Error('Not implemented')
     }
 
     /**
      * Returns the currently-used data center ID.
      */
-    get dcId() {
+    get dcId(): number | undefined {
         throw new Error('Not Implemented')
     }
 
     /**
      * Returns the server address where the library should connect to.
      */
-    get serverAddress() {
+    get serverAddress(): string | undefined {
         throw new Error('Not Implemented')
     }
 
     /**
      * Returns the port to which the library should connect to.
      */
-    get port() {
+    get port(): number | undefined {
         throw new Error('Not Implemented')
     }
 
@@ -50,7 +53,7 @@ class Session {
      * Returns an ``AuthKey`` instance associated with the saved
      * data center, or `None` if a new one should be generated.
      */
-    get authKey() {
+    get authKey(): AuthKey | undefined {
         throw new Error('Not Implemented')
     }
 
@@ -58,7 +61,7 @@ class Session {
      * Sets the ``AuthKey`` to be used for the saved data center.
      * @param value
      */
-    set authKey(value) {
+    set authKey(value: AuthKey | undefined) {
         throw new Error('Not Implemented')
     }
 
@@ -112,6 +115,7 @@ class Session {
      * Called on client disconnection. Should be used to
      * free any used resources. Can be left empty if none.
      */
+
     /*CONTEST
     close() {
 
@@ -151,12 +155,10 @@ class Session {
      * whatever information is relevant (e.g., ID or access hash).
      * @param tlo
      */
-    /*CONTEST
-    processEntities(tlo) {
+    processEntities(tlo:any) {
         throw new Error('Not Implemented')
     }
 
-     */
 
     /**
      * Turns the given key into an ``InputPeer`` (e.g. ``InputPeerUser``).
@@ -171,5 +173,3 @@ class Session {
 
      */
 }
-
-module.exports = Session

+ 0 - 256
gramjs/sessions/Memory.js

@@ -1,256 +0,0 @@
-const utils = require('../Utils')
-const types = require('../tl').constructors
-const Session = require('./Abstract')
-
-class MemorySession extends Session {
-    constructor() {
-        super()
-
-        this._serverAddress = null
-        this._dcId = 0
-        this._port = null
-        this._takeoutId = null
-
-        this._entities = new Set()
-        this._updateStates = {}
-    }
-
-    setDC(dcId, serverAddress, port) {
-        this._dcId = dcId | 0
-        this._serverAddress = serverAddress
-        this._port = port
-    }
-
-    get dcId() {
-        return this._dcId
-    }
-
-    get serverAddress() {
-        return this._serverAddress
-    }
-
-    get port() {
-        return this._port
-    }
-
-    get authKey() {
-        return this._authKey
-    }
-
-    set authKey(value) {
-        this._authKey = value
-    }
-    /* CONTEST
-    get takeoutId() {
-        return this._takeoutId
-    }
-
-    set takeoutId(value) {
-        this._takeoutId = value
-    }
-
-
-    getUpdateState(entityId) {
-        return this._updateStates[entityId]
-    }
-
-    setUpdateState(entityId, state) {
-        return this._updateStates[entityId] = state
-    }
-
-    close() {
-    }
-
-    save() {
-    }
-
-    async load() {
-
-    }
-
-    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.classType === "constructor")) {
-            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 `accessHash`.
-            // See issues #354 and #392. It also checks that the entity
-            // is not `min`, because its `accessHash` 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.classType === "constructor" && utils.isListLike(tlo)) {
-            // This may be a list of users already for instance
-            entities = tlo
-        } else {
-            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)
-        for (const e of entities) {
-            const row = this._entityToRow(e)
-            if (row) {
-                rows.push(row)
-            }
-        }
-        return rows
-    }
-
-    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]]
-            }
-        }
-    }
-
-    getEntityRowsByUsername(username) {
-        for (const e of this._entities) { // id, hash, username, phone, name
-            if (e[2] === username) {
-                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]]
-            }
-        }
-    }
-
-    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.classType === 'constructor') {
-                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 = MemorySession

+ 267 - 0
gramjs/sessions/Memory.ts

@@ -0,0 +1,267 @@
+import {Session} from './Abstract';
+import {AuthKey} from "../crypto/AuthKey";
+import {Api} from "../tl";
+import bigInt from "big-integer";
+
+import {getDisplayName, getInputPeer, getPeerId, isArrayLike} from "../Utils";
+import {utils} from "../index";
+import {EntityLike} from "../define";
+
+export class MemorySession extends Session {
+    protected _serverAddress?: string;
+    protected _dcId: number;
+    protected _port?: number;
+    private _takeoutId: undefined;
+    private _entities: Set<any>;
+    private _updateStates: {};
+    protected _authKey?: AuthKey;
+
+    constructor() {
+        super();
+
+        this._serverAddress = undefined;
+        this._dcId = 0;
+        this._port = undefined;
+        this._takeoutId = undefined;
+
+        this._entities = new Set();
+        this._updateStates = {}
+    }
+
+    setDC(dcId: number, serverAddress: string, port: number) {
+        this._dcId = dcId | 0;
+        this._serverAddress = serverAddress;
+        this._port = port
+    }
+
+    get dcId() {
+        return this._dcId;
+    }
+
+    get serverAddress() {
+        return this._serverAddress
+    }
+
+    get port() {
+        return this._port
+    }
+
+    get authKey() {
+        return this._authKey;
+    }
+
+    set authKey(value) {
+        this._authKey = value;
+    }
+
+    get takeoutId() {
+        return this._takeoutId
+    }
+
+    set takeoutId(value) {
+        this._takeoutId = value
+    }
+
+    /*
+        getUpdateState(entityId:number) {
+            return this._updateStates[entityId]
+        }
+
+        setUpdateState(entityId, state) {
+            return this._updateStates[entityId] = state
+        }
+    */
+    close() {
+    }
+
+    save() {
+    }
+
+    async load() {
+
+    }
+
+    delete() {
+    }
+
+    _entityValuesToRow(id: number, hash: bigInt.BigInteger, username: string, phone: string, name: string) {
+        // 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: any) {
+        if (!(e.classType === "constructor")) {
+            return
+        }
+        let p;
+        let markedId;
+        try {
+            p = getInputPeer(e, false);
+            markedId = getPeerId(p)
+        } catch (e) {
+            return
+        }
+        let pHash;
+        if (p instanceof Api.InputPeerUser || p instanceof Api.InputPeerChannel) {
+            pHash = p.accessHash
+        } else if (p instanceof Api.InputPeerChat) {
+            pHash = bigInt.zero;
+        } else {
+            return
+        }
+
+        let username = e.username;
+        if (username) {
+            username = username.toLowerCase()
+        }
+        const phone = e.phone;
+        const name = getDisplayName(e);
+        return this._entityValuesToRow(markedId, pHash, username, phone, name)
+    }
+
+    _entitiesToRows(tlo: any) {
+        let entities: any = [];
+        if (tlo.classType === "constructor" && isArrayLike(tlo)) {
+            // This may be a list of users already for instance
+            entities = tlo;
+        } else {
+            if (tlo instanceof Object) {
+                if ('user' in tlo) {
+                    entities.push(tlo.user)
+                }
+                if ('chats' in tlo && isArrayLike(tlo.chats)) {
+                    entities.concat(tlo.chats)
+                }
+                if ('users' in tlo && isArrayLike(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
+    }
+
+    processEntities(tlo: any) {
+        const entitiesSet = this._entitiesToRows(tlo);
+        for (const e of entitiesSet) {
+            this._entities.add(e)
+        }
+    }
+
+    getEntityRowsByPhone(phone: string) {
+        for (const e of this._entities) { // id, hash, username, phone, name
+            if (e[3] === phone) {
+                return [e[0], e[1]]
+            }
+        }
+    }
+
+    getEntityRowsByUsername(username: string) {
+        for (const e of this._entities) { // id, hash, username, phone, name
+            if (e[2] === username) {
+                return [e[0], e[1]]
+            }
+        }
+    }
+
+    getEntityRowsByName(name: string) {
+        for (const e of this._entities) { // id, hash, username, phone, name
+            if (e[4] === name) {
+                return [e[0], e[1]]
+            }
+        }
+    }
+
+    getEntityRowsById(id: number, 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 Api.PeerUser({userId: id})),
+                utils.getPeerId(new Api.PeerChat({chatId: id})),
+                utils.getPeerId(new Api.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: EntityLike) {
+        let exact;
+            if (typeof key === 'object' && key.SUBCLASS_OF_ID) {
+                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 (typeof key === 'object' && key.classType === 'constructor') {
+                    key = utils.getPeerId(key);
+                    exact = true
+                } else {
+                    exact = !(typeof key == 'number') || key < 0
+                }
+            }
+
+
+        let result = undefined;
+        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 === Api.PeerUser) {
+                return new Api.InputPeerUser({userId: entityId, accessHash: entityHash})
+            } else if (kind === Api.PeerChat) {
+                return new Api.InputPeerChat({chatId: entityId})
+            } else if (kind === Api.PeerChannel) {
+                return new Api.InputPeerChannel({channelId: entityId, accessHash: entityHash})
+            }
+        } else {
+            throw new Error('Could not find input entity with key ' + key)
+        }
+    }
+
+}
+

+ 21 - 14
gramjs/sessions/StringSession.js → gramjs/sessions/StringSession.ts

@@ -1,10 +1,13 @@
-const MemorySession = require('./Memory')
-const AuthKey = require('../crypto/AuthKey')
-const BinaryReader = require('../extensions/BinaryReader')
+import {MemorySession} from "./Memory";
+import {BinaryReader} from "../extensions";
+import {AuthKey} from "../crypto/AuthKey";
+
 const CURRENT_VERSION = '1'
 
 
-class StringSession extends MemorySession {
+export class StringSession extends MemorySession {
+    private _key?: Buffer;
+
     /**
      * This session file can be easily saved and loaded as a string. According
      * to the initial design, it contains only the data that is necessary for
@@ -20,7 +23,7 @@ class StringSession extends MemorySession {
      * `decode` definition must be ``function decode(value: string) -> Buffer:``.
      * @param session {string|null}
      */
-    constructor(session = null) {
+    constructor(session?: string) {
         super()
         if (session) {
             if (session[0] !== CURRENT_VERSION) {
@@ -44,7 +47,7 @@ class StringSession extends MemorySession {
      * @param x {Buffer}
      * @returns {string}
      */
-    static encode(x) {
+    static encode(x: Buffer) {
         return x.toString('base64')
     }
 
@@ -52,21 +55,26 @@ class StringSession extends MemorySession {
      * @param x {string}
      * @returns {Buffer}
      */
-    static decode(x) {
+    static decode(x: string) {
         return Buffer.from(x, 'base64')
     }
 
     async load() {
         if (this._key) {
-            this._authKey = new AuthKey()
-            await this._authKey.setKey(this._key)
+            this._authKey = new AuthKey();
+            await this._authKey.setKey(this._key);
         }
     }
 
     save() {
-        if (!this.authKey) {
+        if (!this.authKey || !this.serverAddress || !this.port) {
             return ''
         }
+        // TS is weird
+        const key = this.authKey.getKey();
+        if (!key) {
+            return '';
+        }
         const dcBuffer = Buffer.from([this.dcId])
         const addressBuffer = Buffer.from(this.serverAddress)
         const addressLengthBuffer = Buffer.alloc(2)
@@ -79,11 +87,11 @@ class StringSession extends MemorySession {
             addressLengthBuffer,
             addressBuffer,
             portBuffer,
-            this.authKey.getKey(),
+            key,
         ]))
     }
 
-    getAuthKey(dcId) {
+    getAuthKey(dcId?: number) {
         if (dcId && dcId !== this.dcId) {
             // Not supported.
             return undefined
@@ -92,7 +100,7 @@ class StringSession extends MemorySession {
         return this.authKey
     }
 
-    setAuthKey(authKey, dcId) {
+    setAuthKey(authKey?: AuthKey, dcId?: number) {
         if (dcId && dcId !== this.dcId) {
             // Not supported.
             return undefined
@@ -102,4 +110,3 @@ class StringSession extends MemorySession {
     }
 }
 
-module.exports = StringSession

+ 0 - 9
gramjs/sessions/index.js

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

+ 5 - 0
gramjs/sessions/index.ts

@@ -0,0 +1,5 @@
+export {MemorySession} from './Memory';
+export {StringSession} from './StringSession';
+// @ts-ignore
+//export {CacheApiSession} from './CacheApiSession';
+

+ 0 - 19
gramjs/tl/AllTLObjects.js

@@ -1,19 +0,0 @@
-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
-}

+ 16 - 0
gramjs/tl/AllTLObjects.ts

@@ -0,0 +1,16 @@
+import {Api} from './api'
+
+export const LAYER = 122;
+const tlobjects: any = {};
+
+
+for (const tl of Object.values(Api)) {
+    if ('CONSTRUCTOR_ID' in tl) {
+        tlobjects[tl.CONSTRUCTOR_ID] = tl
+    } else {
+        for (const sub of Object.values(tl)) {
+            tlobjects[sub.CONSTRUCTOR_ID] = sub
+        }
+    }
+}
+export {tlobjects};

+ 0 - 42
gramjs/tl/MTProtoRequest.js

@@ -1,42 +0,0 @@
-class MTProtoRequest {
-    constructor() {
-        this.sent = false
-        this.msgId = 0 // long
-        this.sequence = 0
-
-        this.dirty = false
-        this.sendTime = 0
-        this.confirmReceived = false
-
-        // These should be overrode
-
-        this.constructorId = 0
-        this.confirmed = false
-        this.responded = false
-    }
-
-    // these should not be overrode
-    onSendSuccess() {
-        this.sendTime = new Date().getTime()
-        this.sent = true
-    }
-
-    onConfirm() {
-        this.confirmReceived = true
-    }
-
-    needResend() {
-        return this.dirty || (this.confirmed && !this.confirmReceived && new Date().getTime() - this.sendTime > 3000)
-    }
-
-    // These should be overrode
-    onSend() {
-        throw Error('Not overload ' + this.constructor.name)
-    }
-
-    onResponse(buffer) {}
-
-    onException(exception) {}
-}
-
-module.exports = MTProtoRequest

+ 53 - 0
gramjs/tl/MTProtoRequest.ts

@@ -0,0 +1,53 @@
+export class MTProtoRequest {
+    private sent: boolean;
+    private sequence: number;
+    private msgId: number;
+    private dirty: boolean;
+    private sendTime: number;
+    private confirmReceived: boolean;
+    private constructorId: number;
+    private confirmed: boolean;
+    private responded: boolean;
+
+    constructor() {
+        this.sent = false;
+        this.msgId = 0; // long
+        this.sequence = 0;
+
+        this.dirty = false;
+        this.sendTime = 0;
+        this.confirmReceived = false;
+
+        // These should be overrode
+
+        this.constructorId = 0;
+        this.confirmed = false;
+        this.responded = false
+    }
+
+    // these should not be overrode
+    onSendSuccess() {
+        this.sendTime = new Date().getTime();
+        this.sent = true
+    }
+
+    onConfirm() {
+        this.confirmReceived = true
+    }
+
+    needResend() {
+        return this.dirty || (this.confirmed && !this.confirmReceived && new Date().getTime() - this.sendTime > 3000)
+    }
+
+    // These should be overrode
+    onSend() {
+        throw Error('Not overload ' + this.constructor.name)
+    }
+
+    onResponse(buffer: Buffer) {
+    }
+
+    onException(exception: Error) {
+    }
+}
+

File diff suppressed because it is too large
+ 213 - 514
gramjs/tl/api.d.ts


+ 56 - 49
gramjs/tl/api.js

@@ -1,27 +1,31 @@
+const { generateRandomBytes, readBigIntFromBuffer } = require('../Helpers')
+
+
+function generateRandomBigInt() {
+    return readBigIntFromBuffer(generateRandomBytes(8), false)
+}
+
 const {
     parseTl,
     serializeBytes,
     serializeDate,
 } = require('./generationHelpers')
-const { IS_NODE,toSignedLittleBuffer } = require('../Helpers')
-let tlContent,schemeContent
-if (IS_NODE){
+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 = 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'
+    'chatId,int',
 ])
 const NAMED_BLACKLIST = new Set([
-    'discardEncryption'
+    'discardEncryption',
 ])
 const AUTO_CASTS = new Set([
     'InputPeer',
@@ -33,10 +37,9 @@ const AUTO_CASTS = new Set([
     'InputPhoto',
     'InputMessage',
     'InputDocument',
-    'InputChatPhoto'
+    'InputChatPhoto',
 ])
 
- */
 const CACHING_SUPPORTED = typeof self !== 'undefined' && self.localStorage !== undefined
 
 const CACHE_KEY = 'GramJs:apiCache'
@@ -85,7 +88,6 @@ function mergeWithNamespaces(obj1, obj2) {
             Object.assign(result[key], obj2[key])
         }
     })
-
     return result
 }
 
@@ -129,37 +131,36 @@ function argToBytes(x, type) {
         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)
+    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) {
@@ -207,12 +208,14 @@ function getArgFromReader(reader, arg) {
     }
 }
 
+
 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
@@ -226,10 +229,13 @@ function createClasses(classesType, params) {
 
             constructor(args) {
                 args = args || {}
-                Object.keys(args)
-                    .forEach(argName => {
+                for (const argName in argsConfig) {
+                    if (argName === 'randomId' && !args[argName]) {
+                        this[argName] = generateRandomBigInt()
+                    } else {
                         this[argName] = args[argName]
-                    })
+                    }
+                }
             }
 
             static fromReader(reader) {
@@ -269,7 +275,7 @@ function createClasses(classesType, params) {
                 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'){
+                            if (this[arg] === false || this[arg] === null || this[arg] === undefined || argsConfig[arg].type === 'true') {
                                 continue
                             }
                         }
@@ -288,7 +294,7 @@ function createClasses(classesType, params) {
                                 let flagCalculate = 0
                                 for (const f in argsConfig) {
                                     if (argsConfig[f].isFlag) {
-                                        if (!this[f]) {
+                                        if (this[f] === false || this[f] === undefined || this[f] === null) {
                                             flagCalculate |= 0
                                         } else {
                                             flagCalculate |= 1 << argsConfig[f].flagIndex
@@ -341,7 +347,6 @@ function createClasses(classesType, params) {
                 }
             }
 
-            /*CONTEST
             async resolve(client, utils) {
 
                 if (classesType !== 'request') {
@@ -371,7 +376,7 @@ function createClasses(classesType, params) {
                         }
                     }
                 }
-            }*/
+            }
         }
 
         if (namespace) {
@@ -388,4 +393,6 @@ function createClasses(classesType, params) {
     return classes
 }
 
-module.exports = buildApiFromTlSchema()
+const api = buildApiFromTlSchema()
+
+module.exports = { Api: api }

+ 0 - 57
gramjs/tl/core/GZIPPacked.js

@@ -1,57 +0,0 @@
-const { serializeBytes } = require('../index')
-const { inflate } = require('pako/dist/pako_inflate')
-//CONTEST const { deflate } = require('pako/dist/pako_deflate')
-
-class GZIPPacked {
-    static CONSTRUCTOR_ID = 0x3072cfa1
-    static classType = 'constructor'
-
-    constructor(data) {
-        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()
-            if (gzipped.length < data.length) {
-                return gzipped
-            }
-        }
-        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([
-            g,
-            serializeBytes(await GZIPPacked.gzip(this.data)),
-        ])
-    }
-
-    static async read(reader) {
-        const constructor = reader.readInt(false)
-        if (constructor !== GZIPPacked.CONSTRUCTOR_ID) {
-            throw new Error('not equal')
-        }
-        return await GZIPPacked.gzip(reader.tgReadBytes())
-    }
-
-    static async fromReader(reader) {
-        return new GZIPPacked(await GZIPPacked.ungzip(reader.tgReadBytes()))
-    }
-}
-
-module.exports = GZIPPacked

+ 59 - 0
gramjs/tl/core/GZIPPacked.ts

@@ -0,0 +1,59 @@
+import {serializeBytes} from '../index';
+import {inflate} from 'pako';
+import {BinaryReader} from "../../extensions/BinaryReader";
+
+export class GZIPPacked {
+    static CONSTRUCTOR_ID = 0x3072cfa1;
+    static classType = 'constructor';
+    data: Buffer;
+    private CONSTRUCTOR_ID: number;
+    private classType: string;
+
+    constructor(data: Buffer) {
+        this.data = data;
+        this.CONSTRUCTOR_ID = 0x3072cfa1;
+        this.classType = 'constructor'
+    }
+
+    static async gzipIfSmaller(contentRelated: boolean, data: Buffer) {
+        if (contentRelated && data.length > 512) {
+            const gzipped = await (new GZIPPacked(data)).toBytes();
+            if (gzipped.length < data.length) {
+                return gzipped
+            }
+        }
+        return data
+    }
+
+    static gzip(input: Buffer) {
+        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: Buffer) {
+        return Buffer.from(inflate(input))
+    }
+
+    async toBytes() {
+        const g = Buffer.alloc(4);
+        g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0);
+        return Buffer.concat([
+            g,
+            serializeBytes(await GZIPPacked.gzip(this.data)),
+        ])
+    }
+
+    static async read(reader: BinaryReader) {
+        const constructor = reader.readInt(false);
+        if (constructor !== GZIPPacked.CONSTRUCTOR_ID) {
+            throw new Error('not equal')
+        }
+        return await GZIPPacked.gzip(reader.tgReadBytes())
+    }
+
+    static async fromReader(reader: BinaryReader) {
+        const data = reader.tgReadBytes();
+        return new GZIPPacked(await GZIPPacked.ungzip(data))
+    }
+}

+ 20 - 17
gramjs/tl/core/MessageContainer.js → gramjs/tl/core/MessageContainer.ts

@@ -1,8 +1,9 @@
-const TLMessage = require('./TLMessage')
+import {TLMessage} from './TLMessage';
+import {BinaryReader} from "../../extensions";
 
-class MessageContainer {
+export class MessageContainer {
     static CONSTRUCTOR_ID = 0x73f1f8dc;
-    static classType = 'constructor'
+    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.
@@ -17,29 +18,31 @@ class MessageContainer {
     // is a reasonable conservative value, since it could also depend on
     // other factors like size per request, but we cannot know this.
     static MAXIMUM_LENGTH = 100;
+    private CONSTRUCTOR_ID: number;
+    private messages: any[];
+    private classType: string;
 
-    constructor(messages) {
+    constructor(messages: any[]) {
 
-        this.CONSTRUCTOR_ID = 0x73f1f8dc
-        this.messages = messages
+        this.CONSTRUCTOR_ID = 0x73f1f8dc;
+        this.messages = messages;
         this.classType = 'constructor'
     }
 
-    static async fromReader(reader) {
-        const messages = []
-        const length = reader.readInt()
+    static async fromReader(reader: BinaryReader) {
+        const messages = [];
+        const length = reader.readInt();
         for (let x = 0; x < length; x++) {
-            const msgId = reader.readLong()
-            const seqNo = reader.readInt()
-            const length = reader.readInt()
-            const before = reader.tellPosition()
-            const obj = reader.tgReadObject()
-            reader.setPosition(before + length)
-            const tlMessage = new TLMessage(msgId, seqNo, obj)
+            const msgId = reader.readLong();
+            const seqNo = reader.readInt();
+            const length = reader.readInt();
+            const before = reader.tellPosition();
+            const obj = reader.tgReadObject();
+            reader.setPosition(before + length);
+            const tlMessage = new TLMessage(msgId, seqNo, obj);
             messages.push(tlMessage)
         }
         return new MessageContainer(messages)
     }
 }
 
-module.exports = MessageContainer

+ 0 - 33
gramjs/tl/core/RPCResult.js

@@ -1,33 +0,0 @@
-const { RpcError } = require('../index').constructors
-const GZIPPacked = require('./GZIPPacked')
-
-class RPCResult {
-    static CONSTRUCTOR_ID = 0xf35c6d01;
-    static classType = 'constructor'
-
-    constructor(reqMsgId, body, error) {
-        this.CONSTRUCTOR_ID = 0xf35c6d01
-        this.reqMsgId = reqMsgId
-        this.body = body
-        this.error = error
-        this.classType = 'constructor'
-    }
-
-    static async fromReader(reader) {
-        const msgId = reader.readLong()
-        const innerCode = reader.readInt(false)
-        if (innerCode === RpcError.CONSTRUCTOR_ID) {
-            return new RPCResult(msgId, null, RpcError.fromReader(reader))
-        }
-        if (innerCode === GZIPPacked.CONSTRUCTOR_ID) {
-            return new RPCResult(msgId, (await GZIPPacked.fromReader(reader)).data)
-        }
-        reader.seek(-4)
-        // This reader.read() will read more than necessary, but it's okay.
-        // We could make use of MessageContainer's length here, but since
-        // it's not necessary we don't need to care about it.
-        return new RPCResult(msgId, reader.read(), null)
-    }
-}
-
-module.exports = RPCResult

+ 39 - 0
gramjs/tl/core/RPCResult.ts

@@ -0,0 +1,39 @@
+import {Api} from '../api';
+import {BinaryReader} from "../../extensions";
+import {GZIPPacked} from "./index";
+
+
+
+export class RPCResult {
+    static CONSTRUCTOR_ID = 0xf35c6d01;
+    static classType = 'constructor';
+    private CONSTRUCTOR_ID: number;
+    private reqMsgId: bigInt.BigInteger;
+    private body?: Buffer;
+    private error?: Api.RpcError;
+    private classType: string;
+
+    constructor(reqMsgId: bigInt.BigInteger, body?: Buffer, error?: Api.RpcError) {
+        this.CONSTRUCTOR_ID = 0xf35c6d01;
+        this.reqMsgId = reqMsgId;
+        this.body = body;
+        this.error = error;
+        this.classType = 'constructor'
+    }
+
+    static async fromReader(reader: BinaryReader) {
+        const msgId = reader.readLong();
+        const innerCode = reader.readInt(false);
+        if (innerCode === Api.RpcError.CONSTRUCTOR_ID) {
+            return new RPCResult(msgId, undefined, Api.RpcError.fromReader(reader) as Api.RpcError)
+        }
+        if (innerCode === GZIPPacked.CONSTRUCTOR_ID) {
+            return new RPCResult(msgId, (await GZIPPacked.fromReader(reader)).data)
+        }
+        reader.seek(-4);
+        // This reader.read() will read more than necessary, but it's okay.
+        // We could make use of MessageContainer's length here, but since
+        // it's not necessary we don't need to care about it.
+        return new RPCResult(msgId, reader.read(), undefined)
+    }
+}

+ 0 - 14
gramjs/tl/core/TLMessage.js

@@ -1,14 +0,0 @@
-
-class TLMessage {
-    static SIZE_OVERHEAD = 12;
-    static classType = 'constructor'
-
-    constructor(msgId, seqNo, obj) {
-        this.msgId = msgId
-        this.seqNo = seqNo
-        this.obj = obj
-        this.classType = 'constructor'
-    }
-}
-
-module.exports = TLMessage

Some files were not shown because too many files changed in this diff