瀏覽代碼

Merge branch 'master' into network

painor 5 年之前
父節點
當前提交
99376b4ea9
共有 7 個文件被更改,包括 663 次插入12 次删除
  1. 16 4
      crypto/AES.js
  2. 183 6
      crypto/RSA.js
  3. 250 0
      errors.js
  4. 45 0
      tl/mtprotoRequest.js
  5. 52 0
      tl/session.js
  6. 112 0
      tl/telegramClient.js
  7. 5 2
      utils/Helpers.js

+ 16 - 4
crypto/AES.js

@@ -1,9 +1,14 @@
-const TextEncoder = require("util").TextEncoder;
-const TextDecoder = require("util").TextDecoder;
 const aesjs = require('aes-js');
 const aesjs = require('aes-js');
 
 
 
 
 class AES {
 class AES {
+    /**
+     * Decrypts the given text in 16-bytes blocks by using the given key and 32-bytes initialization vector
+     * @param cipherText {Buffer}
+     * @param key {Buffer}
+     * @param iv {Buffer}
+     * @returns {Buffer}
+     */
     static decryptIge(cipherText, key, iv) {
     static decryptIge(cipherText, key, iv) {
         let iv1 = iv.slice(0, Math.floor(iv.length / 2));
         let iv1 = iv.slice(0, Math.floor(iv.length / 2));
         let iv2 = iv.slice(Math.floor(iv.length / 2));
         let iv2 = iv.slice(Math.floor(iv.length / 2));
@@ -30,9 +35,16 @@ class AES {
             ]);
             ]);
 
 
         }
         }
-        return plainText;
+        return Buffer.from(plainText);
     }
     }
 
 
+    /**
+     * Encrypts the given text in 16-bytes blocks by using the given key and 32-bytes initialization vector
+     * @param plainText {Buffer}
+     * @param key {Buffer}
+     * @param iv {Buffer}
+     * @returns {Buffer}
+     */
     static encryptIge(plainText, key, iv) {
     static encryptIge(plainText, key, iv) {
         if (plainText.length % 16 !== 0) {
         if (plainText.length % 16 !== 0) {
             let padding = new Uint8Array(16 - plainText.length % 16);
             let padding = new Uint8Array(16 - plainText.length % 16);
@@ -65,7 +77,7 @@ class AES {
                 ...cipherText.slice(blockIndex * 16 + 16)
                 ...cipherText.slice(blockIndex * 16 + 16)
             ]);
             ]);
         }
         }
-        return cipherText;
+        return Buffer.from(cipherText);
     }
     }
 
 
 }
 }

+ 183 - 6
crypto/RSA.js

@@ -1,11 +1,8 @@
-const crypto = require('crypto');
 const helpers = require("../utils/Helpers").helpers;
 const helpers = require("../utils/Helpers").helpers;
 
 
 
 
-console.log();
-
 class RSA {
 class RSA {
-    _server_keys = {
+    static _server_keys = {
         '216be86c022bb4c3': new RSAServerKey("216be86c022bb4c3", parseInt('C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9' +
         '216be86c022bb4c3': new RSAServerKey("216be86c022bb4c3", parseInt('C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9' +
             '1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E' +
             '1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E' +
             '580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F' +
             '580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F' +
@@ -29,6 +26,185 @@ class RSA {
             '6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1' +
             '6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1' +
             '5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F', 64), parseInt('010001', 16)),
             '5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F', 64), 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
+        )
+
     };
     };
 
 
     /**
     /**
@@ -77,10 +253,11 @@ class RSAServerKey {
             writer = Buffer.concat([writer, helpers.generateRandomBytes(235 - length)]);
             writer = Buffer.concat([writer, helpers.generateRandomBytes(235 - length)]);
 
 
         }
         }
-        let result = writer.readBigInt64BE();
+        let result = writer.readIntBE(0, writer.byteLength);
         result = (result ** this.e) % this.m;
         result = (result ** this.e) % this.m;
         let buffer = Buffer.alloc(256);
         let buffer = Buffer.alloc(256);
-        buffer.writeBigInt64BE(result);
+        buffer.writeUIntBE(result, 0, 256);
+        return buffer;
     }
     }
 }
 }
 
 

+ 250 - 0
errors.js

@@ -0,0 +1,250 @@
+/**
+ * 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));
+    }
+
+}
+

+ 45 - 0
tl/mtprotoRequest.js

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

+ 52 - 0
tl/session.js

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

+ 112 - 0
tl/telegramClient.js

@@ -0,0 +1,112 @@
+const Session = require("./Session");
+
+class TelegramClient {
+
+    async 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 = network.authenticator.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: 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;
+    }
+}

+ 5 - 2
utils/Helpers.js

@@ -8,9 +8,12 @@ class Helpers {
      * Generates a random long integer (8 bytes), which is optionally signed
      * Generates a random long integer (8 bytes), which is optionally signed
      * @returns {number}
      * @returns {number}
      */
      */
-    static generateRandomLong() {
+    static generateRandomLong(signed) {
         let buf = Buffer.from(this.generateRandomBytes(8)); // 0x12345678 = 305419896
         let buf = Buffer.from(this.generateRandomBytes(8)); // 0x12345678 = 305419896
-        return buf.readUInt32BE(0);
+        if (signed)
+            return buf.readInt32BE(0);
+        else
+            return buf.readUInt32LE(0);
     }
     }
 
 
     /**
     /**