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

Merge branch 'master' of https://github.com/gram-js/gramjs

YouTwitFace 5 жил өмнө
parent
commit
f4d9d806e2
52 өөрчлөгдсөн 3862 нэмэгдсэн , 1840 устгасан
  1. 3 4
      .gitignore
  2. 0 4
      api/settings
  3. 0 32
      crypto/AuthKey.js
  4. 0 251
      crypto/RSA.js
  5. 0 261
      errors.js
  6. 18 19
      gramjs/crypto/AES.js
  7. 62 0
      gramjs/crypto/AuthKey.js
  8. 0 0
      gramjs/crypto/Factorizator.js
  9. 102 0
      gramjs/crypto/RSA.js
  10. 152 0
      gramjs/errors/Common.js
  11. 119 0
      gramjs/errors/RPCBaseErrors.js
  12. 21 0
      gramjs/errors/index.js
  13. 270 0
      gramjs/extensions/BinaryReader.js
  14. 16 0
      gramjs/extensions/BinaryWriter.js
  15. 88 0
      gramjs/extensions/MessagePacker.js
  16. 226 0
      gramjs/network/Authenticator.js
  17. 94 0
      gramjs/network/MTProtoPlainSender.js
  18. 721 0
      gramjs/network/MTProtoSender.js
  19. 270 0
      gramjs/network/MTProtoState.js
  20. 21 0
      gramjs/network/RequestState.js
  21. 160 0
      gramjs/network/connection/Connection.js
  22. 62 0
      gramjs/network/connection/TCPFull.js
  23. 0 0
      gramjs/network/connection/index.js
  24. 0 0
      gramjs/network/index.js
  25. 5 5
      gramjs/tl/MTProtoRequest.js
  26. 102 0
      gramjs/tl/Session.js
  27. 122 0
      gramjs/tl/TelegramClient.js
  28. 46 0
      gramjs/tl/core/GZIPPacked.js
  29. 47 0
      gramjs/tl/core/MessageContainer.js
  30. 38 0
      gramjs/tl/core/RPCResult.js
  31. 14 0
      gramjs/tl/core/TLMessage.js
  32. 18 0
      gramjs/tl/core/index.js
  33. 8 0
      gramjs/tl/index.js
  34. 91 0
      gramjs/tl/tlobject.js
  35. 385 0
      gramjs/utils/Helpers.js
  36. 14 2
      gramjs_generator/generators/errors.js
  37. 3 3
      gramjs_generator/generators/index.js
  38. 300 97
      gramjs_generator/generators/tlobject.js
  39. 1 1
      gramjs_generator/sourcebuilder.js
  40. 7 0
      gramjs_generator/utils.js
  41. 4 4
      index.js
  42. 54 6
      main.js
  43. 0 250
      network/Authenticator.js
  44. 0 58
      network/MTProtoPlainSender.js
  45. 0 317
      network/MTProtoSender.js
  46. 0 73
      network/TcpClient.js
  47. 0 84
      network/TcpTransport.js
  48. 187 0
      package-lock.json
  49. 11 0
      package.json
  50. 0 53
      tl/Session.js
  51. 0 119
      tl/TelegramClient.js
  52. 0 197
      utils/Helpers.js

+ 3 - 4
.gitignore

@@ -1,14 +1,13 @@
 /node_modules/
 /.vscode/
 /.idea/
-
 # Generated code
 /docs/
 /gramjs/tl/functions/
 /gramjs/tl/types/
 /gramjs/tl/patched/
-/gramjs/tl/alltlobjects.py
-/gramjs/errors/rpcerrorlist.py
+/gramjs/tl/alltlobjects.js
+/gramjs/errors/rpcerrorlist.js
 
 # User session
 *.session
@@ -20,4 +19,4 @@ example.js
 # dotenv
 .env
 
-/api/
+settings

+ 0 - 4
api/settings

@@ -1,4 +0,0 @@
-api_id=12345
-api_hash=0123456789abcdef0123456789abcdef
-user_phone=+34600000000
-session_name=anonymous

+ 0 - 32
crypto/AuthKey.js

@@ -1,32 +0,0 @@
-const Helpers = require("../utils/Helpers");
-
-class AuthKey {
-    constructor(data) {
-        this.data = data;
-        let offset = 0;
-        let buffer = Helpers.sha1(data);
-        this.auxHash = buffer.readBigUInt64LE(offset);
-        offset += 8 + 4;
-        this.keyId = buffer.readBigUInt64LE(offset);
-
-    }
-
-    /**
-     * Calculates the new nonce hash based on the current class fields' values
-     * @param new_nonce {Buffer}
-     * @param number {number}
-     * @returns {Buffer}
-     */
-    calcNewNonceHash(new_nonce, number) {
-
-        let tempBuffer = Buffer.alloc(1);
-        tempBuffer.writeInt8(number, 0);
-        let secondBuffer = Buffer.alloc(8);
-        secondBuffer.writeBigUInt64LE(this.auxHash, 0);
-        let buffer = Buffer.concat([new_nonce, tempBuffer, secondBuffer]);
-        return Helpers.calcMsgKey(buffer);
-    }
-
-}
-
-module.exports = AuthKey;

+ 0 - 251
crypto/RSA.js

@@ -1,251 +0,0 @@
-const Helpers = require("../utils/Helpers");
-
-
-class RSAServerKey {
-    constructor(fingerprint, m, e) {
-        this.fingerprint = fingerprint;
-        this.m = m;
-        this.e = e;
-
-    }
-
-    /**
-     * Encrypts the given data with the current key
-     * @param data
-     * @param offset
-     * @param length
-     */
-    encrypt(data, offset, length) {
-        if (offset === undefined) {
-            offset = 0;
-        }
-        if (length === undefined) {
-            length = data.length;
-        }
-        let dataToWrite = data.split(offset, offset + length);
-        let sha1Data = helpers.sha1(dataToWrite);
-        let writer = Buffer.concat([sha1Data, dataToWrite]);
-
-        if (length < 235) {
-            writer = Buffer.concat([writer, Helpers.generateRandomBytes(235 - length)]);
-
-        }
-        let result = writer.readIntBE(0, writer.byteLength);
-        result = (result ** this.e) % this.m;
-        let buffer = Buffer.alloc(256);
-        buffer.writeUIntBE(result, 0, 256);
-        return buffer;
-    }
-}
-
-
-class RSA {
-    static _server_keys = {
-        '216be86c022bb4c3': new RSAServerKey("216be86c022bb4c3", parseInt('C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9' +
-            '1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E' +
-            '580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F' +
-            '9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934' +
-            'EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F' +
-            '81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F' +
-            '6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1' +
-            '5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F', 16), parseInt('010001', 16)),
-// -4344800451088585951
-        '-4344800451088585951': new RSAServerKey(  // Telegram servers //1
-// -----BEGIN RSA PUBLIC KEY-----
-            // MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
-            // lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
-            // an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
-            // Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
-            // 8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
-            // Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
-// -----END RSA PUBLIC KEY-----
-            "-4344800451088585951",
-            parseInt(
-                "C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9" +
-                "1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E" +
-                "580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F" +
-                "9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934" +
-                "EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F" +
-                "81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F" +
-                "6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1" +
-                "5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F", +
-                    16
-            ),  // Modulus
-            parseInt("010001", 16)  // Exponent
-        ),
-
-        // 847625836280919973
-        '847625836280919973': new RSAServerKey(  // Telegram servers //2
-// -----BEGIN PUBLIC KEY-----
-            // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAruw2yP/BCcsJliRoW5eB
-            // VBVle9dtjJw+OYED160Wybum9SXtBBLXriwt4rROd9csv0t0OHCaTmRqBcQ0J8fx
-            // hN6/cpR1GWgOZRUAiQxoMnlt0R93LCX/j1dnVa/gVbCjdSxpbrfY2g2L4frzjJvd
-            // l84Kd9ORYjDEAyFnEA7dD556OptgLQQ2e2iVNq8NZLYTzLp5YpOdO1doK+ttrltg
-            // gTCy5SrKeLoCPPbOgGsdxJxyz5KKcZnSLj16yE5HvJQn0CNpRdENvRUXe6tBP78O
-            // 39oJ8BTHp9oIjd6XWXAsp2CvK45Ol8wFXGF710w9lwCGNbmNxNYhtIkdqfsEcwR5
-            // JwIDAQAB
-// -----END PUBLIC KEY-----
-            '847625836280919973',
-            parseInt(
-                "AEEC36C8FFC109CB099624685B97815415657BD76D8C9C3E398103D7AD16C9BB" +
-                "A6F525ED0412D7AE2C2DE2B44E77D72CBF4B7438709A4E646A05C43427C7F184" +
-                "DEBF72947519680E651500890C6832796DD11F772C25FF8F576755AFE055B0A3" +
-                "752C696EB7D8DA0D8BE1FAF38C9BDD97CE0A77D3916230C4032167100EDD0F9E" +
-                "7A3A9B602D04367B689536AF0D64B613CCBA7962939D3B57682BEB6DAE5B6081" +
-                "30B2E52ACA78BA023CF6CE806B1DC49C72CF928A7199D22E3D7AC84E47BC9427" +
-                "D0236945D10DBD15177BAB413FBF0EDFDA09F014C7A7DA088DDE9759702CA760" +
-                "AF2B8E4E97CC055C617BD74C3D97008635B98DC4D621B4891DA9FB0473047927",
-                16
-            ),  // Modulus
-            parseInt("010001", 16)  // Exponent
-        ),
-
-        // 1562291298945373506
-        '1562291298945373506': new RSAServerKey(  // Telegram servers //3
-// -----BEGIN PUBLIC KEY-----
-            // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvfLHfYH2r9R70w8prHbl
-            // Wt/nDkh+XkgpflqQVcnAfSuTtO05lNPspQmL8Y2XjVT4t8cT6xAkdgfmmvnvRPOO
-            // KPi0OfJXoRVylFzAQG/j83u5K3kRLbae7fLccVhKZhY46lvsueI1hQdLgNV9n1cQ
-            // 3TDS2pQOCtovG4eDl9wacrXOJTG2990VjgnIKNA0UMoP+KF03qzryqIt3oTvZq03
-            // DyWdGK+AZjgBLaDKSnC6qD2cFY81UryRWOab8zKkWAnhw2kFpcqhI0jdV5QaSCEx
-            // vnsjVaX0Y1N0870931/5Jb9ICe4nweZ9kSDF/gip3kWLG0o8XQpChDfyvsqB9OLV
-// /wIDAQAB
-// -----END PUBLIC KEY-----
-            '1562291298945373506',
-            parseInt(
-                "BDF2C77D81F6AFD47BD30F29AC76E55ADFE70E487E5E48297E5A9055C9C07D2B" +
-                "93B4ED3994D3ECA5098BF18D978D54F8B7C713EB10247607E69AF9EF44F38E28" +
-                "F8B439F257A11572945CC0406FE3F37BB92B79112DB69EEDF2DC71584A661638" +
-                "EA5BECB9E23585074B80D57D9F5710DD30D2DA940E0ADA2F1B878397DC1A72B5" +
-                "CE2531B6F7DD158E09C828D03450CA0FF8A174DEACEBCAA22DDE84EF66AD370F" +
-                "259D18AF806638012DA0CA4A70BAA83D9C158F3552BC9158E69BF332A45809E1" +
-                "C36905A5CAA12348DD57941A482131BE7B2355A5F4635374F3BD3DDF5FF925BF" +
-                "4809EE27C1E67D9120C5FE08A9DE458B1B4A3C5D0A428437F2BECA81F4E2D5FF",
-                16
-            ),  // Modulus
-            parseInt("010001", 16)  // Exponent
-        ),
-
-// -5859577972006586033
-        '-5859577972006586033': new RSAServerKey(  // Telegram servers //4
-// -----BEGIN PUBLIC KEY-----
-            // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs/ditzm+mPND6xkhzwFI
-            // z6J/968CtkcSE/7Z2qAJiXbmZ3UDJPGrzqTDHkO30R8VeRM/Kz2f4nR05GIFiITl
-            // 4bEjvpy7xqRDspJcCFIOcyXm8abVDhF+th6knSU0yLtNKuQVP6voMrnt9MV1X92L
-            // GZQLgdHZbPQz0Z5qIpaKhdyA8DEvWWvSUwwc+yi1/gGaybwlzZwqXYoPOhwMebzK
-            // Uk0xW14htcJrRrq+PXXQbRzTMynseCoPIoke0dtCodbA3qQxQovE16q9zz4Otv2k
-            // 4j63cz53J+mhkVWAeWxVGI0lltJmWtEYK6er8VqqWot3nqmWMXogrgRLggv/Nbbo
-            // oQIDAQAB
-// -----END PUBLIC KEY-----
-            '-5859577972006586033',
-            parseInt(
-                "B3F762B739BE98F343EB1921CF0148CFA27FF7AF02B6471213FED9DAA0098976" +
-                "E667750324F1ABCEA4C31E43B7D11F1579133F2B3D9FE27474E462058884E5E1" +
-                "B123BE9CBBC6A443B2925C08520E7325E6F1A6D50E117EB61EA49D2534C8BB4D" +
-                "2AE4153FABE832B9EDF4C5755FDD8B19940B81D1D96CF433D19E6A22968A85DC" +
-                "80F0312F596BD2530C1CFB28B5FE019AC9BC25CD9C2A5D8A0F3A1C0C79BCCA52" +
-                "4D315B5E21B5C26B46BABE3D75D06D1CD33329EC782A0F22891ED1DB42A1D6C0" +
-                "DEA431428BC4D7AABDCF3E0EB6FDA4E23EB7733E7727E9A1915580796C55188D" +
-                "2596D2665AD1182BA7ABF15AAA5A8B779EA996317A20AE044B820BFF35B6E8A1",
-                16
-            ),  // Modulus
-            parseInt("010001", 16)  // Exponent
-        ),
-
-        // 6491968696586960280
-        '6491968696586960280': new RSAServerKey(  // Telegram servers //5
-// -----BEGIN PUBLIC KEY-----
-            // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmpxVY7ld/8DAjz6F6q0
-            // 5shjg8/4p6047bn6/m8yPy1RBsvIyvuDuGnP/RzPEhzXQ9UJ5Ynmh2XJZgHoE9xb
-            // nfxL5BXHplJhMtADXKM9bWB11PU1Eioc3+AXBB8QiNFBn2XI5UkO5hPhbb9mJpjA
-            // 9Uhw8EdfqJP8QetVsI/xrCEbwEXe0xvifRLJbY08/Gp66KpQvy7g8w7VB8wlgePe
-            // xW3pT13Ap6vuC+mQuJPyiHvSxjEKHgqePji9NP3tJUFQjcECqcm0yV7/2d0t/pbC
-            // m+ZH1sadZspQCEPPrtbkQBlvHb4OLiIWPGHKSMeRFvp3IWcmdJqXahxLCUS1Eh6M
-            // AQIDAQAB
-// -----END PUBLIC KEY-----
-            '6491968696586960280',
-            parseInt(
-                "BE6A71558EE577FF03023CFA17AAB4E6C86383CFF8A7AD38EDB9FAFE6F323F2D" +
-                "5106CBC8CAFB83B869CFFD1CCF121CD743D509E589E68765C96601E813DC5B9D" +
-                "FC4BE415C7A6526132D0035CA33D6D6075D4F535122A1CDFE017041F1088D141" +
-                "9F65C8E5490EE613E16DBF662698C0F54870F0475FA893FC41EB55B08FF1AC21" +
-                "1BC045DED31BE27D12C96D8D3CFC6A7AE8AA50BF2EE0F30ED507CC2581E3DEC5" +
-                "6DE94F5DC0A7ABEE0BE990B893F2887BD2C6310A1E0A9E3E38BD34FDED254150" +
-                "8DC102A9C9B4C95EFFD9DD2DFE96C29BE647D6C69D66CA500843CFAED6E44019" +
-                "6F1DBE0E2E22163C61CA48C79116FA77216726749A976A1C4B0944B5121E8C01",
-                16
-            ),  // Modulus
-            parseInt("010001", 16)  // Exponent
-        ),
-
-        // 6427105915145367799
-        '6427105915145367799': new RSAServerKey(  // CDN DC-121
-// -----BEGIN RSA PUBLIC KEY-----
-            // MIIBCgKCAQEA+Lf3PvgE1yxbJUCMaEAkV0QySTVpnaDjiednB5RbtNWjCeqSVakY
-            // HbqqGMIIv5WCGdFdrqOfMNcNSstPtSU6R9UmRw6tquOIykpSuUOje9H+4XVIKquj
-            // yL2ISdK+4ZOMl4hCMkqauw4bP1Sbr03vZRQbU6qEA04V4j879BAyBVhr3WG9+Zi+
-            // t5XfGSTgSExPYEl8rZNHYNV5RB+BuroVH2HLTOpT/mJVfikYpgjfWF5ldezV4Wo9
-            // LSH0cZGSFIaeJl8d0A8Eiy5B9gtBO8mL+XfQRKOOmr7a4BM4Ro2de5rr2i2od7hY
-            // Xd3DO9FRSl4y1zA8Am48Rfd95WHF3N/OmQIDAQAB
-// -----END RSA PUBLIC KEY-----
-            '6427105915145367799',
-            parseInt(
-                "F8B7F73EF804D72C5B25408C6840245744324935699DA0E389E76707945BB4D5" +
-                "A309EA9255A9181DBAAA18C208BF958219D15DAEA39F30D70D4ACB4FB5253A47" +
-                "D526470EADAAE388CA4A52B943A37BD1FEE175482AABA3C8BD8849D2BEE1938C" +
-                "978842324A9ABB0E1B3F549BAF4DEF65141B53AA84034E15E23F3BF410320558" +
-                "6BDD61BDF998BEB795DF1924E0484C4F60497CAD934760D579441F81BABA151F" +
-                "61CB4CEA53FE62557E2918A608DF585E6575ECD5E16A3D2D21F471919214869E" +
-                "265F1DD00F048B2E41F60B413BC98BF977D044A38E9ABEDAE01338468D9D7B9A" +
-                "EBDA2DA877B8585DDDC33BD1514A5E32D7303C026E3C45F77DE561C5DCDFCE99",
-                16
-            ),  // Modulus
-            parseInt("010001", 16)  // Exponent
-        ),
-
-        // 2685959930972952888
-        '2685959930972952888': new RSAServerKey(  // CDN DC-140
-// -----BEGIN RSA PUBLIC KEY-----
-            // MIIBCgKCAQEAzuHVC7sE50Kho/yDVZtWnlmA5Bf/aM8KZY3WzS16w6w1sBqipj8o
-            // gMGG7ULbGBtYmKEaI7IIJO6WM2m1MaXVnsqS8d7PaGAZiy8rSN3S7S2a8wp4RXZe
-            // hs0JAXvZeIz45iByCMBfycbJKmSweYkesRUI7hUO8eQhmm/UYUEpJY7VOt0Iemiu
-            // URSpqlRQ2FlcyHahYUNcvbICb4+/AP7coKBn6cB5FyzM7MCcKxbEKOx3Y3MUnbZq
-            // q5pN6/eRazkegyrlp4kuJ94KsbRFHFX5Dx8uzjrO9wi8LF7gIgZu5DRMcmjXJKq6
-            // rGZ2Z9cnrD8pVu1L2vcInd4K6ximZS2hbwIDAQAB
-// -----END RSA PUBLIC KEY-----
-            '2685959930972952888',
-            parseInt(
-                "CEE1D50BBB04E742A1A3FC83559B569E5980E417FF68CF0A658DD6CD2D7AC3AC" +
-                "35B01AA2A63F2880C186ED42DB181B5898A11A23B20824EE963369B531A5D59E" +
-                "CA92F1DECF6860198B2F2B48DDD2ED2D9AF30A7845765E86CD09017BD9788CF8" +
-                "E6207208C05FC9C6C92A64B079891EB11508EE150EF1E4219A6FD4614129258E" +
-                "D53ADD087A68AE5114A9AA5450D8595CC876A161435CBDB2026F8FBF00FEDCA0" +
-                "A067E9C079172CCCECC09C2B16C428EC776373149DB66AAB9A4DEBF7916B391E" +
-                "832AE5A7892E27DE0AB1B4451C55F90F1F2ECE3ACEF708BC2C5EE022066EE434" +
-                "4C7268D724AABAAC667667D727AC3F2956ED4BDAF7089DDE0AEB18A6652DA16F",
-                16
-            ),  // Modulus
-            parseInt("010001", 16)  // Exponent
-        )
-
-    };
-
-    /**
-     * Encrypts the given data given a fingerprint
-     * @param fingerprint
-     * @param data
-     * @param offset
-     * @param length
-     */
-    static encrypt(fingerprint, data, offset, length) {
-        if (!(fingerprint.toLowerCase() in RSA._server_keys)) {
-            return;
-        }
-        let key = RSA._server_keys[fingerprint.toLowerCase()];
-        return key.encrypt(data, offset, length);
-
-    }
-}
-
-
-module.exports = RSA;

+ 0 - 261
errors.js

@@ -1,261 +0,0 @@
-/**
- * Occurs when a read operation was cancelled
- */
-class ReadCancelledError extends Error {
-    constructor() {
-        super("You must run `python3 tl_generator.py` first. #ReadTheDocs!")
-    }
-}
-
-/**
- * Occurs when you should've ran `tl_generator.py`, but you haven't
- */
-class TLGeneratorNotRan extends Error {
-    constructor() {
-        super("You must run `python3 tl_generator.py` first. #ReadTheDocs!")
-    }
-}
-
-/**
- * Occurs when an invalid parameter is given, for example,
- * when either A or B are required but none is given
- */
-class InvalidParameterError 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) {
-        super('Could not find a matching Constructor ID for the TLObject ' +
-            'that was supposed to be read with ID {}. Most likely, a TLObject ' +
-            'was trying to be read when it should not be read.'.replace("{}",
-                invalidConstructorId.toString("16")));
-        this.invalidConstructorId = invalidConstructorId;
-    }
-}
-
-/**
- * Occurs when a read operation was cancelled
- */
-class InvalidDCError extends Error {
-    constructor(newDC) {
-        super('Your phone number is registered to #{} DC. ' +
-            'This should have been handled automatically; ' +
-            'if it has not, please restart the app.'.replace("{}", newDC));
-        this.newDC = newDC;
-    }
-}
-
-/**
- * Occurs when an invalid checksum is passed
- */
-class InvalidChecksumError extends Error {
-    constructor(checksum, validChecksum) {
-        super('Invalid checksum ({0} when {1} was expected). This packet should be skipped.'
-            .replace("{0}", checksum).replace("{1}", validChecksum));
-        this.checksum = checksum;
-        this.validChecksum = validChecksum;
-
-    }
-}
-
-class RPCError extends Error {
-    static CodeMessages = {
-        303: Array('ERROR_SEE_OTHER', 'The request must be repeated, but directed to a different data center.'),
-
-        400: Array('BAD_REQUEST', 'The query contains errors. In the event that a request was created using a ' +
-            'form and contains user generated data, the user should be notified that the ' +
-            'data must be corrected before the query is repeated.'),
-
-        401: Array('UNAUTHORIZED', 'There was an unauthorized attempt to use functionality available only to ' +
-            'authorized users.'),
-
-        403: Array('FORBIDDEN', 'Privacy violation. For example, an attempt to write a message to someone who ' +
-            'has blacklisted the current user.'),
-
-        404: Array('NOT_FOUND', 'An attempt to invoke a non-existent object, such as a method.'),
-
-        420: Array('FLOOD', 'The maximum allowed number of attempts to invoke the given method with ' +
-            'the given input parameters has been exceeded. For example, in an attempt ' +
-            'to request a large number of text messages (SMS) for the same phone number.'),
-
-        500: Array('INTERNAL', 'An internal server error occurred while a request was being processed; ' +
-            'for example, there was a disruption while accessing a database or file storage.')
-    };
-    static ErrorMessages = {
-        // 303 ERROR_SEE_OTHER
-        'FILE_MIGRATE_(\\d+)': 'The file to be accessed is currently stored in a different data center (#{}).',
-
-        'PHONE_MIGRATE_(\\d+)': 'The phone number a user is trying to use for authorization is associated ' +
-            'with a different data center (#{}).',
-
-        'NETWORK_MIGRATE_(\\d+)': 'The source IP address is associated with a different data center (#{}, ' +
-            'for registration).',
-
-        'USER_MIGRATE_(\\d+)': 'The user whose identity is being used to execute queries is associated with ' +
-            'a different data center  (#{} for registration).',
-
-        // 400 BAD_REQUEST
-        'FIRSTNAME_INVALID': 'The first name is invalid.',
-
-        'LASTNAME_INVALID': 'The last name is invalid.',
-
-        'PHONE_NUMBER_INVALID': 'The phone number is invalid.',
-
-        'PHONE_CODE_HASH_EMPTY': 'phone_code_hash is missing.',
-
-        'PHONE_CODE_EMPTY': 'phone_code is missing.',
-
-        'PHONE_CODE_EXPIRED': 'The confirmation code has expired.',
-
-        'API_ID_INVALID': 'The api_id/api_hash combination is invalid.',
-
-        'PHONE_NUMBER_OCCUPIED': 'The phone number is already in use.',
-
-        'PHONE_NUMBER_UNOCCUPIED': 'The phone number is not yet being used.',
-
-        'USERS_TOO_FEW': 'Not enough users (to create a chat, for example).',
-
-        'USERS_TOO_MUCH': 'The maximum number of users has been exceeded (to create a chat, for example).',
-
-        'TYPE_CONSTRUCTOR_INVALID': 'The type constructor is invalid.',
-
-        'FILE_PART_INVALID': 'The file part number is invalid.',
-
-        'FILE_PARTS_INVALID': 'The number of file parts is invalid.',
-
-        'FILE_PART_(\\d+)_MISSING': 'Part {} of the file is missing from storage.',
-
-        'MD5_CHECKSUM_INVALID': 'The MD5 checksums do not match.',
-
-        'PHOTO_INVALID_DIMENSIONS': 'The photo dimensions are invalid.',
-
-        'FIELD_NAME_INVALID': 'The field with the name FIELD_NAME is invalid.',
-
-        'FIELD_NAME_EMPTY': 'The field with the name FIELD_NAME is missing.',
-
-        'MSG_WAIT_FAILED': 'A waiting call returned an error.',
-
-        'CHAT_ADMIN_REQUIRED': 'Chat admin privileges are required to do that in the specified chat ' +
-            '(for example, to send a message in a channel which is not yours).',
-
-        // 401 UNAUTHORIZED
-        'AUTH_KEY_UNREGISTERED': 'The key is not registered in the system.',
-
-        'AUTH_KEY_INVALID': 'The key is invalid.',
-
-        'USER_DEACTIVATED': 'The user has been deleted/deactivated.',
-
-        'SESSION_REVOKED': 'The authorization has been invalidated, because of the user terminating all sessions.',
-
-        'SESSION_EXPIRED': 'The authorization has expired.',
-
-        'ACTIVE_USER_REQUIRED': 'The method is only available to already activated users.',
-
-        'AUTH_KEY_PERM_EMPTY': 'The method is unavailable for temporary authorization key, not bound to permanent.',
-
-        // 420 FLOOD
-        'FLOOD_WAIT_(\\d+)': 'A wait of {} seconds is required.'
-    };
-
-    constructor(code, message) {
-        let codeMeaning = RPCError.CodeMessages[code];
-        let mustResend = code === 303; // ERROR_SEE_OTHER, "The request must be repeated"
-
-        let calledSuper = false;
-        for (let item in RPCError.ErrorMessages) {
-
-            let key = new RegExp(item);
-            let errorMsg = RPCError.ErrorMessages[item];
-
-            let match = message.match(key);
-
-            if (match) {
-                console.log(match[1]);
-                // Get additionalData if any
-                if (match.length === 2) {
-                    console.log(errorMsg);
-                    let additionalData = parseInt(match[1]);
-                    super(errorMsg.replace("{}", additionalData));
-
-                    this.additionalData = additionalData;
-                } else {
-                    super(errorMsg);
-                }
-                calledSuper = true;
-                break;
-            }
-
-        }
-        if (!calledSuper) {
-            super("Unknown error message with code {0}: {1}"
-                .replace("{0}", code).replace("{1}", message))
-        }
-        this.code = code;
-        this.errorMessage = message;
-        this.codeMeaning = codeMeaning;
-        this.mustResend = mustResend;
-
-    }
-}
-
-/**
- * Occurs when handling a badMessageNotification
- */
-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 ' +
-            'with the “correct” msg_id or wrap it in a container with a new msg_id if the ' +
-            'original message had waited too long on the client to be transmitted).',
-
-        17: 'msg_id too high (similar to the previous case, the client time has to be ' +
-            'synchronized, and the message re-sent with the correct msg_id).',
-
-        18: 'Incorrect two lower order msg_id bits (the server expects client message msg_id ' +
-            'to be divisible by 4).',
-
-        19: 'Container msg_id is the same as msg_id of a previously received message ' +
-            '(this must never happen).',
-
-        20: 'Message too old, and it cannot be verified whether the server has received a ' +
-            'message with this msg_id or not.',
-
-        32: 'msg_seqno too low (the server has already received a message with a lower ' +
-            'msg_id but with either a higher or an equal and odd seqno).',
-
-        33: 'msg_seqno too high (similarly, there is a message with a higher msg_id but with ' +
-            'either a lower or an equal and odd seqno).',
-
-        34: 'An even msg_seqno expected (irrelevant message), but odd received.',
-
-        35: 'Odd msg_seqno expected (relevant message), but even received.',
-
-        48: 'Incorrect server salt (in this case, the bad_server_salt response is received with ' +
-            'the correct salt, and the message is to be re-sent with it).',
-
-        64: 'Invalid container.'
-    };
-
-    constructor(code) {
-        super(BadMessageError.ErrorMessages[code] || "Unknown error code (this should not happen): {}."
-            .replace("{}", code));
-    }
-
-}
-
-
-module.exports = {
-    ReadCancelledError,
-    TLGeneratorNotRan,
-    InvalidParameterError,
-    TypeNotFoundError,
-    InvalidDCError,
-    InvalidChecksumError,
-    RPCError,
-    BadMessageError
-};

+ 18 - 19
crypto/AES.js → gramjs/crypto/AES.js

@@ -1,5 +1,5 @@
 const aesjs = require('aes-js');
-
+const Helpers = require("../utils/Helpers");
 
 class AES {
     /**
@@ -46,40 +46,39 @@ class AES {
      * @returns {Buffer}
      */
     static encryptIge(plainText, key, iv) {
-        if (plainText.length % 16 !== 0) {
-            let padding = new Uint8Array(16 - plainText.length % 16);
-            plainText = new Uint8Array([
-                ...plainText,
-                ...padding,
-            ]);
+        let padding = plainText.length % 16;
+        if (padding) {
+            plainText = Buffer.concat([plainText, Helpers.generateRandomBytes(16 - padding)]);
         }
+
         let iv1 = iv.slice(0, Math.floor(iv.length / 2));
         let iv2 = iv.slice(Math.floor(iv.length / 2));
+
         let aes = new aesjs.AES(key);
-        let blocksCount = Math.floor(plainText.length / 16);
-        let cipherText = new Array(plainText.length).fill(0);
-        for (let blockIndex = 0; blockIndex < blocksCount; blockIndex++) {
-            let plainTextBlock = plainText.slice(blockIndex * 16, blockIndex * 16 + 16);
+        let cipherText = Buffer.alloc(0);
+        let blockCount = Math.floor(plainText.length / 16);
+
+        for (let blockIndex = 0; blockIndex < blockCount; blockIndex++) {
+            let plainTextBlock = Buffer.from(plainText.slice(blockIndex * 16, blockIndex * 16 + 16));
 
             for (let i = 0; i < 16; i++) {
                 plainTextBlock[i] ^= iv1[i];
             }
-            let cipherTextBlock = aes.encrypt(plainTextBlock);
+            let cipherTextBlock = Buffer.from(aes.encrypt(plainTextBlock));
+
             for (let i = 0; i < 16; i++) {
                 cipherTextBlock[i] ^= iv2[i];
             }
 
-            iv1 = cipherTextBlock.slice(0, 16);
+            iv1 = cipherTextBlock;
             iv2 = plainText.slice(blockIndex * 16, blockIndex * 16 + 16);
-            cipherText = new Uint8Array([
-                ...cipherText.slice(0, blockIndex * 16),
-                ...cipherTextBlock.slice(0, 16),
-                ...cipherText.slice(blockIndex * 16 + 16)
+            cipherText = Buffer.concat([
+                cipherText,
+                cipherTextBlock,
             ]);
         }
-        return Buffer.from(cipherText);
+        return cipherText;
     }
-
 }
 
 module.exports = AES;

+ 62 - 0
gramjs/crypto/AuthKey.js

@@ -0,0 +1,62 @@
+const Helpers = require("../utils/Helpers");
+const BinaryReader = require("../extensions/BinaryReader");
+const struct = require("python-struct");
+const bigUintLE = require("biguintle");
+
+class AuthKey {
+    constructor(data) {
+        this.key = data;
+
+    }
+
+    set key(value) {
+        if (!value) {
+            this._key = this.auxHash = this.keyId = null;
+            return
+        }
+        if (value instanceof AuthKey) {
+            this._key = value._key;
+            this.auxHash = value.auxHash;
+            this.keyId = value.keyId;
+            return
+        }
+        this._key = value;
+        let reader = new BinaryReader(Helpers.sha1(this._key));
+        this.auxHash = reader.readLong(false);
+        reader.read(4);
+        this.keyId = reader.readLong(false);
+    }
+
+    get key() {
+        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 new_nonce
+     * @param number
+     * @returns {bigint}
+     */
+    calcNewNonceHash(new_nonce, number) {
+
+        new_nonce = Helpers.readBufferFromBigInt(new_nonce, 32, true, true);
+        let data = Buffer.concat([
+            new_nonce,
+            struct.pack("<BQ", number.toString(), this.auxHash.toString())
+        ]);
+
+        //Calculates the message key from the given data
+        let shaData = Helpers.sha1(data).slice(4, 20);
+        return Helpers.readBigIntFromBuffer(shaData, true, true);
+    }
+
+    equals(other) {
+        return (other instanceof this.constructor && other.key === this._key)
+    }
+
+}
+
+module.exports = AuthKey;

+ 0 - 0
crypto/Factorizator.js → gramjs/crypto/Factorizator.js


+ 102 - 0
gramjs/crypto/RSA.js

@@ -0,0 +1,102 @@
+const NodeRSA = require('node-rsa');
+const {TLObject} = require("../tl/tlobject");
+const struct = require("python-struct");
+const Helpers = require("../utils/Helpers");
+let _serverKeys = {};
+
+/**
+ * Gets the arbitrary-length byte array corresponding to the given integer
+ * @param integer {number,BigInt}
+ * @param signed {boolean}
+ * @returns {Buffer}
+ */
+function getByteArray(integer, signed = false) {
+
+    let bits = integer.toString(2).length;
+    let byteLength = Math.floor((bits + 8 - 1) / 8);
+    let f;
+    f = Helpers.readBufferFromBigInt(BigInt(integer), byteLength, false, signed);
+    return f;
+}
+
+function _computeFingerprint(key) {
+    let buf = Helpers.readBigIntFromBuffer(key.keyPair.n.toBuffer(), false);
+    let nArray = getByteArray(buf);
+
+    let n = TLObject.serializeBytes(nArray);
+    let e = TLObject.serializeBytes(getByteArray(key.keyPair.e));
+//Telegram uses the last 8 bytes as the fingerprint
+    let sh = Helpers.sha1(Buffer.concat([n, e]));
+    return Helpers.readBigIntFromBuffer(sh.slice(-8), true, true);
+}
+
+function addKey(pub) {
+    let key = new NodeRSA(pub);
+    _serverKeys[_computeFingerprint(key)] = key;
+
+}
+
+
+function encrypt(fingerprint, data) {
+    let key = _serverKeys[fingerprint];
+    if (!key) {
+        return undefined;
+    }
+    let buf = Helpers.readBigIntFromBuffer(key.keyPair.n.toBuffer(), false);
+    let rand = Helpers.generateRandomBytes(235 - data.length);
+    rand = Buffer.from(
+        "66a6f809e0dfd71d9dbbc2d6b5fe5fc0be9f5b2b0f2f85688843eea6b2c6d51329750f020c8de27a0a911b07d2a46600493d1abb7caf24" +
+        "01ccd815d7de7c5ea830cdf6cce8bff12f77db589f233bce436b644c3415f16d073335fdadfe313c603485b3274e8fcd148fd1a5e18bd2" +
+        "4b3e983df94d58b61c150333ab8d614101e7a904dc38af3a3b29e73d62", "hex");
+
+    let toEncrypt = Buffer.concat([Helpers.sha1(data), data, rand]);
+    let payload = Helpers.readBigIntFromBuffer(toEncrypt, false);
+    let encrypted = Helpers.modExp(payload, BigInt(key.keyPair.e),
+        buf);
+    let block = Helpers.readBufferFromBigInt(encrypted, 256, false);
+    return block;
+}
+
+let publicKeys = [
+    `-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
+lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
+an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
+Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
+8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
+Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
+-----END RSA PUBLIC KEY-----`,
+
+    `-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEAxq7aeLAqJR20tkQQMfRn+ocfrtMlJsQ2Uksfs7Xcoo77jAid0bRt
+ksiVmT2HEIJUlRxfABoPBV8wY9zRTUMaMA654pUX41mhyVN+XoerGxFvrs9dF1Ru
+vCHbI02dM2ppPvyytvvMoefRoL5BTcpAihFgm5xCaakgsJ/tH5oVl74CdhQw8J5L
+xI/K++KJBUyZ26Uba1632cOiq05JBUW0Z2vWIOk4BLysk7+U9z+SxynKiZR3/xdi
+XvFKk01R3BHV+GUKM2RYazpS/P8v7eyKhAbKxOdRcFpHLlVwfjyM1VlDQrEZxsMp
+NTLYXb6Sce1Uov0YtNx5wEowlREH1WOTlwIDAQAB
+-----END RSA PUBLIC KEY-----`,
+
+    `-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEAsQZnSWVZNfClk29RcDTJQ76n8zZaiTGuUsi8sUhW8AS4PSbPKDm+
+DyJgdHDWdIF3HBzl7DHeFrILuqTs0vfS7Pa2NW8nUBwiaYQmPtwEa4n7bTmBVGsB
+1700/tz8wQWOLUlL2nMv+BPlDhxq4kmJCyJfgrIrHlX8sGPcPA4Y6Rwo0MSqYn3s
+g1Pu5gOKlaT9HKmE6wn5Sut6IiBjWozrRQ6n5h2RXNtO7O2qCDqjgB2vBxhV7B+z
+hRbLbCmW0tYMDsvPpX5M8fsO05svN+lKtCAuz1leFns8piZpptpSCFn7bWxiA9/f
+x5x17D7pfah3Sy2pA+NDXyzSlGcKdaUmwQIDAQAB
+-----END RSA PUBLIC KEY-----`,
+
+    `-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEAwqjFW0pi4reKGbkc9pK83Eunwj/k0G8ZTioMMPbZmW99GivMibwa
+xDM9RDWabEMyUtGoQC2ZcDeLWRK3W8jMP6dnEKAlvLkDLfC4fXYHzFO5KHEqF06i
+qAqBdmI1iBGdQv/OQCBcbXIWCGDY2AsiqLhlGQfPOI7/vvKc188rTriocgUtoTUc
+/n/sIUzkgwTqRyvWYynWARWzQg0I9olLBBC2q5RQJJlnYXZwyTL3y9tdb7zOHkks
+WV9IMQmZmyZh/N7sMbGWQpt4NMchGpPGeJ2e5gHBjDnlIf2p1yZOYeUYrdbwcS0t
+UiggS4UeE8TzIuXFQxw7fzEIlmhIaq3FnwIDAQAB
+-----END RSA PUBLIC KEY-----`
+];
+for (let pub of publicKeys) {
+    addKey(pub);
+}
+
+
+module.exports = {encrypt};

+ 152 - 0
gramjs/errors/Common.js

@@ -0,0 +1,152 @@
+/**
+ * Errors not related to the Telegram API itself
+ */
+
+const {TLRequest} = require("../tl");
+const struct = require("python-struct");
+
+/**
+ * Occurs when a read operation was cancelled.
+ */
+class ReadCancelledError extends Error {
+    constructor() {
+        super("The read operation was cancelled.");
+    }
+}
+
+/**
+ * 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) {
+        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}`);
+        this.invalidConstructorId = invalidConstructorId;
+        this.remaining = remaining;
+    }
+}
+
+/**
+ * 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;
+    }
+}
+
+/**
+ * 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 = (-struct.unpack("<i", payload)[0]);
+        if ((payload.length === 4)) {
+            super(`Invalid response buffer (HTTP code ${code})`);
+        } else {
+            this.code = null;
+            super(`Invalid response buffer (too short ${payload})`);
+        }
+        this.code = code;
+        this.payload = payload;
+
+    }
+}
+
+
+/**
+ * Generic security error, mostly used when generating a new AuthKey.
+ */
+class SecurityError extends Error {
+    constructor(...args) {
+        if (!args) {
+            args = ["A security check failed."];
+        }
+        super(...args);
+    }
+}
+
+/**
+ * Occurs when there's a hash mismatch between the decrypted CDN file
+ * and its expected hash.
+ */
+class CdnFileTamperedError extends SecurityError {
+    constructor() {
+        super("The CDN file has been altered and its download cancelled.");
+    }
+}
+
+/**
+ * Occurs when another exclusive conversation is opened in the same chat.
+ */
+class AlreadyInConversationError extends Error {
+    constructor() {
+        super("Cannot open exclusive conversation in a chat that already has one open conversation");
+    }
+}
+
+/**
+ * Occurs when handling a badMessageNotification
+ */
+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 ' +
+            'with the “correct” msg_id or wrap it in a container with a new msg_id if the ' +
+            'original message had waited too long on the client to be transmitted).',
+
+        17: 'msg_id too high (similar to the previous case, the client time has to be ' +
+            'synchronized, and the message re-sent with the correct msg_id).',
+
+        18: 'Incorrect two lower order msg_id bits (the server expects client message msg_id ' +
+            'to be divisible by 4).',
+
+        19: 'Container msg_id is the same as msg_id of a previously received message ' +
+            '(this must never happen).',
+
+        20: 'Message too old, and it cannot be verified whether the server has received a ' +
+            'message with this msg_id or not.',
+
+        32: 'msg_seqno too low (the server has already received a message with a lower ' +
+            'msg_id but with either a higher or an equal and odd seqno).',
+
+        33: 'msg_seqno too high (similarly, there is a message with a higher msg_id but with ' +
+            'either a lower or an equal and odd seqno).',
+
+        34: 'An even msg_seqno expected (irrelevant message), but odd received.',
+
+        35: 'Odd msg_seqno expected (relevant message), but even received.',
+
+        48: 'Incorrect server salt (in this case, the bad_server_salt response is received with ' +
+            'the correct salt, and the message is to be re-sent with it).',
+
+        64: 'Invalid container.'
+    };
+
+    constructor(code) {
+        super(BadMessageError.ErrorMessages[code] || `Unknown error code (this should not happen): ${code}.`);
+        this.code = code;
+
+    }
+
+}
+
+// TODO : Support multi errors.
+
+module.exports = {
+    ReadCancelledError,
+    TypeNotFoundError,
+    InvalidChecksumError,
+    InvalidBufferError,
+    SecurityError,
+    CdnFileTamperedError,
+    AlreadyInConversationError,
+    BadMessageError
+};

+ 119 - 0
gramjs/errors/RPCBaseErrors.js

@@ -0,0 +1,119 @@
+/**
+ * Base class for all Remote Procedure Call errors.
+ */
+class RPCError extends Error {
+
+
+    constructor(request, message, code = null) {
+        super("RPCError {0}: {1}{2}"
+            .replace("{0}", code)
+            .replace("{1}", message)
+            .replace("{2}", RPCError._fmtRequest(request)));
+        this.code = code;
+        this.message = message;
+    }
+
+    static _fmtRequest(request) {
+        // TODO fix this
+        return ` (caused by ${request})`
+    }
+}
+
+/**
+ * The request must be repeated, but directed to a different data center.
+ */
+class InvalidDCError extends RPCError {
+    code = 303;
+    message = 'ERROR_SEE_OTHER'
+}
+
+
+/**
+ * The query contains errors. In the event that a request was created
+ * 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;
+    message = 'BAD_REQUEST'
+}
+
+/**
+ * There was an unauthorized attempt to use functionality available only
+ * to authorized users.
+ */
+class UnauthorizedError extends RPCError {
+    code = 401;
+    message = 'UNAUTHORIZED'
+}
+
+/**
+ * Privacy violation. For example, an attempt to write a message to
+ * someone who has blacklisted the current user.
+ */
+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;
+    message = 'NOT_FOUND'
+}
+
+/**
+ * Errors related to invalid authorization key, like
+ * AUTH_KEY_DUPLICATED which can cause the connection to fail.
+ */
+class AuthKeyError extends RPCError {
+    code = 406;
+    message = 'AUTH_KEY'
+}
+
+/**
+ * The maximum allowed number of attempts to invoke the given method
+ * with the given input parameters has been exceeded. For example, in an
+ * attempt to request a large number of text messages (SMS) for the same
+ * phone number.
+ */
+class FloodError extends RPCError {
+    code = 420;
+    message = 'FLOOD'
+
+
+}
+
+/**
+ * An internal server error occurred while a request was being processed
+ * for example, there was a disruption while accessing a database or file
+ * storage
+ */
+class ServerError extends RPCError {
+    code = 500;  // Also witnessed as -500
+    message = 'INTERNAL'
+}
+
+/**
+ * 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
+    message = 'Timeout';
+}
+
+module.exports = {
+    RPCError,
+    InvalidDCError,
+    BadRequestError,
+    UnauthorizedError,
+    ForbiddenError,
+    NotFoundError,
+    AuthKeyError,
+    FloodError,
+    ServerError,
+    TimedOutError
+}

+ 21 - 0
gramjs/errors/index.js

@@ -0,0 +1,21 @@
+/**
+ * Converts a Telegram's RPC Error to a Python error.
+ * @param rpcError the RPCError instance
+ * @param request the request that caused this error
+ * @constructor the RPCError as a Python exception that represents this error
+ */
+const {rpcErrorsObject} = require("./rpcerrorlist");
+
+function RPCMessageToError(rpcError, request) {
+    //Try to get the error by direct look-up, otherwise regex
+    let cls = rpcErrorsObject[rpcError.errorMessage];
+    if (cls) {
+        return new cls(request);
+    } else {
+        return rpcError.errorMessage;
+    }
+}
+
+module.exports = {
+    RPCMessageToError
+}

+ 270 - 0
gramjs/extensions/BinaryReader.js

@@ -0,0 +1,270 @@
+const unpack = require("python-struct").unpack;
+const {TypeNotFoundError} = require("../errors/Common");
+const {coreObjects} = require("../tl/core");
+const {tlobjects} = require("../tl/alltlobjects");
+const Helpers = require("../utils/Helpers");
+class BinaryReader {
+
+    /**
+     * Small utility class to read binary data.
+     * @param data {Buffer}
+     */
+    constructor(data) {
+        this.stream = data;
+        this._last = null;
+        this.offset = 0;
+    }
+
+    // region Reading
+
+    // "All numbers are written as little endian."
+    // https://core.telegram.org/mtproto
+    /**
+     * Reads a single byte value.
+     */
+    readByte() {
+        return this.read(1)[0];
+    }
+
+    /**
+     * Reads an integer (4 bytes or 32 bits) value.
+     * @param signed {Boolean}
+     */
+    readInt(signed = true) {
+        let res;
+        if (signed) {
+            res = this.stream.readInt32LE(this.offset);
+        } else {
+            res = this.stream.readUInt32LE(this.offset);
+        }
+        this.offset += 4;
+        return res;
+    }
+
+    /**
+     * Reads a long integer (8 bytes or 64 bits) value.
+     * @param signed
+     * @returns {bigint}
+     */
+    readLong(signed = true) {
+        let res;
+        if (signed) {
+            res = this.stream.readBigInt64LE(this.offset);
+        } else {
+            res = this.stream.readBigUInt64LE(this.offset);
+        }
+        this.offset += 8;
+
+        return res;
+    }
+
+    /**
+     * Reads a real floating point (4 bytes) value.
+     * @returns {number}
+     */
+    readFloat() {
+        return unpack('<f', this.read(4))[0];
+    }
+
+    /**
+     * Reads a real floating point (8 bytes) value.
+     * @returns {BigInt}
+     */
+    readDouble() {
+        return unpack('<f', this.read(8))[0];
+    }
+
+    /**
+     * Reads a n-bits long integer value.
+     * @param bits
+     * @param signed {Boolean}
+     */
+    readLargeInt(bits, signed = true) {
+        let buffer = this.read(Math.floor(bits / 8));
+        return Helpers.readBigIntFromBuffer(buffer, true, signed);
+    }
+
+    /**
+     * Read the given amount of bytes, or -1 to read all remaining.
+     * @param length {number}
+     */
+    read(length = -1) {
+        if (length === -1) {
+            length = this.stream.length - this.offset;
+        }
+        let 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;
+        return result;
+    }
+
+    /**
+     * Gets the byte array representing the current buffer as a whole.
+     * @returns {Buffer}
+     */
+    getBuffer() {
+        return this.stream;
+    }
+
+    // endregion
+
+    // region Telegram custom reading
+    /**
+     * Reads a Telegram-encoded byte array, without the need of
+     * specifying its length.
+     * @returns {Buffer}
+     */
+    tgReadBytes() {
+        let firstByte = this.readByte();
+        let padding, length;
+        if (firstByte === 254) {
+            length = this.readByte() | this.readByte() << 8 | this.readByte() << 16;
+            padding = length % 4;
+        } else {
+            length = firstByte;
+            padding = (length + 1) % 4;
+        }
+        let data = this.read(length);
+
+        if (padding > 0) {
+            padding = 4 - padding;
+            this.read(padding);
+        }
+
+        return data;
+    }
+
+    /**
+     * Reads a Telegram-encoded string.
+     * @returns {string}
+     */
+    tgReadString() {
+        return this.tgReadBytes().toString("utf-8");
+    }
+
+    /**
+     * Reads a Telegram boolean value.
+     * @returns {boolean}
+     */
+    tgReadBool() {
+        let value = this.readInt(false);
+        if (value === 0x997275b5) { // boolTrue
+            return true;
+        } else if (value === 0xbc799737) { //boolFalse
+            return false;
+        } else {
+            throw new Error(`Invalid boolean code ${value.toString("16")}`);
+        }
+    }
+
+    /**
+     * Reads and converts Unix time (used by Telegram)
+     * into a Javascript {Date} object.
+     * @returns {Date}
+     */
+    tgReadDate() {
+        let value = this.readInt();
+        return new Date(value * 1000);
+    }
+
+    /**
+     * Reads a Telegram object.
+     */
+    tgReadObject() {
+        let 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!
+             */
+            let value = constructorId;
+            if (value === 0x997275b5) { // boolTrue
+                return true
+            } else if (value === 0xbc799737) {  // boolFalse
+                return false;
+            } else if (value === 0x1cb5c415) {  // Vector
+                let temp = [];
+                let length = this.readInt();
+                for (let i = 0; i < length; i++) {
+                    temp.push(this.tgReadObject());
+                }
+                return temp;
+            }
+
+            clazz = coreObjects[constructorId];
+
+            if (clazz === undefined) {
+                // If there was still no luck, give up
+                this.seek(-4); // Go back
+                let pos = this.tellPosition();
+                let error = new TypeNotFoundError(constructorId, this.read());
+                this.setPosition(pos);
+                throw error;
+            }
+
+        }
+        return clazz.fromReader(this);
+
+    }
+
+    /**
+     * Reads a vector (a list) of Telegram objects.
+     * @returns {[Buffer]}
+     */
+    tgReadVector() {
+        if (this.readInt(false) !== 0x1cb5c415) {
+            throw new Error('Invalid constructor code, vector was expected');
+        }
+        let count = this.readInt();
+        let temp = [];
+        for (let i = 0; i < count; i++) {
+            temp.push(this.tgReadObject());
+        }
+        return temp;
+    }
+
+    // endregion
+
+    /**
+     * Closes the reader.
+     */
+    close() {
+        this.stream = null;
+    }
+
+    // region Position related
+
+    /**
+     * Tells the current position on the stream.
+     * @returns {number}
+     */
+    tellPosition() {
+        return this.offset;
+    }
+
+    /**
+     * Sets the current position on the stream.
+     * @param position
+     */
+    setPosition(position) {
+        this.offset = position;
+    }
+
+    /**
+     * Seeks the stream position given an offset from the current position.
+     * The offset may be negative.
+     * @param offset
+     */
+    seek(offset) {
+        this.offset += offset;
+    }
+
+    //endregion
+
+}
+
+module.exports = BinaryReader;

+ 16 - 0
gramjs/extensions/BinaryWriter.js

@@ -0,0 +1,16 @@
+class BinaryWriter {
+    constructor(stream) {
+        this._stream = stream;
+    }
+
+    write(buffer) {
+        this._stream = Buffer.concat([this._stream, buffer]);
+    }
+
+    getValue() {
+        return this._stream;
+    }
+
+}
+
+module.exports = BinaryWriter;

+ 88 - 0
gramjs/extensions/MessagePacker.js

@@ -0,0 +1,88 @@
+const Helpers = require("../utils/Helpers");
+const MessageContainer = require("../tl/core/MessageContainer");
+const TLMessage = require("../tl/core/TLMessage");
+const {TLRequest} = require("../tl/tlobject");
+const BinaryWriter = require("../extensions/BinaryWriter");
+
+
+class MessagePacker {
+    constructor(state, logger) {
+        this._state = state;
+        this._queue = [];
+        this._ready = false;
+        this._log = logger;
+    }
+
+    append(state) {
+        this._queue.push(state);
+        this._ready = true;
+    }
+
+    extend(states) {
+        for (let state of states) {
+            this._queue.push(state);
+        }
+        this._ready = true;
+    }
+
+    async get() {
+        if (!this._queue.length) {
+            this._ready = false;
+            while (!this._ready) {
+                await Helpers.sleep(100);
+            }
+        }
+        let data;
+        let buffer = new BinaryWriter(Buffer.alloc(0));
+
+        let batch = [];
+        let size = 0;
+
+        while (this._queue.length && batch.length <= MessageContainer.MAXIMUM_LENGTH) {
+            let 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 instanceof TLRequest,
+                    afterId
+                );
+
+                this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.constructor.name}`);
+                batch.push(state);
+                continue;
+            }
+            if (batch.length) {
+                this._queue.unshift(state);
+                break;
+            }
+            this._log.warn(`Message payload for ${state.request.constructor.name} is too long ${state.data.length} and cannot be sent`);
+            state.promise.reject("Request Payload is too big");
+            size = 0;
+            continue
+        }
+        if (!batch.length) {
+            return null;
+        }
+        if (batch.length > 1) {
+            data = Buffer.concat([struct.pack(
+                '<Ii', MessageContainer.CONSTRUCTOR_ID, batch.length
+            ), buffer.getValue()]);
+
+            let containerId = await this._state.writeDataAsMessage(
+                buffer, data, false
+            );
+            for (let s of batch) {
+                s.containerId = containerId;
+            }
+        }
+
+        data = buffer.getValue();
+        return {batch, data}
+    }
+}
+
+module.exports = MessagePacker;

+ 226 - 0
gramjs/network/Authenticator.js

@@ -0,0 +1,226 @@
+const AES = require("../crypto/AES");
+const AuthKey = require("../crypto/AuthKey");
+const Factorizator = require("../crypto/Factorizator");
+const RSA = require("../crypto/RSA");
+const Helpers = require("../utils/Helpers");
+const {ServerDHParamsFail} = require("../tl/types");
+const {ServerDHParamsOk} = require("../tl/types");
+const {ReqDHParamsRequest} = require("../tl/functions");
+const {SecurityError} = require("../errors/Common");
+const {PQInnerData} = require("../tl/types");
+const BinaryReader = require("../extensions/BinaryReader");
+const {ClientDHInnerData} = require("../tl/types");
+const {DhGenFail} = require("../tl/types");
+const {DhGenRetry} = require("../tl/types");
+const {DhGenOk} = require("../tl/types");
+const {SetClientDHParamsRequest} = require("../tl/functions");
+const {ServerDHInnerData} = require("../tl/types");
+const {ResPQ} = require("../tl/types");
+const {ReqPqMultiRequest} = require("../tl/functions");
+
+
+/**
+ * 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);
+
+    let nonce = Helpers.readBigIntFromBuffer(bytes, false, true);
+
+    let resPQ = await sender.send(new ReqPqMultiRequest({nonce: nonce}));
+    log.debug("Starting authKey generation step 1");
+
+    if (!(resPQ instanceof ResPQ)) {
+        throw new Error(`Step 1 answer was ${resPQ}`)
+    }
+    if (resPQ.nonce !== nonce) {
+        throw new SecurityError("Step 1 invalid nonce from server")
+    }
+    let 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);
+    p = getByteArray(p);
+
+    q = getByteArray(q);
+
+    bytes = Helpers.generateRandomBytes(32);
+    let newNonce = Helpers.readBigIntFromBuffer(bytes, true, true);
+
+
+    let pqInnerData = new PQInnerData({
+            pq: 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 (let fingerprint of resPQ.serverPublicKeyFingerprints) {
+        cipherText = RSA.encrypt(fingerprint.toString(), pqInnerData.bytes);
+        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');
+    }
+
+    let serverDhParams = await sender.send(new ReqDHParamsRequest({
+            nonce: resPQ.nonce,
+            serverNonce: resPQ.serverNonce,
+            p: p, q: q,
+            publicKeyFingerprint: targetFingerprint,
+            encryptedData: cipherText
+        }
+    ));
+    if (!(serverDhParams instanceof ServerDHParamsOk || serverDhParams instanceof ServerDHParamsFail)) {
+        throw new Error(`Step 2.1 answer was ${serverDhParams}`)
+    }
+    if (serverDhParams.nonce !== resPQ.nonce) {
+        throw new SecurityError('Step 2 invalid nonce from server');
+
+    }
+
+    if (serverDhParams.serverNonce !== resPQ.serverNonce) {
+        throw new SecurityError('Step 2 invalid server nonce from server')
+    }
+
+    if (serverDhParams instanceof ServerDHParamsFail) {
+        let sh = Helpers.sha1(Helpers.readBufferFromBigInt(newNonce, 32, true, true).slice(4, 20));
+        let nnh = Helpers.readBigIntFromBuffer(sh, true, true);
+        if (serverDhParams.newNonceHash !== nnh) {
+            throw new SecurityError('Step 2 invalid DH fail nonce from server')
+
+        }
+    }
+    if (!(serverDhParams instanceof 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
+    let {key, iv} = Helpers.generateKeyDataFromNonce(resPQ.serverNonce, newNonce);
+    if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
+        // See PR#453
+        throw new SecurityError('Step 3 AES block size mismatch')
+    }
+    let plainTextAnswer = AES.decryptIge(
+        serverDhParams.encryptedAnswer, key, iv
+    );
+    let reader = new BinaryReader(plainTextAnswer);
+    reader.read(20); // hash sum
+    let serverDhInner = reader.tgReadObject();
+    if (!(serverDhInner instanceof ServerDHInnerData)) {
+        throw new Error(`Step 3 answer was ${serverDhInner}`)
+    }
+
+    if (serverDhInner.nonce !== resPQ.nonce) {
+        throw new SecurityError('Step 3 Invalid nonce in encrypted answer')
+    }
+    if (serverDhInner.serverNonce !== resPQ.serverNonce) {
+        throw new SecurityError('Step 3 Invalid server nonce in encrypted answer')
+    }
+    let dhPrime = Helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false);
+    let ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false);
+    let timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000);
+
+    let b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false);
+    let gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime);
+    let gab = Helpers.modExp(ga, b, dhPrime);
+
+    // Prepare client DH Inner Data
+    let clientDhInner = new ClientDHInnerData({
+            nonce: resPQ.nonce,
+            serverNonce: resPQ.serverNonce,
+            retryId: 0,  // TODO Actual retry ID
+            gB: getByteArray(gb, false)
+        }
+    ).bytes;
+
+    let clientDdhInnerHashed = Buffer.concat([
+        Helpers.sha1(clientDhInner),
+        clientDhInner
+    ]);
+    // Encryption
+    let clientDhEncrypted = AES.encryptIge(clientDdhInnerHashed, key, iv);
+    let dhGen = await sender.send(new SetClientDHParamsRequest({
+            nonce: resPQ.nonce,
+            serverNonce: resPQ.serverNonce,
+            encryptedData: clientDhEncrypted,
+        }
+    ));
+    let nonceTypes = [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}`)
+    }
+    let name = dhGen.constructor.name;
+    if (dhGen.nonce !== resPQ.nonce) {
+        throw new SecurityError(`Step 3 invalid ${name} nonce from server`)
+    }
+    if (dhGen.serverNonce !== resPQ.serverNonce) {
+        throw new SecurityError(`Step 3 invalid ${name} server nonce from server`)
+
+    }
+    let authKey = new AuthKey(getByteArray(gab));
+    let nonceNumber = 1 + nonceTypes.indexOf(dhGen.constructor);
+
+    let newNonceHash = authKey.calcNewNonceHash(newNonce, nonceNumber);
+    let dhHash = dhGen[`newNonceHash${nonceNumber}`];
+
+    if (dhHash !== newNonceHash) {
+        throw new SecurityError('Step 3 invalid new nonce hash');
+    }
+
+    if (!(dhGen instanceof DhGenOk)) {
+        throw new Error(`Step 3.2 answer was ${dhGen}`)
+    }
+    log.debug("Finished authKey generation step 3");
+
+    return {authKey, timeOffset};
+
+
+}
+
+
+/**
+ * Gets a fingerprint text in 01-23-45-67-89-AB-CD-EF format (no hyphens)
+ * @param fingerprint {Array}
+ * @returns {string}
+ */
+function getFingerprintText(fingerprint) {
+    return fingerprint.toString();
+}
+
+
+/**
+ * Gets the arbitrary-length byte array corresponding to the given integer
+ * @param integer {number,BigInt}
+ * @param signed {boolean}
+ * @returns {Buffer}
+ */
+function getByteArray(integer, signed = false) {
+    let bits = integer.toString(2).length;
+    let byteLength = Math.floor((bits + 8 - 1) / 8);
+    let f;
+    f = Helpers.readBufferFromBigInt(BigInt(integer), byteLength, false, signed);
+
+    return f;
+}
+
+module.exports = doAuthentication;

+ 94 - 0
gramjs/network/MTProtoPlainSender.js

@@ -0,0 +1,94 @@
+/**
+ *  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("../utils/Helpers");
+const MTProtoState = require("./MTProtoState");
+const struct = require("python-struct");
+const BinaryReader = require("../extensions/BinaryReader");
+const {InvalidBufferError} = require("../errors/Common");
+
+/**
+ * MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)
+ */
+
+class MTProtoPlainSender {
+    /**
+     * Initializes the MTProto plain sender.
+     * @param connection connection: the Connection to be used.
+     * @param loggers
+     */
+    constructor(connection, loggers) {
+        this._state = new MTProtoState(connection, loggers);
+        this._connection = connection;
+
+    }
+
+    /**
+     * Sends and receives the result for the given request.
+     * @param request
+     */
+    async send(request) {
+        let body = request.bytes;
+        let msgId = this._state._getNewMsgId();
+        let res = Buffer.concat([
+            struct.pack('<qqi', [0, msgId.toString(), body.length]),
+            body
+        ]);
+
+        await this._connection.send(res);
+        body = await this._connection.recv();
+        if (body.length < 9) {
+            throw new InvalidBufferError(body);
+        }
+        let reader = new BinaryReader(body);
+        let authKeyId = reader.readLong();
+        if (authKeyId !== 0n) {
+            throw new Error("Bad authKeyId");
+        }
+        msgId = reader.readLong();
+        if (msgId === 0n) {
+            throw new Error("Bad msgId");
+        }
+        /** ^ We should make sure that the read ``msg_id`` is greater
+         * than our own ``msg_id``. However, under some circumstances
+         * (bad system clock/working behind proxies) this seems to not
+         * be the case, which would cause endless assertion errors.
+         */
+
+        let length = reader.readInt();
+        if (length <= 0) {
+            throw new Error("Bad length");
+        }
+        /**
+         * We could read length 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.
+         */
+        return reader.tgReadObject();
+
+    }
+
+
+    /**
+     * Generates a new message ID based on the current time (in ms) since epoch
+     * @returns {BigInt}
+     */
+    getNewMsgId() {
+        //See https://core.telegram.org/mtproto/description#message-identifier-msg-id
+        let msTime = Date.now();
+        let newMsgId = ((BigInt(Math.floor(msTime / 1000)) << BigInt(32)) | // "must approximately equal unixtime*2^32"
+            (BigInt(msTime % 1000) << BigInt(32)) | // "approximate moment in time the message was created"
+            BigInt(Helpers.getRandomInt(0, 524288)) << BigInt(2));// "message identifiers are divisible by 4"
+        //Ensure that we always return a message ID which is higher than the previous one
+        if (this._lastMsgId >= newMsgId) {
+            newMsgId = this._lastMsgId + 4n
+        }
+        this._lastMsgId = newMsgId;
+        return BigInt(newMsgId);
+
+    }
+
+}
+
+module.exports = MTProtoPlainSender;

+ 721 - 0
gramjs/network/MTProtoSender.js

@@ -0,0 +1,721 @@
+const MtProtoPlainSender = require("./MTProtoPlainSender");
+const MTProtoState = require("./MTProtoState");
+const Helpers = require("../utils/Helpers");
+const {MsgsAck} = require("../tl/types");
+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 format = require('string-format');
+
+const MessagePacker = require("../extensions/MessagePacker");
+const Pong = require("../tl/core/GZIPPacked");
+const BinaryReader = require("../extensions/BinaryReader");
+const {
+    BadServerSalt, BadMsgNotification, MsgDetailedInfo, MsgNewDetailedInfo,
+    NewSessionCreated, FutureSalts, MsgsStateReq, MsgResendReq, MsgsAllInfo
+} = require("../tl/types");
+const {SecurityError} = require("../errors/Common");
+const {InvalidBufferError} = require("../errors/Common");
+const {LogOutRequest} = require("../tl/functions/auth");
+const log4js = require('log4js');
+const {RPCMessageToError} = require("../errors");
+const {TypeNotFoundError} = require("../errors/Common");
+const logger = log4js.getLogger("gramjs");
+
+//const {tlobjects} = require("../gramjs/tl/alltlobjects");
+format.extend(String.prototype, {});
+
+/**
+ * MTProto Mobile Protocol sender
+ * (https://core.telegram.org/mtproto/description)
+ * This class is responsible for wrapping requests into `TLMessage`'s,
+ * sending them over the network and receiving them in a safe manner.
+ *
+ * Automatic reconnection due to temporary network issues is a concern
+ * for this class as well, including retry of messages that could not
+ * be sent successfully.
+ *
+ * A new authorization key will be generated on connection if no other
+ * key exists yet.
+ */
+class MTProtoSender {
+
+    /**
+     * @param authKey
+     * @param opt
+     */
+    constructor(authKey, opt = {
+        logger: null,
+        retries: 5,
+        delay: 1,
+        autoReconnect: true,
+        connectTimeout: null,
+        authKeyCallback: null,
+        updateCallback: null,
+        autoReconnectCallback: null
+    }) {
+        this._connection = null;
+        this._log = opt.logger;
+        this._retries = opt.retries;
+        this._delay = opt.delay;
+        this._autoReconnect = opt.autoReconnect;
+        this._connectTimeout = opt.connectTimeout;
+        this._authKeyCallback = opt.authKeyCallback;
+        this._updateCallback = opt.updateCallback;
+        this._autoReconnectCallback = opt.autoReconnectCallback;
+
+        /**
+         * Whether the user has explicitly connected or disconnected.
+         *
+         * If a disconnection happens for any other reason and it
+         * was *not* user action then the pending messages won't
+         * be cleared but on explicit user disconnection all the
+         * pending futures should be cancelled.
+         */
+        this._user_connected = 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;
+
+        /**
+         * Preserving the references of the AuthKey and state is important
+         */
+        this.authKey = authKey || new AuthKey(null);
+        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);
+
+        /**
+         * Sent states are remembered until a response is received.
+         */
+        this._pending_state = {};
+
+        /**
+         * Responses must be acknowledged, and we can also batch these.
+         */
+        this._pending_ack = 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 = [];
+
+        /**
+         * 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),
+        }
+
+
+    }
+
+    // Public API
+
+    /**
+     * Connects to the specified given connection using the given auth key.
+     * @param connection
+     * @returns {Promise<boolean>}
+     */
+    async connect(connection) {
+        if (this._user_connected) {
+            this._log.info('User is already connected!');
+            return false;
+        }
+        this._connection = connection;
+        await this._connect();
+        return true;
+    }
+
+    is_connected() {
+        return this._user_connected
+    }
+
+    /**
+     * Cleanly disconnects the instance from the network, cancels
+     * all pending requests, and closes the send and receive loops.
+     */
+    async disconnect() {
+        await this._disconnect();
+    }
+
+    /**
+     *
+     This method enqueues the given request to be sent. Its send
+     state will be saved until a response arrives, and a ``Future``
+     that will be resolved when the response arrives will be returned:
+
+     .. code-block:: javascript
+
+     async def method():
+     # Sending (enqueued for the send loop)
+     future = sender.send(request)
+     # Receiving (waits for the receive loop to read the result)
+     result = await future
+
+     Designed like this because Telegram may send the response at
+     any point, and it can send other items while one waits for it.
+     Once the response for this future arrives, it is set with the
+     received result, quite similar to how a ``receive()`` call
+     would otherwise work.
+
+     Since the receiving part is "built in" the future, it's
+     impossible to await receive a result that was never sent.
+     * @param request
+     * @returns {RequestState}
+     */
+    send(request) {
+        if (!this._user_connected) {
+            throw new Error('Cannot send requests while disconnected')
+        }
+
+        if (!Helpers.isArrayLike(request)) {
+            let state = new RequestState(request);
+            this._send_queue.append(state);
+            return state.promise;
+        } else {
+            throw new Error("not supported");
+        }
+    }
+
+    /**
+     * Performs the actual connection, retrying, generating the
+     * authorization key if necessary, and starting the send and
+     * receive loops.
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _connect() {
+        this._log.info('Connecting to {0}...'.replace("{0}", this._connection));
+        await this._connection.connect();
+        this._log.debug("Connection success!");
+        if (!this.authKey._key) {
+            let plain = new MtProtoPlainSender(this._connection, this._loggers);
+            this._log.debug('New auth_key attempt ...');
+            let res = await doAuthentication(plain, this._log);
+            this._log.debug("Generated new auth_key successfully");
+            this.authKey.key = res.authKey;
+            this._state.time_offset = res.timeOffset;
+
+            /**
+             * This is *EXTREMELY* important since we don't control
+             * external references to the authorization key, we must
+             * notify whenever we change it. This is crucial when we
+             * switch to different data centers.
+             */
+            if (this._authKeyCallback) {
+                this._authKeyCallback(this.authKey)
+
+            }
+
+        } else {
+            this._log.debug('Already have an auth key ...');
+        }
+        this._user_connected = true;
+
+        this._log.debug('Starting send loop');
+        this._send_loop_handle = this._send_loop();
+
+        this._log.debug('Starting receive loop');
+        this._recv_loop_handle = this._recv_loop();
+
+        // _disconnected only completes after manual disconnection
+        // or errors after which the sender cannot continue such
+        // as failing to reconnect or any unexpected error.
+
+        this._log.info('Connection to %s complete!', this._connection.toString())
+
+    }
+
+
+    async _disconnect(error = null) {
+        if (this._connection === null) {
+            this._log.info('Not disconnecting (already have no connection)');
+            return
+        }
+        this._log.info('Disconnecting from %s...', this._connection);
+        this._user_connected = false;
+        this._log.debug('Closing current connection...');
+        await this._connection.disconnect()
+
+    }
+
+    /**
+     * This loop is responsible for popping items off the send
+     * queue, encrypting them, and sending them over the network.
+     * Besides `connect`, only this method ever sends data.
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _send_loop() {
+        while (this._user_connected && !this._reconnecting) {
+            if (this._pending_ack.size) {
+                let 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._log.debug('Waiting for messages to send...');
+            this._log.debug("Waiting for messages to send...");
+            // 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.
+            let res = await this._send_queue.get();
+            if (!res) {
+                continue
+            }
+            let data = res.data;
+            let batch = res.batch;
+            this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`);
+
+            data = this._state.encryptMessageData(data);
+
+            try {
+                await this._connection.send(data);
+            } catch (e) {
+                console.log(e);
+                this._log.info('Connection closed while sending data');
+                return
+            }
+            for (let state of batch) {
+                this._pending_state[state.msgId] = state;
+            }
+            this._log.debug('Encrypted messages put in a queue to be sent');
+
+        }
+    }
+
+
+    async _recv_loop() {
+        let body, message;
+
+        while (this._user_connected && !this._reconnecting) {
+            //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.debug('Connection closed while receiving data');
+                return
+            }
+            try {
+                message = await this._state.decryptMessageData(body);
+            } catch (e) {
+                console.log(e);
+
+                if (e instanceof TypeNotFoundError) {
+                    // Received object which we don't know how to deserialize
+                    this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`);
+                    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}`);
+                    continue
+                } else if (e instanceof InvalidBufferError) {
+                    this._log.info('Broken authorization key; resetting');
+                    this.authKey.key = null;
+                    if (this._authKeyCallback) {
+                        this._authKeyCallback(null)
+                    }
+                    return
+                } else {
+                    this._log.error('Unhandled error while receiving data');
+                    return
+                }
+            }
+            try {
+                await this._processMessage(message)
+            } catch (e) {
+                console.log(e);
+                this._log.error('Unhandled error while receiving data');
+
+            }
+        }
+
+    }
+
+    // Response Handlers
+
+    /**
+     * Adds the given message to the list of messages that must be
+     * acknowledged and dispatches control to different ``_handle_*``
+     * method based on its type.
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _processMessage(message) {
+        this._pending_ack.add(message.msgId);
+        message.obj = await message.obj;
+        let handler = this._handlers[message.obj.CONSTRUCTOR_ID];
+        if (!handler) {
+            handler = this._handleUpdate
+        }
+
+        await handler(message);
+    }
+
+    /**
+     * Pops the states known to match the given ID from pending messages.
+     * This method should be used when the response isn't specific.
+     * @param msgId
+     * @returns {*[]}
+     * @private
+     */
+    _popStates(msgId) {
+        let state = this._pending_state[msgId];
+        if (state) {
+            delete this._pending_state[msgId];
+            return [state];
+        }
+
+        let to_pop = [];
+
+        for (state in this._pending_state) {
+            if (state.containerId === msgId) {
+                to_pop.push(state.msgId);
+            }
+        }
+
+        if (to_pop) {
+            let temp = [];
+            for (let x of to_pop) {
+                temp.push(this._pending_state[x]);
+                delete this._pending_state[x];
+
+            }
+            return temp
+        }
+
+        for (let ack of this._last_acks) {
+            if (ack.msgId === msgId)
+                return [ack]
+        }
+
+        return []
+    }
+
+
+    /**
+     * Handles the result for Remote Procedure Calls:
+     * rpc_result#f35c6d01 req_msg_id:long result:bytes = RpcResult;
+     * This is where the future results for sent requests are set.
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleRPCResult(message) {
+        let RPCResult = message.obj;
+        let state = this._pending_state[RPCResult.reqMsgId];
+        if (state) {
+            delete this._pending_state[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
+            // However receiving a File() with empty bytes is "common".
+            // See #658, #759 and #958. They seem to happen in a container
+            // which contain the real response right after.
+            try {
+                let reader = new BinaryReader(RPCResult.body);
+                if (!(reader.tgReadObject() instanceof File)) {
+                    throw new TypeNotFoundError("Not an upload.File");
+                }
+            } catch (e) {
+                console.log(e);
+                if (e instanceof TypeNotFoundError) {
+                    this._log.info(`Received response without parent request: ${RPCResult.body}`);
+                    return
+                } else {
+                    throw e;
+                }
+            }
+        }
+        if (RPCResult.error) {
+            let error = RPCMessageToError(RPCResult.error, state.request);
+            console.log("error happen", error);
+
+            this._send_queue.append(
+                new RequestState(new MsgsAck({msgIds: [state.msgId]}))
+            );
+            state.reject(error)
+        } else {
+            let reader = new BinaryReader(RPCResult.body);
+            let read = await state.request.readResult(reader);
+            state.resolve(read);
+        }
+
+
+    }
+
+    /**
+     * Processes the inner messages of a container with many of them:
+     * msg_container#73f1f8dc messages:vector<%Message> = MessageContainer;
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleContainer(message) {
+        this._log.debug('Handling container');
+        for (let inner_message of message.obj.messages) {
+            await this._processMessage(inner_message)
+        }
+    }
+
+    /**
+     * Unpacks the data from a gzipped object and processes it:
+     * gzip_packed#3072cfa1 packed_data:bytes = Object;
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleGzipPacked(message) {
+        this._log.debug('Handling gzipped data');
+        let reader = new BinaryReader(message.obj.data);
+        message.obj = reader.tgReadObject();
+        await this._processMessage(message)
+    }
+
+    async _handleUpdate(message) {
+        if (message.obj.SUBCLASS_OF_ID !== 0x8af52aac) {  // crc32(b'Updates')
+            //TODO fix this. currently getting an error about this not being defined.
+            logger.warn(`Note: ${message.obj.constructor.name} is not an update, not dispatching it`);
+            return
+        }
+        this._log.debug('Handling update %s', message.obj.constructor.name);
+        if (this._updateCallback) {
+            this._updateCallback(message.obj)
+        }
+    }
+
+    /**
+     * Handles pong results, which don't come inside a ``RPCResult``
+     * but are still sent through a request:
+     * pong#347773c5 msg_id:long ping_id:long = Pong;
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handlePong(message) {
+        let pong = message.obj;
+        this._log.debug(`Handling pong for message ${pong.msgId}`);
+        let state = this._pending_state[pong.msgId];
+        delete this._pending_state[pong.msgId];
+
+        // Todo Check result
+        if (state) {
+            state.resolve(pong)
+        }
+    }
+
+    /**
+     * Corrects the currently used server salt to use the right value
+     * before enqueuing the rejected message to be re-sent:
+     * bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int
+     * error_code:int new_server_salt:long = BadMsgNotification;
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleBadServerSalt(message) {
+        let badSalt = message.obj;
+        this._log.debug(`Handling bad salt for message ${badSalt.badMsgId}`);
+        this._state.salt = badSalt.newServerSalt;
+        let states = this._popStates(badSalt.badMsgId);
+        this._send_queue.extend(states);
+        this._log.debug(`${states.length} message(s) will be resent`);
+    }
+
+    /**
+     * Adjusts the current state to be correct based on the
+     * received bad message notification whenever possible:
+     * bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int
+     * error_code:int = BadMsgNotification;
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleBadNotification(message) {
+        let badMsg = message.obj;
+        let states = this._popStates(badMsg.badMsgId);
+        this._log.debug(`Handling bad msg ${badMsg}`);
+        if ([16, 17].contains(badMsg.errorCode)) {
+            // Sent msg_id too low or too high (respectively).
+            // Use the current msg_id to determine the right time offset.
+            let to = this._state.updateTimeOffset(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
+            // TODO A better fix would be to start with a new fresh session ID
+            this._state._sequence += 64
+        } else if (badMsg.errorCode === 33) {
+            // msg_seqno too high never seems to happen but just in case
+            this._state._sequence -= 16
+        } else {
+            for (let state of states) {
+                // TODO set errors;
+                /* state.future.set_exception(
+                BadMessageError(state.request, bad_msg.error_code))*/
+            }
+
+            return
+        }
+        // Messages are to be re-sent once we've corrected the issue
+        this._send_queue.extend(states);
+        this._log.debug(`${states.length} messages will be resent due to bad msg`)
+    }
+
+    /**
+     * Updates the current status with the received detailed information:
+     * msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long
+     * bytes:int status:int = MsgDetailedInfo;
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleDetailedInfo(message) {
+        // TODO https://goo.gl/VvpCC6
+        let msgId = message.obj.answerMsgId;
+        this._log.debug(`Handling detailed info for message ${msgId}`);
+        this._pending_ack.add(msgId)
+    }
+
+    /**
+     * Updates the current status with the received detailed information:
+     * msg_new_detailed_info#809db6df answer_msg_id:long
+     * bytes:int status:int = MsgDetailedInfo;
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleNewDetailedInfo(message) {
+        // TODO https://goo.gl/VvpCC6
+        let msgId = message.obj.answerMsgId;
+        this._log.debug(`Handling new detailed info for message ${msgId}`);
+        this._pending_ack.add(msgId)
+    }
+
+    /**
+     * Updates the current status with the received session information:
+     * new_session_created#9ec20908 first_msg_id:long unique_id:long
+     * server_salt:long = NewSession;
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleNewSessionCreated(message) {
+        // TODO https://goo.gl/LMyN7A
+        this._log.debug('Handling new session created');
+        this._state.salt = message.obj.serverSalt;
+    }
+
+    /**
+     * Handles a server acknowledge about our messages. Normally
+     * these can be ignored except in the case of ``auth.logOut``:
+     *
+     *     auth.logOut#5717da40 = Bool;
+     *
+     * Telegram doesn't seem to send its result so we need to confirm
+     * it manually. No other request is known to have this behaviour.
+
+     * Since the ID of sent messages consisting of a container is
+     * never returned (unless on a bad notification), this method
+     * also removes containers messages when any of their inner
+     * messages are acknowledged.
+
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleAck(message) {
+        let ack = message.obj;
+        this._log.debug(`Handling acknowledge for ${ack.msgIds}`);
+        for (let msgId of ack.msgIds) {
+            let state = this._pending_state[msgId];
+            if (state && state.request instanceof LogOutRequest) {
+                delete this._pending_state[msgId];
+                state.resolve(true)
+            }
+        }
+
+    }
+
+    /**
+     * Handles future salt results, which don't come inside a
+     * ``rpc_result`` but are still sent through a request:
+     *     future_salts#ae500895 req_msg_id:long now:int
+     *     salts:vector<future_salt> = FutureSalts;
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleFutureSalts(message) {
+        // 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}`);
+        let state = this._pending_state[message.msgId];
+
+        if (state) {
+            delete this._pending_state[message];
+            state.resolve(message.obj)
+        }
+    }
+
+    /**
+     * Handles both :tl:`MsgsStateReq` and :tl:`MsgResendReq` by
+     * enqueuing a :tl:`MsgsStateInfo` to be sent at a later point.
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleStateForgotten(message) {
+        this._send_queue.append(new RequestState(new MsgsStateInfo(
+            message.msgId, String.fromCharCode(1).repeat(message.obj.msgIds))))
+    }
+
+    /**
+     * Handles :tl:`MsgsAllInfo` by doing nothing (yet).
+     * @param message
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _handleMsgAll(message) {
+
+    }
+
+
+}
+
+module.exports = MTProtoSender;

+ 270 - 0
gramjs/network/MTProtoState.js

@@ -0,0 +1,270 @@
+const struct = require("python-struct");
+const Helpers = require("../utils/Helpers");
+const AES = require("../crypto/AES");
+const BinaryReader = require("../extensions/BinaryReader");
+const GZIPPacked = require("../tl/core/GZIPPacked");
+const {TLMessage} = require("../tl/core");
+const {SecurityError} = require("../errors/Common");
+const {InvalidBufferError} = require("../errors/Common");
+
+class MTProtoState {
+    /**
+     *
+     `telethon.network.mtprotosender.MTProtoSender` needs to hold a state
+     in order to be able to encrypt and decrypt incoming/outgoing messages,
+     as well as generating the message IDs. Instances of this class hold
+     together all the required information.
+
+     It doesn't make sense to use `telethon.sessions.abstract.Session` for
+     the sender because the sender should *not* be concerned about storing
+     this information to disk, as one may create as many senders as they
+     desire to any other data center, or some CDN. Using the same session
+     for all these is not a good idea as each need their own authkey, and
+     the concept of "copying" sessions with the unnecessary entities or
+     updates state for these connections doesn't make sense.
+
+     While it would be possible to have a `MTProtoPlainState` that does no
+     encryption so that it was usable through the `MTProtoLayer` and thus
+     avoid the need for a `MTProtoPlainSender`, the `MTProtoLayer` is more
+     focused to efficiency and this state is also more advanced (since it
+     supports gzipping and invoking after other message IDs). There are too
+     many methods that would be needed to make it convenient to use for the
+     authentication process, at which point the `MTProtoPlainSender` is better
+     * @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;
+        this.reset();
+    }
+
+    /**
+     * Resets the state
+     */
+    reset() {
+        // Session IDs can be random on every connection
+        this.id = Helpers.generateRandomLong(true);
+        this._sequence = 0;
+        this._lastMsgId = 0;
+    }
+
+    /**
+     * Updates the message ID to a new one,
+     * used when the time offset changed.
+     * @param message
+     */
+    updateMessageId(message) {
+        message.msgId = this._getNewMsgId();
+    }
+
+    /**
+     * Calculate the key based on Telegram guidelines, specifying whether it's the client or not
+     * @param authKey
+     * @param msgKey
+     * @param client
+     * @returns {{iv: Buffer, key: Buffer}}
+     */
+    _calcKey(authKey, msgKey, client) {
+        let x = client === true ? 0 : 8;
+        let sha256a = Helpers.sha256(Buffer.concat([
+            msgKey,
+            authKey.slice(x, (x + 36))
+        ]));
+        let sha256b = Helpers.sha256(Buffer.concat([
+            authKey.slice(x + 40, x + 76),
+            msgKey,
+        ]));
+
+        let key = Buffer.concat([
+            sha256a.slice(0, 8),
+            sha256b.slice(8, 24),
+            sha256a.slice(24, 32)]);
+        let iv = Buffer.concat([
+            sha256b.slice(0, 8),
+            sha256a.slice(8, 24),
+            sha256b.slice(24, 32)]);
+        return {key, iv}
+
+    }
+
+    /**
+     * Writes a message containing the given data into buffer.
+     * Returns the message id.
+     * @param buffer
+     * @param data
+     * @param contentRelated
+     * @param afterId
+     */
+    async writeDataAsMessage(buffer, data, contentRelated, afterId) {
+        let msgId = this._getNewMsgId();
+        let seqNo = this._getSeqNo(contentRelated);
+        let body;
+        if (!afterId) {
+            body = await GZIPPacked.GZIPIfSmaller(contentRelated, data);
+        } else {
+            body = await GZIPPacked.GZIPIfSmaller(contentRelated,
+                new InvokeAfterMsgRequest(afterId, data).toBuffer()
+            );
+        }
+        buffer.write(struct.pack('<qii', msgId.toString(), seqNo, body.length));
+        buffer.write(body);
+        return msgId;
+    }
+
+    /**
+     * Encrypts the given message data using the current authorization key
+     * following MTProto 2.0 guidelines core.telegram.org/mtproto/description.
+     * @param data
+     */
+    encryptMessageData(data) {
+        data = Buffer.concat([
+            struct.pack('<qq', this.salt.toString(), this.id.toString()),
+            data,
+        ]);
+        let 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)"
+        let msgKeyLarge = Helpers.sha256(
+            Buffer.concat([
+                this.authKey.key.slice(88, 88 + 32),
+                data,
+                padding
+            ])
+        );
+        // "msg_key = substr (msg_key_large, 8, 16)"
+        let msgKey = msgKeyLarge.slice(8, 24);
+
+        let {iv, key} = this._calcKey(this.authKey.key, msgKey, true);
+
+        let keyId = Helpers.readBufferFromBigInt(this.authKey.keyId, 8);
+        return Buffer.concat([
+            keyId,
+            msgKey,
+            AES.encryptIge(Buffer.concat([
+                    data,
+                    padding
+                ]),
+                key,
+                iv)
+        ]);
+
+    }
+
+    /**
+     * Inverse of `encrypt_message_data` for incoming server messages.
+     * @param body
+     */
+    async decryptMessageData(body) {
+        if (body.length < 8) {
+            throw new InvalidBufferError(body);
+        }
+
+        // TODO Check salt,sessionId, and sequenceNumber
+        let keyId = Helpers.readBigIntFromBuffer(body.slice(0, 8));
+
+        if (keyId !== this.authKey.keyId) {
+            throw new SecurityError('Server replied with an invalid auth key');
+        }
+
+        let msgKey = body.slice(8, 24);
+        let {iv, key} = this._calcKey(this.authKey.key, msgKey, false);
+        body = AES.decryptIge(body.slice(24), key, iv);
+
+        // https://core.telegram.org/mtproto/security_guidelines
+        // Sections "checking sha256 hash" and "message length"
+
+        let ourKey = Helpers.sha256(Buffer.concat([
+            this.authKey.key.slice(96, 96 + 32),
+            body
+        ]));
+
+        if (!msgKey.equals(ourKey.slice(8, 24))) {
+            throw new SecurityError(
+                "Received msg_key doesn't match with expected one")
+        }
+
+        let reader = new BinaryReader(body);
+        reader.readLong(); // removeSalt
+        let serverId = reader.readLong();
+        if (serverId !== this.id) {
+            //throw new SecurityError('Server replied with a wrong session ID');
+        }
+
+        let remoteMsgId = reader.readLong();
+        let 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.
+        let obj = await reader.tgReadObject();
+
+        return new TLMessage(remoteMsgId, remoteSequence, obj);
+
+    }
+
+    /**
+     * Generates a new unique message ID based on the current
+     * time (in ms) since epoch, applying a known time offset.
+     * @private
+     */
+    _getNewMsgId() {
+
+        let now = new Date().getTime() / 1000 + this.timeOffset;
+        let nanoseconds = Math.floor((now - Math.floor(now)) * 1e+9);
+        let newMsgId = (BigInt(Math.floor(now)) << 32n) | (BigInt(nanoseconds) << 2n);
+        if (this._lastMsgId >= newMsgId) {
+            newMsgId = this._lastMsgId + 4n;
+        }
+        this._lastMsgId = newMsgId;
+
+        return newMsgId;
+    }
+
+    /**
+     * Updates the time offset to the correct
+     * one given a known valid message ID.
+     * @param correctMsgId
+     */
+    updateTimeOffset(correctMsgId) {
+        let bad = this._getNewMsgId();
+        let old = this.timeOffset;
+        let now = Math.floor(new Date().getTime() / 1000);
+        let correct = correctMsgId >> 32n;
+        this.timeOffset = correct - now;
+
+        if (this.timeOffset !== old) {
+            this._lastMsgId = 0;
+            this._log.debug(
+                `Updated time offset (old offset ${old}, bad ${bad}, good ${correctMsgId}, new ${this.timeOffset})`,
+            )
+        }
+
+        return this.timeOffset;
+    }
+
+    /**
+     * Generates the next sequence number depending on whether
+     * it should be for a content-related query or not.
+     * @param contentRelated
+     * @private
+     */
+    _getSeqNo(contentRelated) {
+        if (contentRelated) {
+            let result = this._sequence * 2 + 1;
+            this._sequence += 1;
+            return result;
+        } else {
+            return this._sequence * 2;
+        }
+    }
+
+}
+
+
+module.exports = MTProtoState;

+ 21 - 0
gramjs/network/RequestState.js

@@ -0,0 +1,21 @@
+const Helpers = require("../utils/Helpers");
+
+class RequestState {
+
+
+    constructor(request, after = null) {
+        this.containerId = null;
+        this.msgId = null;
+        this.request = request;
+        this.data = request.bytes;
+        this.after = after;
+        this.result = null;
+        this.promise = new Promise((resolve, reject) => {
+            this.resolve = resolve;
+            this.reject = reject
+        });
+    }
+
+}
+
+module.exports = RequestState;

+ 160 - 0
gramjs/network/connection/Connection.js

@@ -0,0 +1,160 @@
+const {PromiseSocket, TimeoutError} = require("promise-socket");
+const {Socket} = require("net");
+const Helpers = require("../../utils/Helpers");
+
+/**
+ * The `Connection` class is a wrapper around ``asyncio.open_connection``.
+ *
+ * Subclasses will implement different transport modes as atomic operations,
+ * which this class eases doing since the exposed interface simply puts and
+ * gets complete data payloads to and from queues.
+ *
+ * The only error that will raise from send and receive methods is
+ * ``ConnectionError``, which will raise when attempting to send if
+ * the client is disconnected (includes remote disconnections).
+ */
+class Connection {
+    packetCodec = null;
+
+    constructor(ip, port, dcId, loggers) {
+        this._ip = ip;
+        this._port = port;
+        this._dcId = dcId;
+        this._log = loggers;
+        this._reader = null;
+        this._writer = null;
+        this._connected = false;
+        this._sendTask = null;
+        this._recvTask = null;
+        this._codec = null;
+        this._obfuscation = null;  // TcpObfuscated and MTProxy
+        this._sendArray = [];
+        this._recvArray = [];
+        this.socket = new PromiseSocket(new Socket());
+
+    }
+
+    async _connect() {
+        await this.socket.connect(this._port, this._ip);
+
+        //await this.socket.connect({host: this._ip, port: this._port});
+        this._codec = new this.packetCodec(this);
+        this._initConn();
+    }
+
+    async connect() {
+        await this._connect();
+        this._connected = true;
+        this._sendTask = this._sendLoop();
+        this._recvTask = this._recvLoop();
+    }
+
+    async disconnect() {
+        this._connected = false;
+        this.socket.close();
+    }
+
+    async send(data) {
+        if (!this._connected) {
+            throw new Error("Not connected");
+        }
+        while (this._sendArray.length !== 0) {
+            await Helpers.sleep(1000);
+        }
+        this._sendArray.push(data);
+    }
+
+    async recv() {
+        while (this._connected) {
+
+            while (this._recvArray.length === 0) {
+                await Helpers.sleep(1000);
+            }
+            let result = this._recvArray.pop();
+
+            if (result) { // null = sentinel value = keep trying
+                return result
+            }
+        }
+        throw new Error("Not connected");
+    }
+
+    async _sendLoop() {
+        // TODO handle errors
+        try {
+
+
+            while (this._connected) {
+                while (this._sendArray.length === 0) {
+                    await Helpers.sleep(1000);
+                }
+                await this._send(this._sendArray.pop());
+
+            }
+        } catch (e) {
+            console.log(e);
+            this._log.info('The server closed the connection while sending')
+
+        }
+    }
+
+    async _recvLoop() {
+        let data;
+        while (this._connected) {
+            try {
+                data = await this._recv();
+            } catch (e) {
+                console.log(e);
+                this._log.info("The server closed the connection")
+            }
+            while (this._recvArray.length !== 0) {
+                await Helpers.sleep(1000);
+            }
+
+            this._recvArray.push(data);
+        }
+    }
+
+    async _initConn() {
+        if (this._codec.tag) {
+            await this.socket.write(this._codec.tag);
+        }
+    }
+
+    async _send(data) {
+        let encodedPacket = this._codec.encodePacket(data);
+        await this.socket.write(encodedPacket);
+
+    }
+
+    async _recv() {
+        return await this._codec.readPacket(this.socket);
+    }
+
+    toString() {
+        return `${this._ip}:${this._port}/${this.constructor.name.replace("Connection", "")}`
+    }
+
+}
+
+class PacketCodec {
+    constructor(connection) {
+        this._conn = connection;
+    }
+
+    encodePacket(data) {
+        throw new Error("Not Implemented")
+
+        //Override
+    }
+
+    async readPacket(reader) {
+        //override
+        throw new Error("Not Implemented")
+    }
+}
+
+module.exports = {
+    Connection,
+    PacketCodec
+};

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

@@ -0,0 +1,62 @@
+const {Connection, PacketCodec} = require("./Connection");
+const struct = require("python-struct");
+const {crc32} = require("crc");
+const {InvalidChecksumError} = require("../../errors/Common");
+const Socket = require("net").Socket;
+const Helpers = require("../../utils/Helpers");
+
+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)
+        let length = data.length + 12;
+        data = Buffer.concat([struct.pack('<ii', length, this._sendCounter), data]);
+        let crc = struct.pack('<I', crc32(data));
+        this._sendCounter += 1;
+        return Buffer.concat([data, crc]);
+    }
+
+    /**
+     *
+     * @param reader {Socket}
+     * @returns {Promise<*>}
+     */
+    async readPacket(reader) {
+        let packetLenSeq = await reader.read(8); // 4 and 4
+        //process.exit(0);
+
+        if (packetLenSeq === undefined) {
+            console.log("connection closed. exiting");
+            process.exit(0)
+            throw new Error("closed connection");
+
+        }
+
+        let res = struct.unpack("<ii", packetLenSeq);
+        let packetLen = res[0];
+        let seq = res[1];
+        let body = await reader.read(packetLen - 8);
+        let checksum = struct.unpack("<I", body.slice(-4))[0];
+        body = body.slice(0, -4);
+
+        let validChecksum = crc32(Buffer.concat([packetLenSeq, body]));
+        if (!(validChecksum === checksum)) {
+            throw new InvalidChecksumError(checksum, validChecksum);
+        }
+        return body;
+    }
+}
+
+class ConnectionTCPFull extends Connection {
+    packetCodec = FullPacketCodec;
+}
+
+module.exports = {
+    FullPacketCodec,
+    ConnectionTCPFull
+};

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


+ 0 - 0
gramjs/network/index.js


+ 5 - 5
tl/MTProtoRequest.js → gramjs/tl/MTProtoRequest.js

@@ -26,20 +26,20 @@ class MTProtoRequest {
         this.confirmReceived = true;
     }
 
-    needResend(){
+    needResend() {
         return this.dirty || (this.confirmed && !this.confirmReceived && new Date().getTime() - this.sendTime > 3000);
     }
 
     // These should be overrode
-    onSend(buffer){
-
+    onSend() {
+        throw Error("Not overload " + this.constructor.name);
     }
 
-    onResponse(buffer){
+    onResponse(buffer) {
 
     }
 
-    onException(exception){
+    onException(exception) {
 
     }
 }

+ 102 - 0
gramjs/tl/Session.js

@@ -0,0 +1,102 @@
+const Helpers = require("../utils/Helpers");
+const fs = require("fs").promises;
+const {existsSync, readFileSync} = require("fs");
+const AuthKey = require("../crypto/AuthKey");
+BigInt.toJSON = function () {
+    return {fool: this.fool.toString("hex")}
+};
+BigInt.parseJson = function () {
+    return {fool: BigInt("0x" + this.fool)}
+};
+const DEFAULT_DC_ID = 4;
+const DEFAULT_IPV4_IP = '149.154.167.92';
+const DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]';
+const DEFAULT_PORT = 443;
+
+class Session {
+    constructor(sessionUserId) {
+        this.sessionUserId = sessionUserId;
+        this.serverAddress = DEFAULT_IPV4_IP;
+        this.port = DEFAULT_PORT;
+        //this.serverAddress = "localhost";
+        //this.port = 21;
+        this.authKey = undefined;
+        this.id = Helpers.generateRandomLong(false);
+        this.sequence = 0;
+        this.salt = 0n; // Unsigned long
+        this.timeOffset = 0n;
+        this.lastMessageId = 0n;
+        this.user = undefined;
+
+
+    }
+
+    /**
+     * Saves the current session object as session_user_id.session
+     */
+    async save() {
+        if (this.sessionUserId) {
+            let str = JSON.stringify(this, function (key, value) {
+                if (typeof value === 'bigint') {
+                    return value.toString() + "n";
+                } else {
+                    return value;
+                }
+            });
+
+
+            await fs.writeFile(`${this.sessionUserId}.session`, str);
+        }
+    }
+
+    static tryLoadOrCreateNew(sessionUserId) {
+        if (sessionUserId === undefined) {
+            return new Session();
+        }
+        let filepath = `${sessionUserId}.session`;
+        if (existsSync(filepath)) {
+
+            let ob = JSON.parse(readFileSync(filepath, "utf-8"), function (key, value) {
+                if ((typeof value) == "string" && value.match(/(\d+)n/)) {
+                    return BigInt(value.slice(0, -1));
+                } else {
+                    return value;
+                }
+            });
+
+
+            let authKey = new AuthKey(Buffer.from(ob.authKey._key.data));
+            let session = new Session(ob.sessionUserId);
+            session.serverAddress = ob.serverAddress;
+            session.port = ob.port;
+            //this.serverAddress = "localhost";
+            //this.port = 21;
+            session.authKey = authKey;
+            session.id = ob.id;
+            session.sequence = ob.sequence;
+            session.salt = ob.salt; // Unsigned long
+            session.timeOffset = ob.timeOffset;
+            session.lastMessageId = ob.lastMessageId;
+            session.user = ob.user;
+            return session;
+        } else {
+            return new Session(sessionUserId);
+        }
+    }
+
+    getNewMsgId() {
+        let msTime = new Date().getTime();
+        let newMessageId = (BigInt(BigInt(Math.floor(msTime / 1000)) + this.timeOffset) << 32n) |
+            (BigInt(msTime % 1000) << 22n) |
+            (BigInt(Helpers.getRandomInt(0, 524288)) << 2n); // 2^19
+
+        if (this.lastMessageId >= newMessageId) {
+            newMessageId = this.lastMessageId + 4n;
+        }
+        this.lastMessageId = newMessageId;
+        return newMessageId;
+    }
+}
+
+module.exports = Session;
+

+ 122 - 0
gramjs/tl/TelegramClient.js

@@ -0,0 +1,122 @@
+const Session = require("./Session");
+const doAuthentication = require("../network/Authenticator");
+const MtProtoSender = require("../network/mtprotoSender");
+const MTProtoRequest = require("../tl/MTProtoRequest");
+const {ImportBotAuthorizationRequest} = require("./functions/auth");
+const {ConnectionTCPFull} = require("../network/connection/TCPFull");
+const {TLRequest} = require("./tlobject");
+const {InvokeWithLayerRequest, InitConnectionRequest} = require("./functions/index");
+const {GetConfigRequest} = require("./functions/help");
+const {LAYER} = require("../tl/alltlobjects");
+const log4js = require('log4js');
+
+class TelegramClient {
+
+    constructor(sessionUserId, apiId, apiHash, connection = ConnectionTCPFull) {
+        if (apiId === undefined || apiHash === undefined) {
+            throw Error("Your API ID or Hash are invalid. Please read \"Requirements\" on README.md");
+        }
+
+        this.apiId = apiId;
+        this.apiHash = apiHash;
+        this._connection = ConnectionTCPFull;
+        this._log = log4js.getLogger("gramjs");
+        this._initWith = (x) => {
+            return new InvokeWithLayerRequest({
+                layer: LAYER,
+                query: new InitConnectionRequest({
+                    apiId: this.apiId,
+                    deviceModel: "Windows",
+                    systemVersion: "1.8.3",
+                    appVersion: "1.8",
+                    langCode: "en",
+                    langPack: "",
+                    systemLangCode: "en",
+                    query: x,
+                    proxy: null,
+                })
+            })
+        };
+        this.session = Session.tryLoadOrCreateNew(sessionUserId);
+        //These will be set later
+        this.dcOptions = null;
+        this._sender = new MtProtoSender(this.session.authKey, {
+            logger: this._log,
+        });
+        this.phoneCodeHashes = Array();
+
+    }
+
+
+    /**
+     * Connects to the Telegram servers, executing authentication if required.
+     * Note that authenticating to the Telegram servers is not the same as authenticating
+     * the app, which requires to send a code first.
+     * @returns {Promise<void>}
+     */
+    async connect() {
+        let connection = new this._connection(this.session.serverAddress, this.session.port, this.session.dcId, this._log);
+        if (!await this._sender.connect(connection)) {
+            return;
+        }
+        this.session.authKey = this._sender.authKey;
+        await this.session.save();
+        await this._sender.send(this._initWith(
+            new GetConfigRequest()
+        ));
+
+    }
+
+
+    /**
+     * Disconnects from the Telegram server
+     * @returns {Promise<void>}
+     */
+    async disconnect() {
+        if (this._sender) {
+            await this._sender.disconnect();
+        }
+    }
+
+    /**
+     * Invokes a MTProtoRequest (sends and receives it) and returns its result
+     * @param request
+     * @returns {Promise}
+     */
+    async invoke(request) {
+        if (!(request instanceof TLRequest)) {
+            throw new Error("You can only invoke MTProtoRequests");
+        }
+        let res = await this._sender.send(request);
+        return res;
+    }
+
+
+    /**
+     * Logs in to Telegram to an existing user or bot account.
+
+     You should only use this if you are not authorized yet.
+
+     This method will send the code if it's not provided.
+
+     .. note::
+
+     In most cases, you should simply use `start()` and not this method.
+
+     * @param args {{botToken: string}}
+     * @returns {Promise<void>}
+     */
+    async signIn(args = {phone: null, code: null, password: null, botToken: null, phoneCodeHash: null}) {
+        let botToken = args.botToken;
+        let request = new ImportBotAuthorizationRequest({
+            flags: 0,
+            botAuthToken: botToken,
+            apiId: this.apiId,
+            apiHash: this.apiHash,
+        });
+        let result = await this.invoke(request);
+        return result;
+    }
+}
+
+module.exports = TelegramClient;

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

@@ -0,0 +1,46 @@
+const {TLObject} = require("../tlobject");
+const struct = require("python-struct");
+const {ungzip} = require("node-gzip");
+const {gzip} = require("node-gzip");
+
+class GZIPPacked extends TLObject {
+    static CONSTRUCTOR_ID = 0x3072cfa1;
+
+    constructor(data) {
+        super();
+        this.data = data;
+        this.CONSTRUCTOR_ID = 0x3072cfa1;
+    }
+
+    static async GZIPIfSmaller(contentRelated, data) {
+        if (contentRelated && data.length > 512) {
+            let gzipped = await (new GZIPPacked(data)).toBytes();
+            if (gzipped.length < data.length) {
+                return gzipped;
+            }
+        }
+        return data;
+    }
+
+    async toBytes() {
+        return Buffer.concat([
+            struct.pack("<I", GZIPPacked.CONSTRUCTOR_ID),
+            TLObject.serializeBytes(await gzip(this.data))
+        ])
+    }
+
+    static async read(reader) {
+        let constructor = reader.readInt(false);
+        if (constructor !== GZIPPacked.CONSTRUCTOR_ID) {
+            throw new Error("not equal");
+        }
+        return await gzip(reader.tgReadBytes());
+    }
+
+    static async fromReader(reader) {
+        return new GZIPPacked(await ungzip(reader.tgReadBytes()));
+    }
+
+}
+
+module.exports = GZIPPacked;

+ 47 - 0
gramjs/tl/core/MessageContainer.js

@@ -0,0 +1,47 @@
+const {TLObject} = require("../tlobject");
+const struct = require("python-struct");
+const TLMessage = require("./TLMessage");
+
+class MessageContainer extends TLObject {
+    static CONSTRUCTOR_ID = 0x73f1f8dc;
+
+    // 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.
+    static MAXIMUM_SIZE = 1044456 - 8;
+
+    // Maximum amount of messages that can't be sent inside a single
+    // container, inclusive. Beyond this limit Telegram will respond
+    // with BAD_MESSAGE 64 (invalid container).
+    //
+    // This limit is not 100% accurate and may in some cases be higher.
+    // However, sending up to 100 requests at once in a single container
+    // 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;
+
+    constructor(messages) {
+        super();
+        this.CONSTRUCTOR_ID = 0x73f1f8dc;
+        this.messages = messages;
+    }
+
+    static async fromReader(reader) {
+        let messages = [];
+        let length = reader.readInt();
+        for (let x = 0; x < length; x++) {
+            let msgId = reader.readLong();
+            let seqNo = reader.readInt();
+            let length = reader.readInt();
+            let before = reader.tellPosition();
+            let obj = reader.tgReadObject();
+            reader.setPosition(before + length);
+            let tlMessage = new TLMessage(msgId, seqNo, obj);
+            messages.push(tlMessage)
+        }
+        return new MessageContainer(messages);
+    }
+
+}
+
+module.exports = MessageContainer;

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

@@ -0,0 +1,38 @@
+const {TLObject} = require("../tlobject");
+const {RpcError} = require("../types");
+const GZIPPacked = require("./GZIPPacked");
+console.log(TLObject);
+console.log(RpcError);
+console.log(GZIPPacked);
+
+class RPCResult extends TLObject {
+    static CONSTRUCTOR_ID = 0xf35c6d01;
+
+    constructor(reqMsgId, body, error) {
+        super();
+        this.CONSTRUCTOR_ID = 0xf35c6d01;
+        this.reqMsgId = reqMsgId;
+        this.body = body;
+        this.error = error;
+    }
+
+    static async fromReader(reader) {
+        let msgId = reader.readLong();
+        let 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;

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

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

+ 18 - 0
gramjs/tl/core/index.js

@@ -0,0 +1,18 @@
+const TLMessage = require("./TLMessage");
+const RPCResult = require("./RPCResult");
+const MessageContainer = require("./MessageContainer");
+const GZIPPacked = require("./GZIPPacked");
+coreObjects = {
+    [RPCResult.CONSTRUCTOR_ID]: RPCResult,
+    [GZIPPacked.CONSTRUCTOR_ID]: GZIPPacked,
+    [MessageContainer.CONSTRUCTOR_ID]: MessageContainer
+};
+
+module.exports = {
+    TLMessage,
+    RPCResult,
+    MessageContainer,
+    GZIPPacked,
+    coreObjects,
+}
+

+ 8 - 0
gramjs/tl/index.js

@@ -0,0 +1,8 @@
+const types = require("./types");
+const functions = require("./functions");
+const patched = null;
+module.exports = {
+    types,
+    functions,
+    patched,
+};

+ 91 - 0
gramjs/tl/tlobject.js

@@ -0,0 +1,91 @@
+const struct = require("python-struct");
+
+class TLObject {
+    CONSTRUCTOR_ID = null;
+    SUBCLASS_OF_ID = null;
+
+    static prettyFormat() {
+        // TODO
+    }
+
+    /**
+     * Write bytes by using Telegram guidelines
+     * @param data {Buffer|string}
+     */
+    static serializeBytes(data) {
+        if (!(data instanceof Buffer)) {
+            if (typeof data == "string") {
+                data = Buffer.from(data);
+            } else {
+                throw Error(`Bytes or str expected, not ${data.constructor.name}`);
+            }
+        }
+        let r = [];
+        let padding;
+        if (data.length < 254) {
+            padding = (data.length + 1) % 4;
+            if (padding !== 0) {
+                padding = 4 - padding;
+            }
+            r.push(Buffer.from([data.length]));
+            r.push(data);
+        } else {
+            padding = data.length % 4;
+            if (padding !== 0) {
+                padding = 4 - padding;
+            }
+            r.push(Buffer.from([
+                254,
+                data.length % 256,
+                (data.length >> 8) % 256,
+                (data.length >> 16) % 256,
+
+            ]));
+            r.push(data);
+        }
+        r.push(Buffer.alloc(padding).fill(0));
+        return Buffer.concat(r);
+    }
+
+    static serializeDate(dt) {
+        if (!dt) {
+            return Buffer.alloc(4).fill(0);
+        }
+        if (dt instanceof Date) {
+            dt = Math.floor((Date.now() - dt.getTime()) / 1000);
+        }
+        if (typeof dt == "number") {
+            return struct.pack('<i', dt)
+        }
+        throw Error(`Cannot interpret "${dt}" as a date`);
+
+    }
+
+    fromReader(reader) {
+        throw Error("not implemented");
+    }
+
+}
+
+/**
+ * Represents a content-related `TLObject` (a request that can be sent).
+ */
+class TLRequest extends TLObject {
+    /**
+     *
+     * @param reader {BinaryReader}
+     * @returns {boolean}
+     */
+    readResult(reader) {
+        return reader.tgReadObject();
+    }
+
+    async resolve(self, client, utils) {
+
+    }
+}
+
+module.exports = {
+    TLObject,
+    TLRequest
+};

+ 385 - 0
gramjs/utils/Helpers.js

@@ -0,0 +1,385 @@
+const crypto = require('crypto');
+const fs = require("fs").promises;
+
+class Helpers {
+
+    static readBigIntFromBuffer(buffer, little = true, signed = false) {
+        let randBuffer = Buffer.from(buffer);
+        let bytesNumber = randBuffer.length;
+        if (little) {
+            randBuffer = randBuffer.reverse();
+        }
+        let bigInt = BigInt("0x" + randBuffer.toString("hex"));
+        if (signed && Math.floor(bigInt.toString("2").length / 8) >= bytesNumber) {
+            bigInt -= 2n ** BigInt(bytesNumber * 8);
+        }
+        return bigInt;
+    }
+
+
+    static readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) {
+        let bitLength = bigInt.toString("2").length;
+
+        let bytes = Math.ceil(bitLength / 8);
+        if (bytesNumber < bytes) {
+            throw new Error("OverflowError: int too big to convert")
+        } else if (bytesNumber > bytes) {
+
+        }
+        if (!signed && bigInt < 0) {
+            throw new Error("Cannot convert to unsigned");
+        }
+        let below = false;
+        if (bigInt < 0) {
+            below = true;
+            bigInt = -bigInt;
+        }
+
+        let hex = bigInt.toString("16").padStart(bytesNumber * 2, "0");
+        let l = Buffer.from(hex, "hex");
+        if (little) {
+            l = l.reverse();
+        }
+
+        if (signed && below) {
+            if (little) {
+                l[0] = 256 - l[0];
+                for (let i = 1; i < l.length; i++) {
+                    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 {BigInt}
+     */
+    static generateRandomLong(signed = true) {
+        return this.readBigIntFromBuffer(Helpers.generateRandomBytes(8), true, signed);
+    }
+
+
+    /**
+     * .... really javascript
+     * @param n {number}
+     * @param m {number}
+     * @returns {number}
+     */
+    static mod(n, m) {
+        return ((n % m) + m) % m;
+    }
+
+
+    /**
+     * Generates a random bytes array
+     * @param count
+     * @returns {Buffer}
+     */
+    static generateRandomBytes(count) {
+        return crypto.randomBytes(count);
+    }
+
+    /**
+     * Loads the user settings located under `api/`
+     * @param path
+     * @returns {Promise<void>}
+     */
+    static async loadSettings(path = "api/settings") {
+        let settings = {};
+        let left, right, value_pair;
+
+        let data = await fs.readFile(path, 'utf-8');
+
+        for (let line of data.toString().split('\n')) {
+            value_pair = line.split("=");
+            if (value_pair.length !== 2) {
+                break;
+            }
+            left = value_pair[0].replace(/ \r?\n|\r/g, '');
+            right = value_pair[1].replace(/ \r?\n|\r/g, '');
+            if (!isNaN(right)) {
+                settings[left] = Number.parseInt(right);
+            } else {
+                settings[left] = right;
+            }
+        }
+
+
+        return settings;
+
+
+    }
+
+    /**
+     * Calculate the key based on Telegram guidelines, specifying whether it's the client or not
+     * @param shared_key
+     * @param msg_key
+     * @param client
+     * @returns {{iv: Buffer, key: Buffer}}
+     */
+
+    static calcKey(shared_key, msg_key, client) {
+        let x = client === true ? 0 : 8;
+        let iv, key, sha1a, sha1b, sha1c, sha1d;
+        sha1a = Helpers.sha1(Buffer.concat([
+            msg_key,
+            shared_key.slice(x, (x + 32))
+        ]));
+        sha1b = Helpers.sha1(Buffer.concat([
+            shared_key.slice(x + 32, x + 48),
+            msg_key,
+            shared_key.slice(x + 48, x + 64)
+        ]));
+        sha1c = Helpers.sha1(Buffer.concat([
+            shared_key.slice(x + 64, x + 96),
+            msg_key
+        ]));
+        sha1d = Helpers.sha1(Buffer.concat([
+            msg_key,
+            shared_key.slice((x + 96), (x + 128))
+        ]));
+        key = Buffer.concat([
+            sha1a.slice(0, 8),
+            sha1b.slice(8, 20),
+            sha1c.slice(4, 16)]);
+        iv = Buffer.concat([
+            sha1a.slice(8, 20),
+            sha1b.slice(0, 8),
+            sha1c.slice(16, 20),
+            sha1d.slice(0, 8)]);
+        return {key, iv}
+
+    }
+
+    /**
+     * Calculates the message key from the given data
+     * @param data
+     * @returns {Buffer}
+     */
+    static calcMsgKey(data) {
+        return Helpers.sha1(data).slice(4, 20);
+
+
+    }
+
+    /**
+     * Generates the key data corresponding to the given nonces
+     * @param serverNonce
+     * @param newNonce
+     * @returns {{key: Buffer, iv: Buffer}}
+     */
+    static generateKeyDataFromNonce(serverNonce, newNonce) {
+        serverNonce = Helpers.readBufferFromBigInt(serverNonce, 16, true, true);
+        newNonce = Helpers.readBufferFromBigInt(newNonce, 32, true, true);
+        let hash1 = Helpers.sha1(Buffer.concat([newNonce, serverNonce]));
+        let hash2 = Helpers.sha1(Buffer.concat([serverNonce, newNonce]));
+        let hash3 = Helpers.sha1(Buffer.concat([newNonce, newNonce]));
+        let keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)]);
+        let ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]);
+        return {key: keyBuffer, iv: ivBuffer}
+    }
+
+    /**
+     * Calculates the SHA1 digest for the given data
+     * @param data
+     * @returns {Buffer}
+     */
+    static sha1(data) {
+        let shaSum = crypto.createHash('sha1');
+        shaSum.update(data);
+        return shaSum.digest();
+
+    }
+
+    /**
+     * Calculates the SHA256 digest for the given data
+     * @param data
+     * @returns {Buffer}
+     */
+    static sha256(data) {
+        let shaSum = crypto.createHash('sha256');
+        shaSum.update(data);
+        return shaSum.digest();
+
+    }
+
+
+    /**
+     * Reads a Telegram-encoded string
+     * @param buffer {Buffer}
+     * @param offset {number}
+     * @returns {{string: string, offset: number}}
+     */
+    static tgReadString(buffer, offset) {
+        let res = Helpers.tgReadByte(buffer, offset);
+        offset = res.offset;
+        let string = res.data.toString("utf8");
+        return {string, offset}
+    }
+
+    /**
+     *
+     * @param reader {Buffer}
+     * @param offset {number}
+     */
+    static tgReadObject(reader, offset) {
+        let constructorId = reader.readUInt32LE(offset);
+        offset += 4;
+        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!
+             */
+            if (constructorId === 0x997275b5) {
+                return true
+            } else if (constructorId === 0xbc799737) {
+                return false
+            }
+            throw Error("type not found " + constructorId);
+        }
+        return undefined;
+    }
+
+
+    /**
+     *
+     * @param buffer {Buffer}
+     * @param offset {Number}
+     * @returns {{data: Buffer, offset: Number}}
+     */
+    static tgReadByte(buffer, offset) {
+        let firstByte = buffer[offset];
+        offset += 1;
+        let padding, length;
+        if (firstByte === 254) {
+            length = buffer.readInt8(offset) | (buffer.readInt8(offset + 1) << 8) | (buffer.readInt8(offset + 2) << 16);
+            offset += 3;
+            padding = length % 4;
+        } else {
+            length = firstByte;
+            padding = (length + 1) % 4;
+        }
+
+        let data = buffer.slice(offset, offset + length);
+
+        offset += length;
+
+        if (padding > 0) {
+            padding = 4 - padding;
+            offset += padding;
+        }
+
+        return {data: data, offset: offset}
+    }
+
+
+    static tgWriteString(string) {
+        return Helpers.tgWriteBytes(Buffer.from(string, "utf8"));
+    }
+
+    static tgWriteBytes(data) {
+        let buffer;
+        let padding;
+
+        if (data.length < 254) {
+            padding = (data.length + 1) % 4;
+            if (padding !== 0) {
+                padding = 4 - padding;
+            }
+            buffer = Buffer.concat([Buffer.from([data.length]), data]);
+        } else {
+            padding = data.length % 4;
+            if (padding !== 0) {
+                padding = 4 - padding;
+            }
+            buffer = Buffer.concat([
+                Buffer.from([254]),
+                Buffer.from([data.length % 256]),
+                Buffer.from([(data.length >> 8) % 256]),
+                Buffer.from([(data.length >> 16) % 256]),
+                data,
+            ]);
+
+        }
+
+        return Buffer.concat([buffer, Buffer.alloc(padding).fill(0)]);
+
+
+    }
+
+    /**
+     * Fast mod pow for RSA calculation. a^b % n
+     * @param a
+     * @param b
+     * @param n
+     * @returns {bigint}
+     */
+    static modExp(a, b, n) {
+        a = a % n;
+        let result = 1n;
+        let x = a;
+        while (b > 0n) {
+            let leastSignificantBit = b % 2n;
+            b = b / 2n;
+            if (leastSignificantBit === 1n) {
+                result = result * x;
+                result = result % n;
+            }
+            x = x * x;
+            x = x % n;
+        }
+        return result;
+    };
+
+    /**
+     * returns a random int from min (inclusive) and max (inclusive)
+     * @param min
+     * @param max
+     * @returns {number}
+     */
+    static 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}
+     */
+    static sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
+
+    /**
+     * Checks if the obj is an array
+     * @param obj
+     * @returns {boolean}
+     */
+    static isArrayLike(obj) {
+        if (!obj) return false;
+        let 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;
+    }
+
+}
+
+module.exports = Helpers;
+

+ 14 - 2
gramjs_generator/generators/errors.js

@@ -56,7 +56,7 @@ const generateErrors = (errors, f) => {
             f.write(`super('${capture}'`);
         }
 
-        f.write(' + this._fmtRequest(args.request));\n');
+        f.write(' + RPCError._fmtRequest(args.request));\n');
 
         if (error.hasCaptures) {
             f.write(
@@ -67,7 +67,7 @@ const generateErrors = (errors, f) => {
         f.write('    }\n}\n');
     }
 
-    f.write('\n\nconst rpcErrorsDict = {\n');
+    f.write('\n\nconst rpcErrorsObject = {\n');
 
     for (const error of exactMatch) {
         f.write(`    ${error.pattern}: ${error.name},\n`);
@@ -80,6 +80,18 @@ const generateErrors = (errors, f) => {
     }
 
     f.write('];');
+    f.write("module.exports = {");
+    for (const error of regexMatch) {
+        f.write(`     ${error.name},\n`);
+    }
+    for (const error of exactMatch) {
+        f.write(`     ${error.name},\n`);
+    }
+    f.write("     rpcErrorsObject,\n");
+    f.write("     rpcErrorRe,\n");
+
+    f.write("}");
+
 };
 
 module.exports = {

+ 3 - 3
gramjs_generator/generators/index.js

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

+ 300 - 97
gramjs_generator/generators/tlobject.js

@@ -1,7 +1,8 @@
 const fs = require('fs');
 const util = require('util');
-const { crc32 } = require('crc');
+const {crc32} = require('crc');
 const SourceBuilder = require('../sourcebuilder');
+const {snakeToCamelCase, variableSnakeToCamelCase} = require("../utils");
 
 const AUTO_GEN_NOTICE =
     "/*! File generated by TLObjects' generator. All changes will be ERASED !*/";
@@ -46,11 +47,15 @@ const BASE_TYPES = [
 ];
 
 // Patched types {fullname: custom.ns.Name}
-const PATCHED_TYPES = {
+
+//No patches currently
+/**
+ const PATCHED_TYPES = {
     messageEmpty: 'message.Message',
     message: 'message.Message',
     messageService: 'message.Message',
-};
+};*/
+const PATCHED_TYPES = {};
 
 const writeModules = (
     outDir,
@@ -60,7 +65,7 @@ const writeModules = (
     typeConstructors
 ) => {
     // namespace_tlobjects: {'namespace', [TLObject]}
-    fs.mkdirSync(outDir, { recursive: true });
+    fs.mkdirSync(outDir, {recursive: true});
 
     for (const [ns, tlobjects] of Object.entries(namespaceTlobjects)) {
         const file = `${outDir}/${ns === 'null' ? 'index' : ns}.js`;
@@ -91,10 +96,11 @@ const writeModules = (
 
         // Import struct for the .__bytes__(self) serialization
         builder.writeln("const struct = require('python-struct');");
+        builder.writeln(`const Helpers = require('../../utils/Helpers');`);
 
         const typeNames = new Set();
         const typeDefs = [];
-
+        /*
         // Find all the types in this file and generate type definitions
         // based on the types. The type definitions are written to the
         // file at the end.
@@ -127,7 +133,7 @@ const writeModules = (
                     );
                 }
             }
-        }
+        }*/
 
         const imports = {};
         const primitives = new Set([
@@ -176,7 +182,8 @@ const writeModules = (
         }
 
         // Add imports required for type checking
-        if (imports) {
+        /**
+         if (imports) {
             builder.writeln('if (false) { // TYPE_CHECKING {');
 
             for (const [namespace, names] of Object.entries(imports)) {
@@ -188,7 +195,7 @@ const writeModules = (
             }
 
             builder.endBlock();
-        }
+        }*/
 
         // Generate the class for every TLObject
         for (const t of tlobjects) {
@@ -206,9 +213,55 @@ const writeModules = (
         for (const line of typeDefs) {
             builder.writeln(line);
         }
-
         writeModuleExports(tlobjects, builder);
+        if (file.indexOf("index.js") > 0) {
+            for (const [ns, tlobjects] of Object.entries(namespaceTlobjects)) {
+                if (ns !== 'null') {
+                    builder.writeln("let %s = require('./%s');", ns, ns);
+                }
+            }
+            for (const [ns, tlobjects] of Object.entries(namespaceTlobjects)) {
+                if (ns !== 'null') {
+                    builder.writeln("module.exports.%s = %s;", ns, ns);
+                }
+            }
+
+        }
+
+    }
+};
+
+const writeReadResult = (tlobject, builder) => {
+    // Only requests can have a different response that's not their
+    // serialized body, that is, we'll be setting their .result.
+    //
+    // The default behaviour is reading a TLObject too, so no need
+    // to override it unless necessary.
+    if (!tlobject.isFunction)
+        return;
+
+    // https://core.telegram.org/mtproto/serialize#boxed-and-bare-types
+    // TL;DR; boxed types start with uppercase always, so we can use
+    // this to check whether everything in it is boxed or not.
+    //
+    // Currently only un-boxed responses are Vector<int>/Vector<long>.
+    // If this weren't the case, we should check upper case after
+    // max(index('<'), index('.')) (and if it is, it's boxed, so return).
+    let m = tlobject.result.match(/Vector<(int|long)>/);
+    if (!m) {
+        return
     }
+    //builder.endBlock();
+    builder.writeln('readResult(reader){');
+    builder.writeln('reader.readInt();  // Vector ID');
+    builder.writeln('let temp = [];');
+    builder.writeln("let len = reader.readInt(); //fix this");
+    builder.writeln('for (let i=0;i<len;i++){');
+    let read = m[1][0].toUpperCase() + m[1].slice(1);
+    builder.writeln('temp.push(reader.read%s())', read);
+    builder.endBlock();
+    builder.writeln('return temp');
+    builder.endBlock();
 };
 
 /**
@@ -222,14 +275,17 @@ const writeModules = (
 const writeSourceCode = (tlobject, kind, builder, typeConstructors) => {
     writeClassConstructor(tlobject, kind, typeConstructors, builder);
     writeResolve(tlobject, builder);
-    writeToJson(tlobject, builder);
+    //writeToJson(tlobject, builder);
     writeToBytes(tlobject, builder);
     builder.currentIndent--;
+    writeFromReader(tlobject, builder);
+    writeReadResult(tlobject, builder);
+    builder.currentIndent--;
     builder.writeln('}');
-    // writeFromReader(tlobject, builder);
-    // writeReadResult(tlobject, builder);
+
 };
 
+
 const writeClassConstructor = (tlobject, kind, typeConstructors, builder) => {
     builder.writeln();
     builder.writeln();
@@ -248,6 +304,14 @@ const writeClassConstructor = (tlobject, kind, typeConstructors, builder) => {
         return;
     }
 
+    // Note : this is needed to be able to access them
+    // with or without an instance
+    builder.writeln(
+        `static CONSTRUCTOR_ID = 0x${tlobject.id.toString(16).padStart(8, '0')};`
+    );
+    builder.writeln(`static SUBCLASS_OF_ID = 0x${crc32(tlobject.result).toString("16")};`);
+    builder.writeln();
+
     builder.writeln('/**');
 
     if (tlobject.isFunction) {
@@ -278,22 +342,22 @@ const writeClassConstructor = (tlobject, kind, typeConstructors, builder) => {
     builder.writeln(
         `this.CONSTRUCTOR_ID = 0x${tlobject.id.toString(16).padStart(8, '0')};`
     );
-    builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(tlobject.result)};`);
+    builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(tlobject.result).toString("16")};`);
     builder.writeln();
 
     // Set the arguments
     for (const arg of tlobject.realArgs) {
         if (!arg.canBeInferred) {
-            builder.writeln(`this.${arg.name} = args.${arg.name};`);
+            builder.writeln(`this.${variableSnakeToCamelCase(arg.name)} = args.${variableSnakeToCamelCase(arg.name)};`);
         }
 
-        // Currently the only argument that can be
+            // Currently the only argument that can be
         // inferred are those called 'random_id'
         else if (arg.name === 'random_id') {
             // Endianness doesn't really matter, and 'big' is shorter
-            let code = `int.from_bytes(os.urandom(${
+            let code = `Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(${
                 arg.type === 'long' ? 8 : 4
-            }), 'big', signed=True)`;
+            }),false,true)`;
 
             if (arg.isVector) {
                 // Currently for the case of "messages.forwardMessages"
@@ -308,7 +372,7 @@ const writeClassConstructor = (tlobject, kind, typeConstructors, builder) => {
             }
 
             builder.writeln(
-                `this.random_id = random_id !== null ? random_id : ${code}`
+                `this.randomId = args.randomId !== undefined ? args.randomId : ${code};`
             );
         } else {
             throw new Error(`Cannot infer a value for ${arg}`);
@@ -367,8 +431,8 @@ const writeResolve = (tlobject, builder) => {
         builder.endBlock();
     }
 };
-
-const writeToJson = (tlobject, builder) => {
+/**
+ const writeToJson = (tlobject, builder) => {
     builder.writeln('toJson() {');
     builder.writeln('return {');
 
@@ -411,83 +475,92 @@ const writeToJson = (tlobject, builder) => {
     builder.currentIndent--;
     builder.writeln('}');
 };
-
+ */
 const writeToBytes = (tlobject, builder) => {
     builder.writeln('get bytes() {');
 
     // Some objects require more than one flag parameter to be set
     // at the same time. In this case, add an assertion.
     const repeatedArgs = {};
-
-    for (const arg of tlobject.args) {
+    for (let arg of tlobject.args) {
         if (arg.isFlag) {
             if (!repeatedArgs[arg.flagIndex]) {
                 repeatedArgs[arg.flagIndex] = [];
             }
-
             repeatedArgs[arg.flagIndex].push(arg);
         }
     }
-
-    for (const ra of Object.values(repeatedArgs)) {
+    for (let ra of Object.values(repeatedArgs)) {
         if (ra.length > 1) {
-            const cnd1 = ra.map(
-                a => `(this.${a.name} || this.${a.name} !== null)`
-            );
-            const cnd2 = ra.map(
-                a => `(this.${a.name} === null || this.${a.name} === false)`
-            );
-
-            builder.writeln(
-                'if (!(%s || %s)) {',
-                cnd1.join(' && '),
-                cnd2.join(' && ')
-            );
-
-            builder.writeln(
-                "throw new Error('%s parameters must all be false-y (like null) or all me true-y');",
-                ra.map(a => a.name).join(', ')
-            );
-
-            builder.endBlock();
+            let cnd1 = [];
+            let cnd2 = [];
+            let names = [];
+
+            for (let a of ra) {
+                cnd1.push(`this.${a.name} || this.${a.name}!==null`);
+                cnd2.push(`this.${a.name}===null || this.${a.name}===false`);
+                names.push(a.name);
+            }
+            builder.writeln("if (!((%s) && (%s)))\n\t throw new Error('%s paramaters must all"
+                + " be false-y or all true')", cnd1.join(" && "), cnd2.join(" && "), names.join(", "));
         }
     }
-
-    const bytes = Buffer.from(
-        parseInt(tlobject.id)
-            .toString(16)
-            .padStart(8, `0`),
-        `hex`
-    )
-        .readUInt32LE()
-        .toString(16)
-        .padStart(8, `0`);
-
-    builder.writeln('return parseInt([');
+    builder.writeln("return Buffer.concat([");
     builder.currentIndent++;
-    builder.writeln("'%s',", bytes);
-
-    for (const arg of tlobject.args) {
+    let b = Buffer.alloc(4);
+    b.writeUInt32LE(tlobject.id, 0);
+    // First constructor code, we already know its bytes
+    builder.writeln('Buffer.from("%s","hex"),', b.toString("hex"));
+    for (let arg of tlobject.args) {
         if (writeArgToBytes(builder, arg, tlobject.args)) {
             builder.writeln(',');
         }
     }
-
-    builder.currentIndent--;
-    builder.writeln(']);');
+    builder.writeln("])");
     builder.endBlock();
+
 };
 
 // writeFromReader
+const writeFromReader = (tlobject, builder) => {
+
+    builder.writeln("static fromReader(reader) {");
+    for (const arg of tlobject.args) {
+
+        if (arg.name !== "flag") {
+
+
+            if (arg.name !== "x") {
+
+
+                builder.writeln("let %s", "_" + arg.name + ";");
+            }
+        }
+    }
+    // TODO fix this really
+    builder.writeln("let _x;");
+    builder.writeln("let len;");
+
+
+    for (const arg of tlobject.args) {
+        writeArgReadCode(builder, arg, tlobject.args, "_" + arg.name);
+    }
+    let temp = [];
+    for (let a of tlobject.realArgs) {
+        temp.push(`${variableSnakeToCamelCase(a.name)}:_${a.name}`)
+    }
+    builder.writeln("return new this({%s})", temp.join(",\n\t"));
+    builder.endBlock();
+};
 // writeReadResult
 
 /**
  * Writes the .__bytes__() code for the given argument
- * :param builder: The source code builder
- * :param arg: The argument to write
- * :param args: All the other arguments in TLObject same __bytes__.
+ * @param builder: The source code builder
+ * @param arg: The argument to write
+ * @param args: All the other arguments in TLObject same __bytes__.
  *              This is required to determine the flags value
- * :param name: The name of the argument. Defaults to "self.argname"
+ * @param name: The name of the argument. Defaults to "self.argname"
  *              This argument is an option because it's required when
  *              writing Vectors<>
  */
@@ -495,11 +568,13 @@ const writeArgToBytes = (builder, arg, args, name = null) => {
     if (arg.genericDefinition) {
         return; // Do nothing, this only specifies a later type
     }
-
     if (name === null) {
         name = `this.${arg.name}`;
     }
-
+    if (name =="this.msg_ids"){
+        console.log(name)
+    }
+    name = variableSnakeToCamelCase(name);
     // The argument may be a flag, only write if it's not None AND
     // if it's not a True type.
     // True types are not actually sent, but instead only used to
@@ -512,18 +587,19 @@ const writeArgToBytes = (builder, arg, args, name = null) => {
             // so we need an extra join here. Note that empty vector flags
             // should NOT be sent either!
             builder.write(
-                "%s === null || %s === false ? b'' : b''.join([",
+                "(%s === undefined || %s === false || %s ===null) ? Buffer.alloc(0) :Buffer.concat([",
+                name,
                 name,
                 name
             );
         } else {
-            builder.write("%s === null || %s === false ? b'' : [", name, name);
+            builder.write("(%s === undefined || %s === false || %s ===null) ? Buffer.alloc(0) : [", name, name,name);
         }
     }
 
     if (arg.isVector) {
         if (arg.useVectorId) {
-            builder.write("'15c4b51c',");
+            builder.write("Buffer.from('15c4b51c','hex'),");
         }
 
         builder.write("struct.pack('<i', %s.length),", name);
@@ -531,7 +607,6 @@ const writeArgToBytes = (builder, arg, args, name = null) => {
         // Cannot unpack the values for the outer tuple through *[(
         // since that's a Python >3.5 feature, so add another join.
         builder.write('Buffer.concat(%s.map(x => ', name);
-
         // Temporary disable .is_vector, not to enter this if again
         // Also disable .is_flag since it's not needed per element
         const oldFlag = arg.isFlag;
@@ -540,7 +615,7 @@ const writeArgToBytes = (builder, arg, args, name = null) => {
         arg.isVector = true;
         arg.isFlag = oldFlag;
 
-        builder.write('))', name);
+        builder.write('))');
     } else if (arg.flagIndicator) {
         // Calculate the flags with those items which are not None
         if (!args.some(f => f.isFlag)) {
@@ -553,9 +628,9 @@ const writeArgToBytes = (builder, arg, args, name = null) => {
                     .filter(flag => flag.isFlag)
                     .map(
                         flag =>
-                            `(this.${flag.name} === null || this.${
-                                flag.name
-                            } === false ? 0 : ${1 << flag.flagIndex})`
+                            `(this.${variableSnakeToCamelCase(flag.name)} === undefined || this.${
+                                variableSnakeToCamelCase(flag.name)
+                            } === false || this.${variableSnakeToCamelCase(flag.name)} === null) ? 0 : ${1 << flag.flagIndex}`
                     )
                     .join(' | ')
             );
@@ -564,26 +639,26 @@ const writeArgToBytes = (builder, arg, args, name = null) => {
     } else if (arg.type === 'int') {
         builder.write("struct.pack('<i', %s)", name);
     } else if (arg.type === 'long') {
-        builder.write("struct.pack('<q', %s)", name);
+        builder.write("Helpers.readBufferFromBigInt(%s,8,true,true)", name);
     } else if (arg.type === 'int128') {
-        builder.write("%s.to_bytes(16, 'little', signed=True)", name);
+        builder.write("Helpers.readBufferFromBigInt(%s,16,true,true)", name);
     } else if (arg.type === 'int256') {
-        builder.write("%s.to_bytes(32, 'little', signed=True)", name);
+        builder.write("Helpers.readBufferFromBigInt(%s,32,true,true)", name);
     } else if (arg.type === 'double') {
-        builder.write("struct.pack('<d', %s)", name);
+        builder.write("struct.pack('<d', %s.toString())", name);
     } else if (arg.type === 'string') {
-        builder.write('this.serializeBytes(%s)', name);
+        builder.write('TLObject.serializeBytes(%s)', name);
     } else if (arg.type === 'Bool') {
         builder.write('%s ? 0xb5757299 : 0x379779bc', name);
     } else if (arg.type === 'true') {
         // These are actually NOT written! Only used for flags
     } else if (arg.type === 'bytes') {
-        builder.write('this.serializeBytes(%s)', name);
+        builder.write('TLObject.serializeBytes(%s)', name);
     } else if (arg.type === 'date') {
-        builder.write('this.serializeDatetime(%s)', name);
+        builder.write('TLObject.serializeDatetime(%s)', name);
     } else {
         // Else it may be a custom type
-        builder.write('bytes(%s)', name);
+        builder.write('%s.bytes', name);
 
         // If the type is not boxed (i.e. starts with lowercase) we should
         // not serialize the constructor ID (so remove its first 4 bytes).
@@ -599,15 +674,133 @@ const writeArgToBytes = (builder, arg, args, name = null) => {
         builder.write(']');
 
         if (arg.isVector) {
-            builder.write(']');
+            builder.write(')');
         }
     }
 
     return true;
 };
 
+
+/**
+ * Writes the read code for the given argument, setting the
+ * arg.name variable to its read value.
+ * @param builder The source code builder
+ * @param arg The argument to write
+ * @param args All the other arguments in TLObject same on_send.
+ * This is required to determine the flags value
+ * @param name The name of the argument. Defaults to "self.argname"
+ * This argument is an option because it's required when
+ * writing Vectors<>
+ */
+const writeArgReadCode = (builder, arg, args, name) => {
+    if (arg.genericDefinition) {
+        return // Do nothing, this only specifies a later type
+    }
+    //The argument may be a flag, only write that flag was given!
+    let wasFlag = false;
+    if (arg.isFlag) {
+        // Treat 'true' flags as a special case, since they're true if
+        // they're set, and nothing else needs to actually be read.
+        if (arg.type === "true") {
+            builder.writeln("%s = Boolean(flags & %s);", name, 1 << arg.flagIndex);
+            return;
+        }
+
+        wasFlag = true;
+        builder.writeln("if (flags & %s) {", 1 << arg.flagIndex);
+        // Temporary disable .is_flag not to enter this if
+        // again when calling the method recursively
+        arg.isFlag = false;
+    }
+
+    if (arg.isVector) {
+        if (arg.useVectorId) {
+            // We have to read the vector's constructor ID
+            builder.writeln("reader.readInt();");
+        }
+        builder.writeln("%s = [];", name);
+        builder.writeln("len = reader.readInt();");
+        builder.writeln('for (let i=0;i<len;i++){');
+
+        // Temporary disable .is_vector, not to enter this if again
+        arg.isVector = false;
+        writeArgReadCode(builder, arg, args, "_x");
+        builder.writeln("%s.push(_x);", name);
+        arg.isVector = true;
+
+    } else if (arg.flagIndicator) {
+        //Read the flags, which will indicate what items we should read next
+        builder.writeln("let flags = reader.readInt();");
+        builder.writeln();
+    } else if (arg.type === "int") {
+        builder.writeln("%s = reader.readInt();", name)
+    } else if (arg.type === "long") {
+        builder.writeln("%s = reader.readLong();", name);
+    } else if (arg.type === "int128") {
+        builder.writeln('%s = reader.readLargeInt(128);', name);
+    } else if (arg.type === "int256") {
+        builder.writeln('%s = reader.readLargeInt(256);', name);
+    } else if (arg.type === "double") {
+        builder.writeln('%s = reader.readDouble();', name);
+    } else if (arg.type === "string") {
+        builder.writeln('%s = reader.tgReadString();', name);
+    } else if (arg.type === "Bool") {
+        builder.writeln('%s = reader.tgReadBool();', name);
+    } else if (arg.type === "true") {
+        builder.writeln('%s = true;', name);
+    } else if (arg.type === "bytes") {
+        builder.writeln('%s = reader.tgReadBytes();', name);
+    } else if (arg.type === "date") {
+        builder.writeln('%s = reader.tgReadDate();', name);
+    } else {
+        // Else it may be a custom type
+        if (!arg.skipConstructorId) {
+            builder.writeln('%s = reader.tgReadObject();', name);
+        } else {
+            // Import the correct type inline to avoid cyclic imports.
+            // There may be better solutions so that we can just access
+            // all the types before the files have been parsed, but I
+            // don't know of any.
+            let sepIndex = arg.type.indexOf(".");
+            let ns, t;
+            if (sepIndex === -1) {
+                ns = ".";
+                t = arg.type;
+            } else {
+                ns = "." + arg.type.slice(0, sepIndex);
+                t = arg.type.slice(sepIndex + 1);
+            }
+            let className = snakeToCamelCase(t);
+
+            // There would be no need to import the type if we're in the
+            // file with the same namespace, but since it does no harm
+            // and we don't have information about such thing in the
+            // method we just ignore that case.
+            builder.writeln('let %s = require("%s");', className, ns);
+            builder.writeln("%s = %s.fromReader(reader);", name, className);
+        }
+    }
+
+    // End vector and flag blocks if required (if we opened them before)
+    if (arg.isVector) {
+        builder.writeln("}");
+
+    }
+    if (wasFlag) {
+        builder.endBlock();
+        builder.writeln("else {");
+        builder.writeln("%s = null", name);
+        builder.endBlock();
+        // Restore .isFlag;
+        arg.isFlag = true;
+    }
+};
+
+
 const writePatched = (outDir, namespaceTlobjects) => {
-    fs.mkdirSync(outDir, { recursive: true });
+
+    fs.mkdirSync(outDir, {recursive: true});
 
     for (const [ns, tlobjects] of Object.entries(namespaceTlobjects)) {
         const file = `${outDir}/${ns === 'null' ? 'index' : ns}.js`;
@@ -617,6 +810,8 @@ const writePatched = (outDir, namespaceTlobjects) => {
         builder.writeln(AUTO_GEN_NOTICE);
         builder.writeln("const struct = require('python-struct');");
         builder.writeln(`const { TLObject, types, custom } = require('..');`);
+        builder.writeln(`const Helpers = require('../../utils/Helpers');`);
+
         builder.writeln();
 
         for (const t of tlobjects) {
@@ -625,18 +820,23 @@ const writePatched = (outDir, namespaceTlobjects) => {
                 t.className,
                 PATCHED_TYPES[t.fullname]
             );
-
+            builder.writeln(`static CONSTRUCTOR_ID = 0x${t.id.toString(16)}`);
+            builder.writeln(`static SUBCLASS_OF_ID = 0x${crc32(t.result).toString("16")}`);
+            builder.writeln();
             builder.writeln('constructor() {');
             builder.writeln('super();');
             builder.writeln(`this.CONSTRUCTOR_ID = 0x${t.id.toString(16)}`);
-            builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(t.result)}`);
+            builder.writeln(`this.SUBCLASS_OF_ID = 0x${crc32(t.result).toString("16")}`);
+
             builder.endBlock();
 
-            writeToJson(t, builder);
+            //writeToJson(t, builder);
             writeToBytes(t, builder);
-            // writeFromReader(t, builder);
+            writeFromReader(t, builder);
 
             builder.writeln();
+            builder.endBlock();
+            builder.currentIndent = 0;
             builder.writeln(
                 'types.%s%s = %s',
                 t.namespace ? `${t.namespace}.` : '',
@@ -648,7 +848,7 @@ const writePatched = (outDir, namespaceTlobjects) => {
     }
 };
 
-const writeAllTlobjects = (tlobjects, layer, builder) => {
+const writeAllTLObjects = (tlobjects, layer, builder) => {
     builder.writeln(AUTO_GEN_NOTICE);
     builder.writeln();
 
@@ -687,11 +887,13 @@ const writeAllTlobjects = (tlobjects, layer, builder) => {
     builder.endBlock(true);
 };
 
-const generateTlobjects = (tlobjects, layer, importDepth, outputDir) => {
+const generateTLObjects = (tlobjects, layer, importDepth, outputDir) => {
+    // Group everything by {namespace :[tlobjects]} to generate index.js
     const namespaceFunctions = {};
     const namespaceTypes = {};
     const namespacePatched = {};
 
+    // Group {type: [constructors]} to generate the documentation
     const typeConstructors = {};
 
     for (const tlobject of tlobjects) {
@@ -743,10 +945,10 @@ const generateTlobjects = (tlobjects, layer, importDepth, outputDir) => {
     const stream = fs.createWriteStream(filename);
     const builder = new SourceBuilder(stream);
 
-    writeAllTlobjects(tlobjects, layer, builder);
+    writeAllTLObjects(tlobjects, layer, builder);
 };
 
-const cleanTlobjects = outputDir => {
+const cleanTLObjects = outputDir => {
     for (let d in ['functions', 'types', 'patched']) {
         d = `${outputDir}/d`;
 
@@ -763,6 +965,7 @@ const cleanTlobjects = outputDir => {
 };
 
 const writeModuleExports = (tlobjects, builder) => {
+
     builder.writeln('module.exports = {');
 
     for (const t of tlobjects) {
@@ -774,6 +977,6 @@ const writeModuleExports = (tlobjects, builder) => {
 };
 
 module.exports = {
-    generateTlobjects,
-    cleanTlobjects,
+    generateTLObjects,
+    cleanTLObjects,
 };

+ 1 - 1
gramjs_generator/sourcebuilder.js

@@ -19,7 +19,7 @@ class SourceBuilder {
      * by the current indentation level
      */
     indent() {
-        this.write(' '.repeat(this.currentIndent * this.indentSize));
+        this.write(' '.repeat(Math.abs(this.currentIndent * this.indentSize)));
     }
 
     /**

+ 7 - 0
gramjs_generator/utils.js

@@ -2,7 +2,14 @@ const snakeToCamelCase = (name, suffix) => {
     const result = name.replace(/(?:^|_)([a-z])/g, (_, g) => g.toUpperCase());
     return result.replace(/_/g, '') + (suffix || '');
 };
+const variableSnakeToCamelCase = (str) => str.replace(
+    /([-_][a-z])/g,
+    (group) => group.toUpperCase()
+        .replace('-', '')
+        .replace('_', '')
+);
 
 module.exports = {
     snakeToCamelCase,
+    variableSnakeToCamelCase,
 };

+ 4 - 4
index.js

@@ -51,9 +51,9 @@ const generate = (which, action = 'gen') => {
 
     const {
         generateErrors,
-        generateTlobjects,
+        generateTLObjects,
         generateDocs,
-        cleanTlobjects,
+        cleanTLObjects,
     } = require('./gramjs_generator/generators');
 
     const [layer] = TLOBJECT_IN_TLS.map(findLayer).filter(Boolean);
@@ -96,9 +96,9 @@ const generate = (which, action = 'gen') => {
         console.log(action, 'TLObjects...');
 
         if (clean) {
-            cleanTlobjects(TLOBJECT_OUT);
+            cleanTLObjects(TLOBJECT_OUT);
         } else {
-            generateTlobjects(tlobjects, layer, IMPORT_DEPTH, TLOBJECT_OUT);
+            generateTLObjects(tlobjects, layer, IMPORT_DEPTH, TLOBJECT_OUT);
         }
     }
 

+ 54 - 6
main.js

@@ -1,10 +1,58 @@
-const {Helpers} = require("./utils/Helpers");
-const {TelegramClient} = require("./tl/TelegramClient");
+const Helpers = require("./gramjs/utils/Helpers");
+const TelegramClient = require("./gramjs/tl/TelegramClient");
+const {GetConfigRequest} = require("./gramjs/tl/functions/help");
+const struct = require("python-struct");
+const log4js = require('log4js');
+const {InputPeerChannel} = require("./gramjs/tl/types");
+const {SendMessageRequest} = require("./gramjs/tl/functions/messages");
+const {InputPeerUser} = require("./gramjs/tl/types");
+const {ResolveUsernameRequest} = require("./gramjs/tl/functions/contacts");
+const logger = log4js.getLogger("gramjs");
+
+logger.level = 'debug';
+
+let painorId = 400319287;
+let painorHash = 4770003194588524965n;
+
+let input_peer = new InputPeerChannel({
+    channelId: 1180212174,
+    accessHash: 548480552819456668n,
+});
+let message = new SendMessageRequest({
+    peer: input_peer,
+    message: "hi",
+    randomId: 5,
+});
+console.log(message.bytes.toString("hex"));
 
 (async function () {
+
+
     console.log("Loading interactive example...");
-    let settings = Helpers.loadSettings();
-    let client = TelegramClient(settings["session_name"], 105, settings["api_id"], settings["api_hash"]);
+    let sessionName = "anon";
+    let apiId = ;
+    let apiHash = "";
+    let client = new TelegramClient(sessionName, apiId, apiHash);
     await client.connect();
-    console.log("You should now be connected.");
-})();
+    //let request = new GetConfigRequest();
+    //let res =         await client._sender.send(new GetConfigRequest());
+    //console.log(res)
+    let res = await client.signIn({botToken: ""});
+    let user = res.user;
+    client._authorized = true;
+    let result = await client.invoke(new ResolveUsernameRequest({
+            username: 'gramjschat'
+        }
+    ));
+    console.log(result);
+
+    let message = new SendMessageRequest({
+        peer: input_peer,
+        message: "hi from GramJS",
+    });
+    console.log(message);
+    let r = await client.invoke(message);
+    console.log(r);
+    console.log("You should now be connected.", user);
+})();
+

+ 0 - 250
network/Authenticator.js

@@ -1,250 +0,0 @@
-const AES = require("../crypto/AES");
-const AuthKey = require("../crypto/AuthKey");
-const Factorizator = require("../crypto/Factorizator");
-const RSA = require("../crypto/RSA");
-const MtProtoPlainSender = require("./MTProtoPlainSender");
-const Helpers = require("../utils/Helpers");
-
-function doAuthentication(transport) {
-    let sender = MtProtoPlainSender(transport);
-
-    // Step 1 sending: PQ request
-    let nonce = Helpers.generateRandomBytes(16);
-    let buffer = Buffer.alloc(32);
-    buffer.writeUInt32LE(0x60469778, 0);
-    buffer = Buffer.concat([buffer, nonce]);
-    sender.send(buffer);
-
-    // Step 1 response: PQ request
-    let pq = null;
-    let serverNonce = null;
-    let fingerprints = Array();
-    buffer = sender.receive();
-    let responseCode = buffer.readUInt32LE(0);
-    if (responseCode !== 0x05162463) {
-        throw Error("invalid response code");
-    }
-    let nonceFromServer = buffer.read(16, 8);
-    if (nonce !== nonceFromServer) {
-        throw Error("Invalid nonce from server");
-    }
-    serverNonce = buffer.read(16, 12);
-
-    let {pqBytes, newOffset} = Helpers.tgReadByte(buffer, 12);
-    pq = buffer.readBigInt64BE(newOffset);
-    newOffset += 8;
-    let vectorId = buffer.readInt8(newOffset);
-    newOffset += 1;
-    if (vectorId !== 0x1cb5c415) {
-        throw Error("vector error");
-    }
-    let fingerprints_count = buffer.readInt8(newOffset);
-    for (let i = 0; i < fingerprints_count; i++) {
-        fingerprints.push(buffer.readInt32LE(newOffset));
-        newOffset += 8;
-    }
-
-    // Step 2 sending: DH Exchange
-    let newNonce = Helpers.generateRandomBytes(32);
-    let {p, q} = Factorizator.factorize(pq);
-    let tempBuffer = Buffer.alloc(8);
-    tempBuffer.writeUIntLE(0x83c95aec, 0, 8);
-    let pqInnerData = Buffer.concat([
-        tempBuffer,
-        Helpers.tgWriteBytes(getByteArray(pq, false)),
-        Helpers.tgWriteBytes(getByteArray(Math.min(p, q), false)),
-        Helpers.tgWriteBytes(getByteArray(Math.max(p, q), false)),
-        nonce,
-        serverNonce,
-        newNonce,
-    ]);
-    let cipherText, targetFingerprint;
-    for (let fingerprint of fingerprints) {
-        cipherText = RSA.encrypt(getFingerprintText(fingerprint), pqInnerData);
-        if (cipherText !== undefined) {
-            targetFingerprint = fingerprint;
-            break;
-        }
-    }
-    if (cipherText === undefined) {
-        throw Error("Could not find a valid key for fingerprints");
-    }
-    tempBuffer = Buffer.alloc(8);
-    tempBuffer.writeUIntLE(0xd712e4be, 0, 8);
-
-    let reqDhParams = Buffer.concat([
-        tempBuffer,
-        nonce,
-        serverNonce,
-        Helpers.tgWriteBytes(getByteArray(Math.min(p, q), false)),
-        Helpers.tgWriteBytes(getByteArray(Math.max(p, q), false)),
-        targetFingerprint,
-        Helpers.tgWriteBytes(cipherText)
-    ]);
-    sender.send(reqDhParams);
-    // Step 2 response: DH Exchange
-    newOffset = 0;
-    let reader = sender.receive();
-    responseCode = reader.readInt32LE(newOffset);
-    newOffset += 4;
-    if (responseCode === 0x79cb045d) {
-        throw Error("Server DH params fail: TODO ");
-    }
-    if (responseCode !== 0xd0e8075c) {
-        throw Error("Invalid response code: TODO ");
-    }
-    nonceFromServer = reader.readIntLE(newOffset, 16);
-    newOffset += 16;
-    if (nonceFromServer !== nonce) {
-        throw Error("Invalid nonce from server");
-    }
-    let serverNonceFromServer = reader.readIntLE(newOffset, 16);
-    if (serverNonceFromServer !== nonceFromServer) {
-        throw Error("Invalid server nonce from server");
-    }
-    newOffset += 16;
-    let encryptedAnswer = Helpers.tgReadByte(reader, newOffset).data;
-
-    // Step 3 sending: Complete DH Exchange
-
-    let {key, iv} = Helpers.generateKeyDataFromNonces(serverNonce, newNonce);
-    let plainTextAnswer = AES.decryptIge(encryptedAnswer, key, iv);
-    let g, dhPrime, ga, timeOffset;
-    let dhInnerData = plainTextAnswer;
-    newOffset = 20;
-    let code = dhInnerData.readUInt32BE(newOffset);
-    if (code !== 0xb5890dba) {
-        throw Error("Invalid DH Inner Data code:")
-    }
-    newOffset += 4;
-    let nonceFromServer1 = dhInnerData.readIntLE(newOffset, 16);
-    if (nonceFromServer1 !== nonceFromServer) {
-        throw Error("Invalid nonce in encrypted answer");
-    }
-    newOffset += 16;
-    let serverNonceFromServer1 = dhInnerData.readIntLE(newOffset, 16);
-    if (serverNonceFromServer1 !== serverNonce) {
-        throw Error("Invalid server nonce in encrypted answer");
-    }
-    newOffset += 16;
-    g = dhInnerData.readInt32LE(newOffset);
-    newOffset += 4;
-    let temp = Helpers.tgReadByte(dhInnerData, newOffset);
-    newOffset += temp.offset;
-
-    dhPrime = temp.data.readUInt32BE(0);
-    temp = Helpers.tgReadByte(dhInnerData, newOffset);
-    newOffset += temp.offset;
-    ga = temp.data.readUInt32BE(0);
-    let serverTime = dhInnerData.readInt32LE(newOffset);
-    timeOffset = serverTime - Math.floor(new Date().getTime() / 1000);
-    let b = Helpers.generateRandomBytes(2048).readUInt32BE(0);
-    let gb = (g ** b) % dhPrime;
-    let gab = (ga * b) % dhPrime;
-
-    // Prepare client DH Inner Data
-
-    tempBuffer = Buffer.alloc(8);
-    tempBuffer.writeUIntLE(0x6643b654, 0, 8);
-    let clientDHInnerData = Buffer.concat([
-        tempBuffer,
-        nonce,
-        serverNonce,
-        Buffer.alloc(8).fill(0),
-        Helpers.tgWriteBytes(getByteArray(gb, false)),
-    ]);
-    let clientDhInnerData = Buffer.concat([
-        Helpers.sha1(clientDHInnerData),
-        clientDHInnerData
-    ]);
-
-    // Encryption
-    let clientDhInnerDataEncrypted = AES.encryptIge(clientDhInnerData, key, iv);
-
-    // Prepare Set client DH params
-    tempBuffer = Buffer.alloc(8);
-    tempBuffer.writeUIntLE(0xf5045f1f, 0, 8);
-    let setClientDhParams = Buffer.concat([
-        tempBuffer,
-        nonce,
-        serverNonce,
-        Helpers.tgWriteBytes(clientDhInnerDataEncrypted),
-    ]);
-    sender.send(setClientDhParams);
-
-    // Step 3 response: Complete DH Exchange
-    reader = sender.receive();
-    newOffset = 0;
-    code = reader.readUInt32LE(newOffset);
-    newOffset += 4;
-    if (code === 0x3bcbf734) { //  DH Gen OK
-        nonceFromServer = reader.readIntLE(newOffset, 16);
-        newOffset += 16;
-        if (nonceFromServer !== nonce) {
-            throw Error("Invalid nonce from server");
-        }
-        serverNonceFromServer = reader.readIntLE(newOffset, 16);
-        newOffset += 16;
-        if (serverNonceFromServer !== serverNonce) {
-            throw Error("Invalid server nonce from server");
-        }
-        let newNonceHash1 = reader.readIntLE(newOffset, 16);
-        let authKey = AuthKey(getByteArray(gab, false));
-        let newNonceHashCalculated = authKey.calcNewNonceHash(newNonce, 1);
-        if (newNonceHash1 !== newNonceHashCalculated) {
-            throw Error("Invalid new nonce hash");
-        }
-        return {authKey, timeOffset};
-    } else if (code === 0x46dc1fb9) {
-        throw Error("dh_gen_retry");
-
-    } else if (code === 0x46dc1fb9) {
-        throw Error("dh_gen_fail");
-
-    } else {
-        throw Error("DH Gen unknown");
-
-    }
-
-}
-
-function rightJustify(string, length, char) {
-    let fill = [];
-    while (fill.length + string.length < length) {
-        fill[fill.length] = char;
-    }
-    return fill.join('') + string;
-}
-
-/**
- * Gets a fingerprint text in 01-23-45-67-89-AB-CD-EF format (no hyphens)
- * @param fingerprint {Array}
- * @returns {string}
- */
-function getFingerprintText(fingerprint) {
-    let res = "";
-    for (let b of fingerprint) {
-        res += rightJustify(b.toString(16), 2, '0').toUpperCase();
-    }
-    return res;
-}
-
-/**
- *
- * @param integer {number,BigInt}
- * @param signed {boolean}
- * @returns {number}
- */
-function getByteArray(integer, signed) {
-    let bits = integer.toString(2).length;
-    let byteLength = Math.floor((bits + 8 - 1) / 8);
-    let buffer = Buffer.alloc(byteLength);
-    if (signed) {
-        return buffer.readIntLE(0, byteLength);
-
-    } else {
-        return buffer.readUIntLE(0, byteLength);
-
-    }
-}
-module.exports = doAuthentication;

+ 0 - 58
network/MTProtoPlainSender.js

@@ -1,58 +0,0 @@
-const Helpers = require("../utils/Helpers");
-
-/**
- * MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)
- */
-class MTProtoPlainSender {
-    constructor(transport) {
-        this._sequence = 0;
-        this._timeOffset = 0;
-        this._lastMsgId = 0;
-        this._transport = transport;
-    }
-
-    /**
-     * Sends a plain packet (auth_key_id = 0) containing the given message body (data)
-     * @param data
-     */
-    send(data) {
-        let packet = Buffer.alloc(8, 0);
-        packet.writeBigInt64LE(this.getNewMsgId(), packet.byteLength);
-        packet.writeInt32LE(data.length, packet.byteLength);
-        packet.write(data, packet.byteLength);
-        this._transport.send(packet);
-    }
-
-    /**
-     * Receives a plain packet, returning the body of the response
-     * @returns {Buffer}
-     */
-    receive() {
-        let {seq, body} = this._transport.receive();
-        let message_length = body.readInt32LE(16);
-        return body.slice(20, message_length);
-
-    }
-
-    /**
-     * Generates a new message ID based on the current time (in ms) since epoch
-     * @returns {BigInt}
-     */
-    getNewMsgId() {
-        //See https://core.telegram.org/mtproto/description#message-identifier-msg-id
-        let msTime = Date.now();
-        let newMsgId = ((BigInt(Math.floor(msTime / 1000)) << BigInt(32)) | // "must approximately equal unixtime*2^32"
-            (BigInt(msTime % 1000) << BigInt(32)) | // "approximate moment in time the message was created"
-            BigInt(Helpers.getRandomInt(0, 524288)) << BigInt(2));// "message identifiers are divisible by 4"
-        //Ensure that we always return a message ID which is higher than the previous one
-        if (this._lastMsgId >= newMsgId) {
-            newMsgId = this._lastMsgId + 4
-        }
-        this._lastMsgId = newMsgId;
-        return BigInt(newMsgId);
-
-    }
-
-}
-
-module.exports = MTProtoPlainSender;

+ 0 - 317
network/MTProtoSender.js

@@ -1,317 +0,0 @@
-const MtProtoPlainSender = require("./MTProtoPlainSender");
-const Helpers = require("../utils/Helpers");
-
-/**
- * MTProto Mobile Protocol sender (https://core.telegram.org/mtproto/description)
- */
-class MTProtoSender {
-    constructor(transport, session) {
-        this.transport = transport;
-        this.session = session;
-        this.needConfirmation = Array(); // Message IDs that need confirmation
-        this.onUpdateHandlers = Array();
-
-    }
-
-    /**
-     * Disconnects
-     */
-    disconnect() {
-        this.setListenForUpdates(false);
-        this.transport.close();
-    }
-
-    /**
-     * Adds an update handler (a method with one argument, the received
-     * TLObject) that is fired when there are updates available
-     * @param handler {function}
-     */
-    addUpdateHandler(handler) {
-        let firstHandler = Boolean(this.onUpdateHandlers.length);
-        this.onUpdateHandlers.push(handler);
-        // If this is the first added handler,
-        // we must start receiving updates
-        if (firstHandler) {
-            this.setListenForUpdates(true);
-        }
-    }
-
-    /**
-     * Removes an update handler (a method with one argument, the received
-     * TLObject) that is fired when there are updates available
-     * @param handler {function}
-     */
-    removeUpdateHandler(handler) {
-        let index = this.onUpdateHandlers.indexOf(handler);
-        if (index !== -1) this.onUpdateHandlers.splice(index, 1);
-        if (!Boolean(this.onUpdateHandlers.length)) {
-            this.setListenForUpdates(false);
-
-        }
-    }
-
-    /**
-     *
-     * @param confirmed {boolean}
-     * @returns {number}
-     */
-    generateSequence(confirmed) {
-        if (confirmed) {
-            let result = this.session.sequence * 2 + 1;
-            this.session.sequence += 1;
-            return result;
-        } else {
-            return this.session.sequence * 2;
-        }
-    }
-
-    /**
-     * Sends the specified MTProtoRequest, previously sending any message
-     * which needed confirmation. This also pauses the updates thread
-     * @param request {MtProtoPlainSender}
-     * @param resend
-     */
-    send(request, resend = false) {
-        let buffer;
-        //If any message needs confirmation send an AckRequest first
-        if (Boolean(this.needConfirmation.length)) {
-            let msgsAck = MsgsAck(this.needConfirmation);
-
-            buffer = msgsAck.onSend();
-            this.sendPacket(buffer, msgsAck);
-            this.needConfirmation.length = 0;
-        }
-        //Finally send our packed request
-        buffer = request.on_send();
-        this.sendPacket(buffer, request);
-
-        //And update the saved session
-        this.session.save();
-
-    }
-
-    receive(request) {
-        try {
-            //Try until we get an update
-            while (!request.confirmReceive()) {
-                let {seq, body} = this.transport.receive();
-                let {message, remoteMsgId, remoteSequence} = this.decodeMsg(body);
-                this.processMsg(remoteMsgId, remoteSequence, message, request);
-            }
-        } catch (e) {
-
-        }
-    }
-
-    // region Low level processing
-    /**
-     * Sends the given packet bytes with the additional
-     * information of the original request.
-     * @param packet
-     * @param request
-     */
-    sendPacket(packet, request) {
-        request.msgId = this.session.getNewMsgId();
-
-        // First Calculate plainText to encrypt it
-        let first = Buffer.alloc(8);
-        let second = Buffer.alloc(8);
-        let third = Buffer.alloc(8);
-        let forth = Buffer.alloc(4);
-        let fifth = Buffer.alloc(4);
-        first.writeBigUInt64LE(this.session.salt, 0);
-        second.writeBigUInt64LE(this.session.id, 0);
-        third.writeBigUInt64LE(request.msgId, 0);
-        forth.writeInt32LE(this.generateSequence(request.confirmed), 0);
-        fifth.writeInt32LE(packet.length, 0);
-        let plain = Buffer.concat([
-            first,
-            second,
-            third,
-            forth,
-            fifth,
-            packet
-        ]);
-        let msgKey = Helpers.calcMsgKey(plain);
-        let {key, iv} = Helpers.calcKey(this.session.authKey.key, msgKey, true);
-        let cipherText = AES.encryptIge(plain, key, iv);
-
-        //And then finally send the encrypted packet
-
-        first = Buffer.alloc(8);
-        first.writeUInt32LE(this.session.authKey.keyId, 0);
-        let cipher = Buffer.concat([
-            first,
-            msgKey,
-            cipherText,
-        ]);
-        this.transport.send(cipher);
-    }
-
-    decodeMsg(body) {
-        if (body.length < 8) {
-            throw Error("Can't decode packet");
-        }
-        let offset = 8;
-        let msgKey = body.readIntLE(offset, 16);
-        offset += 16;
-        let {key, iv} = Helpers.calcKey(this.session.authKey.key, msgKey, false);
-        let plainText = AES.decryptIge(body.readIntLE(offset, body.length - offset), key, iv);
-        offset = 0;
-        let remoteSalt = plainText.readBigInt64LE(offset);
-        offset += 8;
-        let remoteSessionId = plainText.readBigInt64LE(offset);
-        offset += 8;
-        let remoteSequence = plainText.readBigInt64LE(offset);
-        offset += 8;
-        let remoteMsgId = plainText.readInt32LE(offset);
-        offset += 4;
-        let msgLen = plainText.readInt32LE(offset);
-        offset += 4;
-        let message = plainText.readIntLE(offset, msgLen);
-        return {message, remoteMsgId, remoteSequence}
-    }
-
-    processMsg(msgId, sequence, reader, offset, request = undefined) {
-        this.needConfirmation.push(msgId);
-        let code = reader.readUInt32LE(offset);
-        offset -= 4;
-
-        // The following codes are "parsed manually"
-        if (code === 0xf35c6d01) {  //rpc_result, (response of an RPC call, i.e., we sent a request)
-            return this.handleRpcResult(msgId, sequence, reader, request);
-        }
-
-        if (code === 0x73f1f8dc) {  //msg_container
-            return this.handlerContainer(msgId, sequence, reader, request);
-        }
-        if (code === 0x3072cfa1) {  //gzip_packed
-            return this.handlerGzipPacked(msgId, sequence, reader, request);
-        }
-        if (code === 0xedab447b) {  //bad_server_salt
-            return this.handleBadServerSalt(msgId, sequence, reader, request);
-        }
-        if (code === 0xa7eff811) {  //bad_msg_notification
-            return this.handleBadMsgNotification(msgId, sequence, reader);
-        }
-        /**
-         * If the code is not parsed manually, then it was parsed by the code generator!
-         * In this case, we will simply treat the incoming TLObject as an Update,
-         * if we can first find a matching TLObject
-         */
-        if (tlobjects.contains(code)) {
-            return this.handleUpdate(msgId, sequence, reader);
-        }
-        console.log("Unknown message");
-        return false;
-    }
-
-    // region Message handling
-
-    handleUpdate(msgId, sequence, reader) {
-        let tlobject = Helpers.tgReadObject(reader);
-        for (let handler of this.onUpdateHandlers) {
-            handler(tlobject);
-        }
-        return Float32Array
-    }
-
-    handleContainer(msgId, sequence, reader, offset, request) {
-        let code = reader.readUInt32LE(offset);
-        offset += 4;
-        let size = reader.readInt32LE(offset);
-        offset += 4;
-        for (let i = 0; i < size; i++) {
-            let innerMsgId = reader.readBigUInt64LE(offset);
-            offset += 8;
-            let innerSequence = reader.readBigInt64LE(offset);
-            offset += 8;
-            let innerLength = reader.readInt32LE(offset);
-            offset += 4;
-            if (!this.processMsg(innerMsgId, sequence, reader, request)) {
-                offset += innerLength;
-            }
-        }
-        return false;
-    }
-
-    handleBadServerSalt(msgId, sequence, reader, offset, request) {
-        let code = reader.readUInt32LE(offset);
-        offset += 4;
-        let badMsgId = reader.readUInt32LE(offset);
-        offset += 4;
-        let badMsgSeqNo = reader.readInt32LE(offset);
-        offset += 4;
-        let errorCode = reader.readInt32LE(offset);
-        offset += 4;
-        let newSalt = reader.readUInt32LE(offset);
-        offset += 4;
-        this.session.salt = newSalt;
-
-        if (!request) {
-            throw Error("Tried to handle a bad server salt with no request specified");
-        }
-
-        //Resend
-        this.send(request, true);
-        return true;
-    }
-
-    handleBadMsgNotification(msgId, sequence, reader, offset) {
-        let code = reader.readUInt32LE(offset);
-        offset += 4;
-        let requestId = reader.readUInt32LE(offset);
-        offset += 4;
-        let requestSequence = reader.readInt32LE(offset);
-        offset += 4;
-        let errorCode = reader.readInt32LE(offset);
-        return BadMessageError(errorCode);
-    }
-
-    handleRpcResult(msgId, sequence, reader, offset, request) {
-        if (!request) {
-            throw Error("RPC results should only happen after a request was sent");
-        }
-
-        let code = reader.readUInt32LE(offset);
-        offset += 4;
-        let requestId = reader.readUInt32LE(offset);
-        offset += 4;
-        let innerCode = reader.readUInt32LE(offset);
-        offset += 4;
-        if (requestId === request.msgId) {
-            request.confirmReceived = true;
-        }
-
-        if (innerCode === 0x2144ca19) {  // RPC Error
-            // TODO add rpc logic
-            throw Error("error");
-        } else {
-            // TODO
-        }
-    }
-
-    handleGzipPacked(msgId, sequence, reader, offset, request) {
-        // TODO
-    }
-
-    setListenForUpdates(enabled) {
-
-        if (enabled) {
-            console.log("Enabled updates");
-        } else {
-            console.log("Disabled updates");
-        }
-    }
-
-    updatesListenMethod() {
-        while (true) {
-            let {seq, body} = this.transport.receive();
-            let {message, remoteMsgId, remoteSequence} = this.decodeMsg(body);
-            this.processMsg(remoteMsgId, remoteSequence, message);
-
-        }
-    }
-}
-
-module.exports = MTProtoSender;

+ 0 - 73
network/TcpClient.js

@@ -1,73 +0,0 @@
-const Socket = require("net").Socket;
-const sleep = require("../utils/Helpers").sleep;
-
-class TcpClient {
-    constructor() {
-        this.connected = false;
-        this.socket = new Socket();
-        this.canceled = false;
-        this.delay = 100;
-    }
-
-    /**
-     * Connects to the specified IP and port number
-     * @param ip
-     * @param port
-     */
-    async connect(ip, port) {
-        this.socket.connect({host: ip, port: port});
-        this.connected = true;
-
-    }
-
-    /**
-     * Closes the connection
-     */
-    async close() {
-        this.socket.destroy();
-        this.connected = true;
-    }
-
-    /**
-     * Writes (sends) the specified bytes to the connected peer
-     * @param data
-     */
-    async write(data) {
-        this.socket.write(data);
-    }
-
-    /**
-     * Reads (receives) the specified bytes from the connected peer
-     * @param bufferSize
-     * @returns {Buffer}
-     */
-    async read(bufferSize) {
-        this.canceled = false;
-        let buffer = Buffer.alloc(0);
-
-        let writtenCount = 0;
-        while (writtenCount < bufferSize) {
-            let leftCount = bufferSize - writtenCount;
-            let partial = this.socket.read(leftCount);
-            if (partial == null) {
-                await sleep(this.delay);
-                continue;
-            }
-            buffer = Buffer.concat([buffer, partial]);
-            writtenCount += buffer.byteLength;
-        }
-
-        return buffer;
-    }
-
-    /**
-     * Cancels the read operation IF it hasn't yet
-     * started, raising a ReadCancelledError
-     */
-    cancelRead() {
-        this.canceled = true;
-    }
-
-}
-
-module.exports = TcpClient;

+ 0 - 84
network/TcpTransport.js

@@ -1,84 +0,0 @@
-const TcpClient = require("./TcpClient");
-const crc = require('crc');
-
-class TcpTransport {
-    constructor(ipAddress, port) {
-        this.tcpClient = new TcpClient();
-        this.sendCounter = 0;
-        this.ipAddress = ipAddress;
-        this.port = port;
-    }
-
-    async connect() {
-        await this.tcpClient.connect(this.ipAddress, this.port);
-    }
-
-    /**
-     * Sends the given packet (bytes array) to the connected peer
-     * Original reference: https://core.telegram.org/mtproto#tcp-transport
-     * The packets are encoded as: total length, sequence number, packet and checksum (CRC32)
-     * @param packet
-     */
-    send(packet) {
-        if (this.tcpClient.connected) {
-            throw Error("not connected");
-        }
-        let buffer = Buffer.alloc(4 + 4);
-        buffer.writeInt32LE(packet.length + 12, 0);
-        buffer.writeInt32LE(this.sendCounter, 4);
-        buffer = Buffer.concat([buffer, packet]);
-        let tempBuffer = Buffer.alloc(4);
-        tempBuffer.writeUInt32LE(crc.crc32(buffer), 0);
-        buffer = Buffer.concat([buffer, tempBuffer]);
-        this.tcpClient.write(buffer);
-        this.sendCounter++;
-    }
-
-    /**
-     * Receives a TCP message (tuple(sequence number, body)) from the connected peer
-     * @returns {{body: {Buffer}, seq: {Buffer}}}
-     */
-    receive() {
-        /**First read everything we need**/
-        let packetLengthBytes = this.tcpClient.read(4);
-        let packetLength = Buffer.from(packetLengthBytes).readInt32LE(0);
-        let seqBytes = this.tcpClient.read(4);
-        let seq = Buffer.from(seqBytes).readInt32LE(0);
-        let body = this.tcpClient.read(packetLength - 12);
-        let checksum = Buffer.from(this.tcpClient.read(4)).readUInt32LE(0);
-        /**Then perform the checks**/
-        let rv = Buffer.concat([packetLengthBytes, seqBytes, body]);
-        let validChecksum = crc.crc32(rv);
-        if (checksum !== validChecksum) {
-            throw Error("invalid checksum");
-        }
-        /** If we passed the tests, we can then return a valid TCP message**/
-
-        return {seq, body}
-    }
-
-    close() {
-        if (this.tcpClient.connected) {
-            this.tcpClient.close();
-        }
-    }
-
-    /**
-     * Cancels (stops) trying to receive from the
-     * remote peer and raises an {Error}
-     */
-    cancelReceive() {
-        this.tcpClient.cancelRead();
-    }
-
-    /**
-     * Gets the client read delay
-     * @returns {number}
-     */
-    getClientDelay() {
-        return this.tcpClient.delay;
-    }
-
-
-}
-module.exports = TcpTransport;

+ 187 - 0
package-lock.json

@@ -89,6 +89,14 @@
         "sprintf-js": "~1.0.2"
       }
     },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
     "astral-regex": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
@@ -104,6 +112,33 @@
       "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
       "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
     },
+    "bigint-buffer": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.2.tgz",
+      "integrity": "sha512-gof9NgbodUgCXD2aUHfUKk5KUDk6/zPlwE+YYpRM2t7zS0K/6WipJFWfziIY+EavxFDuGUnuuQQzzvNcckPKNA==",
+      "dev": true,
+      "requires": {
+        "bindings": "^1.3.0"
+      }
+    },
+    "biguintle": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/biguintle/-/biguintle-1.0.3.tgz",
+      "integrity": "sha512-NRNO+dcFQyS2N0fZ0Lt2QWxhGUsQiREsooSUlt5sP7aMb4LSHk07iYnpgMxhCYZHoZ++rPgxDVAVdGfeytT8TQ==",
+      "dev": true,
+      "requires": {
+        "nanoassert": "^1.1.0"
+      }
+    },
+    "bindings": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+      "dev": true,
+      "requires": {
+        "file-uri-to-path": "1.0.0"
+      }
+    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -201,6 +236,11 @@
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
     },
+    "core-js": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.3.1.tgz",
+      "integrity": "sha512-1PGI49Lz5qYo3EBz0kymSfJgTvn2G/c03lBTJ7PO0R1liQ7Yd6E570odu5p4CxG/WB0yUwCmAWummo79yOQUcQ=="
+    },
     "cosmiconfig": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
@@ -260,6 +300,11 @@
       "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.4.6.tgz",
       "integrity": "sha512-VisC5TBBhOF+70zjrF9FOiqI2LZOhXK/vAWlOrqyqz3lLa+P8jzJ7L/sg90MHmkSY/brAXWwrmGSZR0tM5yi4g=="
     },
+    "date-format": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
+      "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA=="
+    },
     "debug": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -479,6 +524,12 @@
         "flat-cache": "^2.0.1"
       }
     },
+    "file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+      "dev": true
+    },
     "find-up": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -503,6 +554,16 @@
       "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
       "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg=="
     },
+    "fs-extra": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+      "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+      "requires": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
+      }
+    },
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -552,6 +613,11 @@
       "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
       "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
     },
+    "graceful-fs": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
+      "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q=="
+    },
     "has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -646,6 +712,11 @@
         "through": "^2.3.6"
       }
     },
+    "ip": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+      "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
+    },
     "is-arrayish": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -726,6 +797,20 @@
       "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
       "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE="
     },
+    "jsonfile": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+      "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+      "requires": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "leemon": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/leemon/-/leemon-6.2.0.tgz",
+      "integrity": "sha512-a5ieuGSGEb5ezCL6UNds5//cVFaKpeexVK0VDCE8/eOF0r0/9Og94LQ33U2Px5dUcHVCDPWQY8gXLgDlDJnyyg==",
+      "dev": true
+    },
     "levn": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
@@ -753,6 +838,18 @@
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
       "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
     },
+    "log4js": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/log4js/-/log4js-5.2.2.tgz",
+      "integrity": "sha512-Iw4ZjbYTMxSTh1jnXM2brpRIr+psM8/nkUiOHu2gFfd0saoX2NdRB69buMWJJuoIJfU/eTzqKy9rVBr0zQwSGQ==",
+      "requires": {
+        "date-format": "^2.1.0",
+        "debug": "^4.1.1",
+        "flatted": "^2.0.1",
+        "rfdc": "^1.1.4",
+        "streamroller": "^2.2.2"
+      }
+    },
     "long": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
@@ -794,6 +891,12 @@
       "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
       "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
     },
+    "nanoassert": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz",
+      "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=",
+      "dev": true
+    },
     "natural-compare": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -804,6 +907,19 @@
       "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
       "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
     },
+    "node-gzip": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/node-gzip/-/node-gzip-1.1.2.tgz",
+      "integrity": "sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw=="
+    },
+    "node-rsa": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.0.6.tgz",
+      "integrity": "sha512-v42495lozKpuQmrcIzld9ds/Tn7pwjuh0BHSHnhPrKkAVSyTAyrZodFLFafOfWiUKamLt4lgWdngP8W/LzCm2w==",
+      "requires": {
+        "asn1": "^0.2.4"
+      }
+    },
     "normalize-package-data": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -958,6 +1074,38 @@
       "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
       "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
     },
+    "promise-duplex": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/promise-duplex/-/promise-duplex-5.0.2.tgz",
+      "integrity": "sha512-ikiaxkaQCdUWsF3rRBOIWGqVldBVRcn1a23Dbw8lQAS65FCBZGT0Ni+uedT0HFlY3Y/fwTNQtxlvwzP1m00KFg==",
+      "requires": {
+        "core-js": "^3.2.1",
+        "promise-readable": "^5.0.3",
+        "promise-writable": "^5.0.3"
+      }
+    },
+    "promise-readable": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/promise-readable/-/promise-readable-5.0.3.tgz",
+      "integrity": "sha512-sshSIS/7Nzoyfyit2FZhM5Dka9AqqbcBNpRrmcAytLdPxSDvHXN8y37hqfcDcBK5XV0VlHUWaaFFY0vK3UPl9Q==",
+      "requires": {
+        "core-js": "^3.2.1"
+      }
+    },
+    "promise-socket": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/promise-socket/-/promise-socket-6.0.2.tgz",
+      "integrity": "sha512-MBgx8CuB8jFd9eoLOU3d/2b5Eban+mXjr1zAIOTWxr/sgD975DfezY/OF3IwzsDIsZP/3omE4qxXsgDGmC8b5Q==",
+      "requires": {
+        "promise-duplex": "^5.0.2",
+        "tslib": "^1.10.0"
+      }
+    },
+    "promise-writable": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/promise-writable/-/promise-writable-5.0.3.tgz",
+      "integrity": "sha512-mG/6Mo1b8SYT8790wnE8aNE4yerW4MXANoDs840UpVfxakzutQuJtqJDKTwcLoXz+ihlY2qeBjj6lx/VHk1nTQ=="
+    },
     "pump": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -982,6 +1130,11 @@
         "long": "^4.0.0"
       }
     },
+    "random-bigint": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/random-bigint/-/random-bigint-0.0.1.tgz",
+      "integrity": "sha512-X+NTsf5Hzl/tRNLiNTD3N1LRU0eKdIE0+plNlV1CmXLTlnAxj6HipcTnOhWvFRoSytCz6l1f4KYFf/iH8NNSLw=="
+    },
     "read-pkg": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@@ -1033,6 +1186,11 @@
         "signal-exit": "^3.0.2"
       }
     },
+    "rfdc": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
+      "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug=="
+    },
     "rimraf": {
       "version": "2.6.3",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
@@ -1110,6 +1268,20 @@
         "is-fullwidth-code-point": "^2.0.0"
       }
     },
+    "smart-buffer": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz",
+      "integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw=="
+    },
+    "socks": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.2.tgz",
+      "integrity": "sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==",
+      "requires": {
+        "ip": "^1.1.5",
+        "smart-buffer": "4.0.2"
+      }
+    },
     "spdx-correct": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
@@ -1143,6 +1315,16 @@
       "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
       "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
     },
+    "streamroller": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.2.tgz",
+      "integrity": "sha512-wizmZ8NNiqeNIYHv8MqBBbSIeNNcsXyoKxbGYBpiFHCjTGlNHqGNGElwrSM3Awg+0j6U96/eFrSnjW+h3aRo0Q==",
+      "requires": {
+        "date-format": "^2.1.0",
+        "debug": "^4.1.1",
+        "fs-extra": "^8.1.0"
+      }
+    },
     "string-format": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz",
@@ -1259,6 +1441,11 @@
       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
       "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg=="
     },
+    "universalify": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+      "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+    },
     "uri-js": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",

+ 11 - 0
package.json

@@ -18,7 +18,18 @@
     "csv-parse": "^4.4.6",
     "fast-csv": "^3.4.0",
     "glob": "^7.1.4",
+    "log4js": "^5.2.2",
+    "node-gzip": "^1.1.2",
+    "node-rsa": "^1.0.6",
+    "promise-socket": "^6.0.2",
     "python-struct": "^1.1.1",
+    "random-bigint": "0.0.1",
+    "socks": "^2.3.2",
     "string-format": "^2.0.0"
+  },
+  "devDependencies": {
+    "bigint-buffer": "^1.1.2",
+    "biguintle": "^1.0.3",
+    "leemon": "^6.2.0"
   }
 }

+ 0 - 53
tl/Session.js

@@ -1,53 +0,0 @@
-const Helpers = require("../utils/Helpers");
-const fs = require("fs");
-
-class Session {
-    constructor(sessionUserId) {
-        this.sessionUserId = sessionUserId;
-        this.serverAddress = "149.154.167.40";
-        this.port = 80;
-        this.authKey = undefined;
-        this.id = Helpers.generateRandomLong(false);
-        this.sequence = 0;
-        this.salt = 0; // Unsigned long
-        this.timeOffset = 0;
-        this.lastMessageId = 0;
-        this.user = undefined;
-    }
-
-    /**
-     * Saves the current session object as session_user_id.session
-     */
-    async save() {
-        if (this.sessionUserId) {
-            await fs.writeFile(`${this.sessionUserId}.session`, JSON.stringify(this));
-        }
-    }
-
-    static tryLoadOrCreateNew(sessionUserId) {
-        if (sessionUserId === undefined) {
-            return new Session();
-        }
-        let filepath = `${sessionUserId}.session`;
-        if (fs.existsSync(filepath)) {
-            return JSON.parse(fs.readFileSync(filepath, "utf-8"));
-        } else {
-            return Session(sessionUserId);
-        }
-    }
-
-    getNewMsgId() {
-        let msTime = new Date().getTime();
-        let newMessageId = (BigInt(Math.floor(msTime / 1000) + this.timeOffset) << BigInt(32)) |
-            ((msTime % 1000) << 22) |
-            (Helpers.getRandomInt(0, 524288) << 2); // 2^19
-
-        if (this.lastMessageId >= newMessageId) {
-            newMessageId = this.lastMessageId + 4;
-        }
-        this.lastMessageId = newMessageId;
-        return newMessageId;
-    }
-}
-
-module.exports = Session;

+ 0 - 119
tl/TelegramClient.js

@@ -1,119 +0,0 @@
-const {Session} = require("./Session");
-const {doAuthentication} = require("../network/authenticator");
-const {MtProtoSender} = require("../network/mtprotoSender");
-const {TcpTransport} = require("../network/tcpTransport");
-const {InvokeWithLayerRequest, InitConnectionRequest} = require("../gramjs/tl/functions/index");
-const {GetConfigRequest} = require("../gramjs/tl/functions/help");
-
-class TelegramClient {
-
-    constructor(sessionUserId, layer, apiId, apiHash) {
-        if (apiId === undefined || apiHash === undefined) {
-            throw Error("Your API ID or Hash are invalid. Please read \"Requirements\" on README.md");
-        }
-        this.apiId = apiId;
-        this.apiHash = apiHash;
-
-        this.layer = layer;
-
-        this.session = Session.tryLoadOrCreateNew(sessionUserId);
-        this.transport = TcpTransport(this.session.serverAddress, this.session.port);
-
-        //These will be set later
-        this.dcOptions = undefined;
-        this.sender = undefined;
-        this.phoneCodeHashes = Array();
-
-    }
-
-    /**
-     * Connects to the Telegram servers, executing authentication if required.
-     * Note that authenticating to the Telegram servers is not the same as authenticating
-     * the app, which requires to send a code first.
-     * @param reconnect {Boolean}
-     * @returns {Promise<Boolean>}
-     */
-    async connect(reconnect = false) {
-        try {
-            if (!this.session.authKey || reconnect) {
-                let res = doAuthentication(this.transport);
-                this.session.authKey = res.authKey;
-                this.session.timeOffset = res.timeOffset;
-                this.session.save();
-            }
-            this.sender = MtProtoSender(this.transport, this.session);
-
-            // Now it's time to send an InitConnectionRequest
-            // This must always be invoked with the layer we'll be using
-            let query = InitConnectionRequest({
-                apiId: this.apiId,
-                deviceModel: "PlaceHolder",
-                systemVersion: "PlaceHolder",
-                appVersion: "0.0.1",
-                langCode: "en",
-                query: GetConfigRequest()
-            });
-            let result = await this.invoke(InvokeWithLayerRequest({
-                layer: this.layer,
-                query: query
-            }));
-
-            // We're only interested in the DC options
-            // although many other options are available!
-            this.dcOptions = result.dcOptions;
-            return true;
-        } catch (error) {
-            console.log('Could not stabilise initial connection: {}'.replace("{}", error));
-            return false;
-        }
-    }
-
-    /**
-     * Reconnects to the specified DC ID. This is automatically called after an InvalidDCError is raised
-     * @param dc_id {number}
-     */
-    async reconnect_to_dc(dc_id) {
-
-        if (this.dcOptions === undefined || this.dcOptions.length === 0) {
-            throw new Error("Can't reconnect. Stabilise an initial connection first.");
-        }
-        let dc;
-        for (dc of this.dcOptions) {
-            if (dc.id === dc_id) {
-                break;
-            }
-        }
-        this.transport.close();
-        this.transport = new TcpTransport(dc.ipAddress, dc.port);
-        this.session.server_address = dc.ipAddress;
-        this.session.port = dc.port;
-        this.session.save();
-        await this.connect(true);
-    }
-
-    /**
-     * Disconnects from the Telegram server
-     * @returns {Promise<void>}
-     */
-    async disconnect() {
-        if (this.sender) {
-            await this.sender.disconnect();
-        }
-    }
-
-    /**
-     * Invokes a MTProtoRequest (sends and receives it) and returns its result
-     * @param request
-     * @returns {Promise}
-     */
-    async invoke(request) {
-        if (!MTProtoRequest.prototype.isPrototypeOf(Object.getPrototypeOf(request).prototype)) {
-            throw new Error("You can only invoke MtProtoRequests");
-        }
-        await this.sender.send(request);
-        await this.sender.receive(request);
-        return request.result;
-    }
-}
-
-module.exports = TelegramClient;

+ 0 - 197
utils/Helpers.js

@@ -1,197 +0,0 @@
-const crypto = require('crypto');
-const fs = require("fs").promises;
-
-class Helpers {
-
-
-    /**
-     * Generates a random long integer (8 bytes), which is optionally signed
-     * @returns {number}
-     */
-    static generateRandomLong(signed) {
-        let buf = Buffer.from(this.generateRandomBytes(8)); // 0x12345678 = 305419896
-        if (signed)
-            return buf.readInt32BE(0);
-        else
-            return buf.readUInt32LE(0);
-    }
-
-    /**
-     * Generates a random bytes array
-     * @param count
-     * @returns {Buffer}
-     */
-    static generateRandomBytes(count) {
-        return crypto.randomBytes(count);
-    }
-
-    /**
-     * Loads the user settings located under `api/`
-     * @param path
-     * @returns {Promise<void>}
-     */
-    static async loadSettings(path = "../api/settings") {
-        let settings = {};
-        let left, right, value_pair;
-
-        let data = await fs.readFile(path, 'utf-8');
-
-        for (let line of data.toString().split('\n')) {
-            value_pair = line.split("=");
-            if (value_pair.length !== 2) {
-                break;
-            }
-            left = value_pair[0].replace(/ \r?\n|\r/g, '');
-            right = value_pair[1].replace(/ \r?\n|\r/g, '');
-            if (!isNaN(right)) {
-                settings[left] = Number.parseInt(right);
-            } else {
-                settings[left] = right;
-            }
-        }
-
-
-        return settings;
-
-
-    }
-
-    /**
-     * Calculate the key based on Telegram guidelines, specifying whether it's the client or not
-     * @param shared_key
-     * @param msg_key
-     * @param client
-     * @returns {[*, *]}
-     */
-
-    static calcKey(shared_key, msg_key, client) {
-        let x = client !== null ? 0 : 8;
-        let iv, key, sha1a, sha1b, sha1c, sha1d;
-        sha1a = this.sha1((msg_key + shared_key.slice(x, (x + 32))));
-        sha1b = this.sha1(((shared_key.slice((x + 32), (x + 48)) + msg_key) + shared_key.slice((x + 48), (x + 64))));
-        sha1c = this.sha1((shared_key.slice((x + 64), (x + 96)) + msg_key));
-        sha1d = this.sha1((msg_key + shared_key.slice((x + 96), (x + 128))));
-        key = ((sha1a.slice(0, 8) + sha1b.slice(8, 20)) + sha1c.slice(4, 16));
-        iv = (((sha1a.slice(8, 20) + sha1b.slice(0, 8)) + sha1c.slice(16, 20)) + sha1d.slice(0, 8));
-        return [key, iv];
-
-
-    }
-
-    /**
-     * Calculates the message key from the given data
-     * @param data
-     * @returns {Buffer}
-     */
-    static calcMsgKey(data) {
-        return this.sha1(data).slice(4, 20);
-
-
-    }
-
-    /**
-     * Generates the key data corresponding to the given nonces
-     * @param serverNonce
-     * @param newNonce
-     * @returns {{ivBuffer: Buffer, keyBuffer: Buffer}}
-     */
-    static generateKeyDataFromNonces(serverNonce, newNonce) {
-        let hash1 = this.sha1(Buffer.concat([newNonce, serverNonce]));
-        let hash2 = this.sha1(Buffer.concat([serverNonce, newNonce]));
-        let hash3 = this.sha1(Buffer.concat([newNonce, newNonce]));
-        let keyBuffer = Buffer.concat([hash1, hash1.slice(0, 12)]);
-        let ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]);
-        return {keyBuffer: keyBuffer, ivBuffer: ivBuffer}
-    }
-
-    /**
-     * Calculates the SHA1 digest for the given data
-     * @param data
-     * @returns {Buffer}
-     */
-    static sha1(data) {
-        let shaSum = crypto.createHash('sha1');
-        shaSum.update(data);
-        return shaSum.digest();
-
-    }
-
-
-    /**
-     *
-     * @param buffer {Buffer}
-     * @param offset {Number}
-     * @returns {{data: {Buffer}, offset: {Number}}}
-     */
-    static tgReadByte(buffer, offset) {
-        let firstByte = buffer.readInt8(offset);
-        offset += 1;
-        let padding, length;
-        if (firstByte === 255) {
-            length = buffer.readInt8(offset) | (buffer.readInt8(offset) << 8) | (buffer.readInt8(offset) << 16);
-            offset += 1;
-            padding = length % 4;
-        } else {
-            length = firstByte;
-            padding = (length + 1) % 4;
-        }
-        let data = buffer.readInt8(offset);
-        offset += 1;
-        if (padding > 0) {
-            padding = 4 - padding;
-            offset += padding;
-        }
-        return {data, offset}
-    }
-
-    static tgWriteBytes(data) {
-        let buffer;
-        let padding;
-
-        if (data.length < 254) {
-            padding = (data.length + 1) % 4;
-            if (padding !== 0) {
-                padding = 4 - padding;
-            }
-            buffer = Buffer.from([data.length, data]);
-        } else {
-            padding = data.length % 4;
-            if (padding !== 0) {
-                padding = 4 - padding;
-            }
-            buffer = Buffer.concat([Buffer.from([254]),
-                Buffer.from([data.length % 256]),
-                Buffer.from([(data.length >> 8) % 256]),
-                Buffer.from([(data.length >> 16) % 256]),
-                Buffer.from([data]),
-                Buffer.from([padding])
-            ]);
-
-        }
-        return buffer;
-
-
-    }
-
-    /**
-     * returns a random int from min (inclusive) and max (inclusive)
-     * @param min
-     * @param max
-     * @returns {number}
-     */
-    static 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}
-     */
-    static sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
-
-}
-
-module.exports = Helpers;