diff --git a/src/lib/gramjs/Helpers.js b/src/lib/gramjs/Helpers.js index 8e4d87fed..0c8722bcc 100644 --- a/src/lib/gramjs/Helpers.js +++ b/src/lib/gramjs/Helpers.js @@ -1,5 +1,5 @@ -const crypto = require('./crypto/crypto') -const BigInt = require('big-integer') +const crypto = require('./crypto/crypto'); +const BigInt = require('big-integer'); /** * converts a buffer to big int @@ -9,17 +9,17 @@ const BigInt = require('big-integer') * @returns {bigInt.BigInteger} */ function readBigIntFromBuffer(buffer, little = true, signed = false) { - let randBuffer = Buffer.from(buffer) - const bytesNumber = randBuffer.length + let randBuffer = Buffer.from(buffer); + const bytesNumber = randBuffer.length; if (little) { - randBuffer = randBuffer.reverse() + randBuffer = randBuffer.reverse(); } - let bigInt = BigInt(randBuffer.toString('hex'), 16) + let bigInt = BigInt(randBuffer.toString('hex'), 16); if (signed && Math.floor(bigInt.toString('2').length / 8) >= bytesNumber) { bigInt = bigInt.subtract(BigInt(2) - .pow(BigInt(bytesNumber * 8))) + .pow(BigInt(bytesNumber * 8))); } - return bigInt + return bigInt; } /** @@ -29,12 +29,13 @@ function readBigIntFromBuffer(buffer, little = true, signed = false) { * @returns {Buffer} */ function toSignedLittleBuffer(big, number = 8) { - const bigNumber = BigInt(big) - const byteArray = [] + const bigNumber = BigInt(big); + const byteArray = []; for (let i = 0; i < number; i++) { - byteArray[i] = bigNumber.shiftRight(8 * i).and(255) + byteArray[i] = bigNumber.shiftRight(8 * i) + .and(255); } - return Buffer.from(byteArray) + return Buffer.from(byteArray); } @@ -47,54 +48,54 @@ function toSignedLittleBuffer(big, number = 8) { * @returns {Buffer} */ function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) { - bigInt = BigInt(bigInt) - const bitLength = bigInt.bitLength() + bigInt = BigInt(bigInt); + const bitLength = bigInt.bitLength(); - const bytes = Math.ceil(bitLength / 8) + const bytes = Math.ceil(bitLength / 8); if (bytesNumber < bytes) { - throw new Error('OverflowError: int too big to convert') + throw new Error('OverflowError: int too big to convert'); } if (!signed && bigInt.lesser(BigInt(0))) { - throw new Error('Cannot convert to unsigned') + throw new Error('Cannot convert to unsigned'); } - let below = false + let below = false; if (bigInt.lesser(BigInt(0))) { - below = true - bigInt = bigInt.abs() + below = true; + bigInt = bigInt.abs(); } const hex = bigInt.toString('16') - .padStart(bytesNumber * 2, '0') - let l = Buffer.from(hex, 'hex') + .padStart(bytesNumber * 2, '0'); + let l = Buffer.from(hex, 'hex'); if (little) { - l = l.reverse() + l = l.reverse(); } if (signed && below) { if (little) { - let reminder = false + let reminder = false; if (l[0] !== 0) { - l[0] -= 1 + l[0] -= 1; } for (let i = 0; i < l.length; i++) { if (l[i] === 0) { - reminder = true - continue + reminder = true; + continue; } if (reminder) { - l[i] -= 1 - reminder = false + l[i] -= 1; + reminder = false; } - l[i] = 255 - l[i] + l[i] = 255 - l[i]; } } else { - l[l.length - 1] = 256 - l[l.length - 1] + l[l.length - 1] = 256 - l[l.length - 1]; for (let i = 0; i < l.length - 1; i++) { - l[i] = 255 - l[i] + l[i] = 255 - l[i]; } } } - return l + return l; } /** @@ -102,7 +103,7 @@ function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false * @returns {BigInteger} */ function generateRandomLong(signed = true) { - return readBigIntFromBuffer(generateRandomBytes(8), true, signed) + return readBigIntFromBuffer(generateRandomBytes(8), true, signed); } /** @@ -112,7 +113,7 @@ function generateRandomLong(signed = true) { * @returns {number} */ function mod(n, m) { - return ((n % m) + m) % m + return ((n % m) + m) % m; } /** @@ -122,7 +123,7 @@ function mod(n, m) { * @returns {BigInt} */ function bigIntMod(n, m) { - return ((n.remainder(m)).add(m)).remainder(m) + return ((n.remainder(m)).add(m)).remainder(m); } /** @@ -131,7 +132,7 @@ function bigIntMod(n, m) { * @returns {Buffer} */ function generateRandomBytes(count) { - return Buffer.from(crypto.randomBytes(count)) + return Buffer.from(crypto.randomBytes(count)); } /** @@ -141,6 +142,7 @@ function generateRandomBytes(count) { * @param client * @returns {{iv: Buffer, key: Buffer}} */ + /*CONTEST this is mtproto 1 (mostly used for secret chats) async function calcKey(sharedKey, msgKey, client) { @@ -168,26 +170,26 @@ async function calcKey(sharedKey, msgKey, client) { * @returns {{key: Buffer, iv: Buffer}} */ async function generateKeyDataFromNonce(serverNonce, newNonce) { - serverNonce = toSignedLittleBuffer(serverNonce, 16) - newNonce = toSignedLittleBuffer(newNonce, 32) + serverNonce = toSignedLittleBuffer(serverNonce, 16); + newNonce = toSignedLittleBuffer(newNonce, 32); const [hash1, hash2, hash3] = await Promise.all([ sha1(Buffer.concat([newNonce, serverNonce])), sha1(Buffer.concat([serverNonce, newNonce])), - sha1(Buffer.concat([newNonce, newNonce])) - ]) - const keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)]) - const ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]) + sha1(Buffer.concat([newNonce, newNonce])), + ]); + const keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)]); + const ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]); return { key: keyBuffer, - iv: ivBuffer - } + iv: ivBuffer, + }; } function convertToLittle(buf) { const correct = Buffer.alloc(buf.length * 4); for (let i = 0; i < buf.length; i++) { - correct.writeUInt32BE(buf[i], i * 4) + correct.writeUInt32BE(buf[i], i * 4); } return correct; } @@ -198,9 +200,9 @@ function convertToLittle(buf) { * @returns {Promise} */ function sha1(data) { - const shaSum = crypto.createHash('sha1') - shaSum.update(data) - return shaSum.digest() + const shaSum = crypto.createHash('sha1'); + shaSum.update(data); + return shaSum.digest(); } @@ -210,9 +212,9 @@ function sha1(data) { * @returns {Promise} */ function sha256(data) { - const shaSum = crypto.createHash('sha256') - shaSum.update(data) - return shaSum.digest() + const shaSum = crypto.createHash('sha256'); + shaSum.update(data); + return shaSum.digest(); } /** @@ -223,20 +225,20 @@ function sha256(data) { * @returns {bigInt.BigInteger} */ function modExp(a, b, n) { - a = a.remainder(n) - let result = BigInt.one - let x = a + a = a.remainder(n); + let result = BigInt.one; + let x = a; while (b.greater(BigInt.zero)) { - const leastSignificantBit = b.remainder(BigInt(2)) - b = b.divide(BigInt(2)) + const leastSignificantBit = b.remainder(BigInt(2)); + b = b.divide(BigInt(2)); if (leastSignificantBit.eq(BigInt.one)) { - result = result.multiply(x) - result = result.remainder(n) + result = result.multiply(x); + result = result.remainder(n); } - x = x.multiply(x) - x = x.remainder(n) + x = x.multiply(x); + x = x.remainder(n); } - return result + return result; } @@ -247,9 +249,9 @@ function modExp(a, b, n) { * @returns {Buffer} */ function getByteArray(integer, signed = false) { - const bits = integer.toString(2).length - const byteLength = Math.floor((bits + 8 - 1) / 8) - return readBufferFromBigInt(BigInt(integer), byteLength, false, signed) + const bits = integer.toString(2).length; + const byteLength = Math.floor((bits + 8 - 1) / 8); + return readBufferFromBigInt(BigInt(integer), byteLength, false, signed); } /** @@ -259,9 +261,9 @@ function getByteArray(integer, signed = false) { * @returns {number} */ function getRandomInt(min, max) { - min = Math.ceil(min) - max = Math.floor(max) - return Math.floor(Math.random() * (max - min + 1)) + min + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; } /** @@ -269,7 +271,7 @@ function getRandomInt(min, max) { * @param ms time in milliseconds * @returns {Promise} */ -const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); /** * Checks if the obj is an array @@ -293,36 +295,37 @@ function isArrayLike(obj) { return true } */ + // Taken from https://stackoverflow.com/questions/18638900/javascript-crc32/18639999#18639999 function makeCRCTable() { - let c - const crcTable = [] + let c; + const crcTable = []; for (let n = 0; n < 256; n++) { - c = n + c = n; for (let k = 0; k < 8; k++) { - c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)) + c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } - crcTable[n] = c + crcTable[n] = c; } - return crcTable + return crcTable; } -let crcTable = null +let crcTable = null; function crc32(buf) { if (!crcTable) { - crcTable = makeCRCTable() + crcTable = makeCRCTable(); } if (!Buffer.isBuffer(buf)) { - buf = Buffer.from(buf) + buf = Buffer.from(buf); } - let crc = -1 + let crc = -1; for (let index = 0; index < buf.length; index++) { - const byte = buf[index] - crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8) + const byte = buf[index]; + crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8); } - return (crc ^ (-1)) >>> 0 + return (crc ^ (-1)) >>> 0; } module.exports = { @@ -343,6 +346,6 @@ module.exports = { getByteArray, //isArrayLike, toSignedLittleBuffer, - convertToLittle + convertToLittle, -} +}; diff --git a/src/lib/gramjs/Password.js b/src/lib/gramjs/Password.js index 50b951dfb..b8386005b 100644 --- a/src/lib/gramjs/Password.js +++ b/src/lib/gramjs/Password.js @@ -1,10 +1,16 @@ -const BigInt = require('big-integer') -const Factorizator = require('./crypto/Factorizator') -const { constructors } = require('./tl') -const { readBigIntFromBuffer, readBufferFromBigInt, sha256, bigIntMod, modExp, - generateRandomBytes } = require('./Helpers') -const crypto = require('./crypto/crypto') -const SIZE_FOR_HASH = 256 +const BigInt = require('big-integer'); +const Factorizator = require('./crypto/Factorizator'); +const { constructors } = require('./tl'); +const { + readBigIntFromBuffer, + readBufferFromBigInt, + sha256, + bigIntMod, + modExp, + generateRandomBytes, +} = require('./Helpers'); +const crypto = require('./crypto/crypto'); +const SIZE_FOR_HASH = 256; /** * @@ -12,6 +18,7 @@ const SIZE_FOR_HASH = 256 * @param prime{BigInteger} * @param g{BigInteger} */ + /* We don't support changing passwords yet function checkPrimeAndGoodCheck(prime, g) { @@ -80,13 +87,13 @@ function checkPrimeAndGood(primeBytes, g) { 0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F, 0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF, 0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B, - ]) + ]); if (goodPrime.equals(primeBytes)) { - if ([ 3, 4, 5, 7 ].includes(g)) { - return // It's good + if ([3, 4, 5, 7].includes(g)) { + return; // It's good } } - throw new Error("Changing passwords unsupported") + throw new Error('Changing passwords unsupported'); //checkPrimeAndGoodCheck(readBigIntFromBuffer(primeBytes, false), g) } @@ -97,7 +104,8 @@ function checkPrimeAndGood(primeBytes, g) { * @returns {boolean} */ function isGoodLarge(number, p) { - return (number.greater(BigInt(0)) && (p.subtract(number).greater(BigInt(0)))) + return (number.greater(BigInt(0)) && (p.subtract(number) + .greater(BigInt(0)))); } /** @@ -106,7 +114,7 @@ function isGoodLarge(number, p) { * @returns {Buffer} */ function numBytesForHash(number) { - return Buffer.concat([ Buffer.alloc(SIZE_FOR_HASH - number.length), number ]) + return Buffer.concat([Buffer.alloc(SIZE_FOR_HASH - number.length), number]); } /** @@ -115,7 +123,7 @@ function numBytesForHash(number) { * @returns {Buffer} */ function bigNumForHash(g) { - return readBufferFromBigInt(g, SIZE_FOR_HASH, false) + return readBufferFromBigInt(g, SIZE_FOR_HASH, false); } /** @@ -125,24 +133,24 @@ function bigNumForHash(g) { * @returns {Boolean} */ function isGoodModExpFirst(modexp, prime) { - const diff = prime.subtract(modexp) + const diff = prime.subtract(modexp); - const minDiffBitsCount = 2048 - 64 - const maxModExpSize = 256 + const minDiffBitsCount = 2048 - 64; + const maxModExpSize = 256; return !(diff.lesser(BigInt(0)) || diff.bitLength() < minDiffBitsCount || modexp.bitLength() < minDiffBitsCount || - Math.floor((modexp.bitLength() + 7) / 8) > maxModExpSize) + Math.floor((modexp.bitLength() + 7) / 8) > maxModExpSize); } function xor(a, b) { - const length = Math.min(a.length, b.length) + const length = Math.min(a.length, b.length); for (let i = 0; i < length; i++) { - a[i] = a[i] ^ b[i] + a[i] = a[i] ^ b[i]; } - return a + return a; } /** @@ -154,7 +162,7 @@ function xor(a, b) { */ function pbkdf2sha512(password, salt, iterations) { - return crypto.pbkdf2(password, salt, iterations, 64, 'sha512') + return crypto.pbkdf2(password, salt, iterations, 64, 'sha512'); } /** @@ -164,10 +172,10 @@ function pbkdf2sha512(password, salt, iterations) { * @returns {Buffer|*} */ async function computeHash(algo, password) { - const hash1 = await sha256(Buffer.concat([ algo.salt1, Buffer.from(password, 'utf-8'), algo.salt1 ])) - const hash2 = await sha256(Buffer.concat([ algo.salt2, hash1, algo.salt2 ])) - const hash3 = await pbkdf2sha512(hash2, algo.salt1, 100000) - return sha256(Buffer.concat([ algo.salt2, hash3, algo.salt2 ])) + const hash1 = await sha256(Buffer.concat([algo.salt1, Buffer.from(password, 'utf-8'), algo.salt1])); + const hash2 = await sha256(Buffer.concat([algo.salt2, hash1, algo.salt2])); + const hash3 = await pbkdf2sha512(hash2, algo.salt1, 100000); + return sha256(Buffer.concat([algo.salt2, hash3, algo.salt2])); } /** @@ -177,15 +185,15 @@ async function computeHash(algo, password) { */ async function computeDigest(algo, password) { try { - checkPrimeAndGood(algo.p, algo.g) + checkPrimeAndGood(algo.p, algo.g); } catch (e) { - throw new Error('bad p/g in password') + throw new Error('bad p/g in password'); } const value = modExp(BigInt(algo.g), readBigIntFromBuffer(await computeHash(algo, password), false), - readBigIntFromBuffer(algo.p, false)) - return bigNumForHash(value) + readBigIntFromBuffer(algo.p, false)); + return bigNumForHash(value); } /** @@ -194,70 +202,70 @@ async function computeDigest(algo, password) { * @param password {string} */ async function computeCheck(request, password) { - const algo = request.currentAlgo + const algo = request.currentAlgo; if (!(algo instanceof constructors.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)) { - throw new Error(`Unsupported password algorithm ${algo.className}`) + throw new Error(`Unsupported password algorithm ${algo.className}`); } - const pwHash = await computeHash(algo, password) - const p = readBigIntFromBuffer(algo.p, false) - const g = algo.g - const B = readBigIntFromBuffer(request.srp_B, false) + const pwHash = await computeHash(algo, password); + const p = readBigIntFromBuffer(algo.p, false); + const g = algo.g; + const B = readBigIntFromBuffer(request.srp_B, false); try { - checkPrimeAndGood(algo.p, g) + checkPrimeAndGood(algo.p, g); } catch (e) { - throw new Error('bad /g in password') + throw new Error('bad /g in password'); } if (!isGoodLarge(B, p)) { - throw new Error('bad b in check') + throw new Error('bad b in check'); } - const x = readBigIntFromBuffer(pwHash, false) - const pForHash = numBytesForHash(algo.p) - const gForHash = bigNumForHash(g) - const bForHash = numBytesForHash(request.srp_B) - const gX = modExp(BigInt(g), x, p) - const k = readBigIntFromBuffer(await sha256(Buffer.concat([ pForHash, gForHash ])), false) - const kgX = bigIntMod(k.multiply(gX),p) - const generateAndCheckRandom =async () => { - const randomSize = 256 + const x = readBigIntFromBuffer(pwHash, false); + const pForHash = numBytesForHash(algo.p); + const gForHash = bigNumForHash(g); + const bForHash = numBytesForHash(request.srp_B); + const gX = modExp(BigInt(g), x, p); + const k = readBigIntFromBuffer(await sha256(Buffer.concat([pForHash, gForHash])), false); + const kgX = bigIntMod(k.multiply(gX), p); + const generateAndCheckRandom = async () => { + const randomSize = 256; // eslint-disable-next-line no-constant-condition while (true) { - const random = generateRandomBytes(randomSize) - const a = readBigIntFromBuffer(random, false) - const A = modExp(BigInt(g), a, p) + const random = generateRandomBytes(randomSize); + const a = readBigIntFromBuffer(random, false); + const A = modExp(BigInt(g), a, p); if (isGoodModExpFirst(A, p)) { - const aForHash = bigNumForHash(A) - const u = readBigIntFromBuffer(await sha256(Buffer.concat([ aForHash, bForHash ])), false) + const aForHash = bigNumForHash(A); + const u = readBigIntFromBuffer(await sha256(Buffer.concat([aForHash, bForHash])), false); if (u.greater(BigInt(0))) { - return [ a, aForHash, u ] + return [a, aForHash, u]; } } } - } - const [ a, aForHash, u ] =await generateAndCheckRandom() - const gB = bigIntMod(B.subtract(kgX),p) + }; + const [a, aForHash, u] = await generateAndCheckRandom(); + const gB = bigIntMod(B.subtract(kgX), p); if (!isGoodModExpFirst(gB, p)) { - throw new Error('bad gB') + throw new Error('bad gB'); } - const ux = u.multiply(x) - const aUx = a.add(ux) - const S = modExp(gB, aUx, p) - const [K, pSha ,gSha, salt1Sha, salt2Sha] = await Promise.all([ + const ux = u.multiply(x); + const aUx = a.add(ux); + const S = modExp(gB, aUx, p); + const [K, pSha, gSha, salt1Sha, salt2Sha] = await Promise.all([ sha256(bigNumForHash(S)), sha256(pForHash), sha256(gForHash), sha256(algo.salt1), - sha256(algo.salt2) - ]) + sha256(algo.salt2), + ]); const M1 = await sha256(Buffer.concat([ - xor(pSha,gSha), + xor(pSha, gSha), salt1Sha, salt2Sha, aForHash, bForHash, K, - ])) + ])); return new constructors.InputCheckPasswordSRP({ @@ -265,11 +273,11 @@ async function computeCheck(request, password) { A: Buffer.from(aForHash), M1: M1, - }) + }); } module.exports = { computeCheck, computeDigest, -} +}; diff --git a/src/lib/gramjs/Utils.js b/src/lib/gramjs/Utils.js index b42fb1be8..141e2814a 100644 --- a/src/lib/gramjs/Utils.js +++ b/src/lib/gramjs/Utils.js @@ -1,18 +1,18 @@ -const { constructors } = require('./tl') +const { constructors } = require('./tl'); const USERNAME_RE = new RegExp('@|(?:https?:\\/\\/)?(?:www\\.)?' + - '(?:telegram\\.(?:me|dog)|t\\.me)\\/(@|joinchat\\/)?') + '(?:telegram\\.(?:me|dog)|t\\.me)\\/(@|joinchat\\/)?'); -const JPEG_HEADER = Buffer.from('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00', 'hex') -const JPEG_FOOTER = Buffer.from('ffd9', 'hex') +const JPEG_HEADER = Buffer.from('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00', 'hex'); +const JPEG_FOOTER = Buffer.from('ffd9', 'hex'); -const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite=') +const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite='); const VALID_USERNAME_RE = new RegExp('^([a-z]((?!__)[\\w\\d]){3,30}[a-z\\d]|gif|vid|' + - 'pic|bing|wiki|imdb|bold|vote|like|coub)$') + 'pic|bing|wiki|imdb|bold|vote|like|coub)$'); function _raiseCastFail(entity, target) { - throw new Error(`Cannot cast ${entity.className} to any kind of ${target}`) + throw new Error(`Cannot cast ${entity.className} to any kind of ${target}`); } /** @@ -37,41 +37,41 @@ function getInputPeer(entity, allowSelf = true, checkHash = true) { // e.g. custom.Dialog (can't cyclic import). if (allowSelf && 'inputEntity' in entity) { - return entity.inputEntity + return entity.inputEntity; } else if ('entity' in entity) { - return getInputPeer(entity.entity) + return getInputPeer(entity.entity); } else { - _raiseCastFail(entity, 'InputPeer') + _raiseCastFail(entity, 'InputPeer'); } } if (entity.SUBCLASS_OF_ID === 0xc91c90b6) { // crc32(b'InputPeer') - return entity + return entity; } if (entity instanceof constructors.User) { if (entity.isSelf && allowSelf) { - return new constructors.InputPeerSelf() + return new constructors.InputPeerSelf(); } else if (entity.accessHash !== undefined || !checkHash) { return new constructors.InputPeerUser({ userId: entity.id, accessHash: entity.accessHash, - }) + }); } else { - throw new Error('User without accessHash or min info cannot be input') + throw new Error('User without accessHash or min info cannot be input'); } } if (entity instanceof constructors.Chat || entity instanceof constructors.ChatEmpty || entity instanceof constructors.ChatForbidden) { - return new constructors.InputPeerChat({ chatId: entity.id }) + return new constructors.InputPeerChat({ chatId: entity.id }); } if (entity instanceof constructors.Channel) { if (entity.accessHash !== undefined || !checkHash) { return new constructors.InputPeerChannel({ channelId: entity.id, - accessHash: entity.accessHash - }) + accessHash: entity.accessHash, + }); } else { - throw new TypeError('Channel without accessHash or min info cannot be input') + throw new TypeError('Channel without accessHash or min info cannot be input'); } } if (entity instanceof constructors.ChannelForbidden) { @@ -79,38 +79,38 @@ function getInputPeer(entity, allowSelf = true, checkHash = true) { // also not optional, we assume that this truly is the case. return new constructors.InputPeerChannel({ channelId: entity.id, - accessHash: entity.accessHash - }) + accessHash: entity.accessHash, + }); } if (entity instanceof constructors.InputUser) { return new constructors.InputPeerUser({ userId: entity.userId, - accessHash: entity.accessHash - }) + accessHash: entity.accessHash, + }); } if (entity instanceof constructors.InputChannel) { return new constructors.InputPeerChannel({ channelId: entity.channelId, - accessHash: entity.accessHash - }) + accessHash: entity.accessHash, + }); } if (entity instanceof constructors.UserEmpty) { - return new constructors.InputPeerEmpty() + return new constructors.InputPeerEmpty(); } if (entity instanceof constructors.UserFull) { - return getInputPeer(entity.user) + return getInputPeer(entity.user); } if (entity instanceof constructors.ChatFull) { - return new constructors.InputPeerChat({ chatId: entity.id }) + return new constructors.InputPeerChat({ chatId: entity.id }); } if (entity instanceof constructors.PeerChat) { - return new constructors.InputPeerChat(entity.chatId) + return new constructors.InputPeerChat(entity.chatId); } - _raiseCastFail(entity, 'InputPeer') + _raiseCastFail(entity, 'InputPeer'); } /** @@ -228,6 +228,7 @@ function getInputDialog(dialog) { _raiseCastFail(dialog, 'InputDialogPeer') } */ + /*CONTEST function getInputMessage(message) { @@ -259,12 +260,12 @@ function getInputMessage(message) { function strippedPhotoToJpg(stripped) { // Note: Changes here should update _stripped_real_length if (stripped.length < 3 || stripped[0] !== 1) { - return stripped + return stripped; } - const header = Buffer.from(JPEG_HEADER) - header[164] = stripped[1] - header[166] = stripped[2] - return Buffer.concat([header, stripped.slice(3), JPEG_FOOTER]) + const header = Buffer.from(JPEG_HEADER); + header[164] = stripped[1]; + header[166] = stripped[2]; + return Buffer.concat([header, stripped.slice(3), JPEG_FOOTER]); } /*CONTEST @@ -329,16 +330,16 @@ function getInputLocation(location) { */ function getAppropriatedPartSize(fileSize) { if (fileSize <= 104857600) { // 100MB - return 128 + return 128; } if (fileSize <= 786432000) { // 750MB - return 256 + return 256; } if (fileSize <= 1572864000) { // 1500MB - return 512 + return 512; } - throw new Error('File size too large') + throw new Error('File size too large'); } /*CONTEST @@ -480,6 +481,7 @@ function resolveId(markedId) { * @returns {{inputEntity: *, entity: *}} * @private */ + /*CONTEST function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer) { @@ -501,15 +503,15 @@ function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer) function getMessageId(message) { if (message === null || message === undefined) { - return null + return null; } if (typeof message == 'number') { - return message + return message; } if (message.SUBCLASS_OF_ID === 0x790009e3) { // crc32(b'Message') - return message.id + return message.id; } - throw new Error(`Invalid message type: ${message.constructor.name}`) + throw new Error(`Invalid message type: ${message.constructor.name}`); } @@ -518,7 +520,8 @@ function getMessageId(message) { * @param phone */ function parsePhone(phone) { - return phone.toString().replace(/[+()\s-]/gm, '') + return phone.toString() + .replace(/[+()\s-]/gm, ''); } /** @@ -531,6 +534,7 @@ function parsePhone(phone) { * @param username {string} */ + /*CONTEST function parseUsername(username) { @@ -577,18 +581,18 @@ function rtrim(s, mask) { function getDisplayName(entity) { if (entity instanceof constructors.User) { if (entity.lastName && entity.firstName) { - return `${entity.firstName} ${entity.lastName}` + return `${entity.firstName} ${entity.lastName}`; } else if (entity.firstName) { - return entity.firstName + return entity.firstName; } else if (entity.lastName) { - return entity.lastName + return entity.lastName; } else { - return '' + return ''; } } else if (entity instanceof constructors.Chat || entity instanceof constructors.Channel) { - return entity.title + return entity.title; } - return '' + return ''; } /** @@ -596,6 +600,7 @@ function getDisplayName(entity) { * @param item * @returns {boolean} */ + /*CONTEST Duplicate ? function isListLike(item) { @@ -618,34 +623,34 @@ function getDC(dcId, cdn = false) { return { id: 1, ipAddress: 'pluto.web.telegram.org', - port: 443 - } + port: 443, + }; case 2: return { id: 2, ipAddress: 'venus.web.telegram.org', - port: 443 - } + port: 443, + }; case 3: return { id: 3, ipAddress: 'aurora.web.telegram.org', - port: 443 - } + port: 443, + }; case 4: return { id: 4, ipAddress: 'vesta.web.telegram.org', - port: 443 - } + port: 443, + }; case 5: return { id: 5, ipAddress: 'flora.web.telegram.org', - port: 443 - } + port: 443, + }; default: - throw new Error(`Cannot find the DC with the ID of ${dcId}`) + throw new Error(`Cannot find the DC with the ID of ${dcId}`); } // TODO chose based on current connection method /* @@ -683,5 +688,5 @@ module.exports = { getAppropriatedPartSize, //getInputLocation, strippedPhotoToJpg, - getDC -} + getDC, +}; diff --git a/src/lib/gramjs/Version.js b/src/lib/gramjs/Version.js index ada7db7c9..092443805 100644 --- a/src/lib/gramjs/Version.js +++ b/src/lib/gramjs/Version.js @@ -1 +1 @@ -module.exports = '0.0.2' +module.exports = '0.0.2'; diff --git a/src/lib/gramjs/client/TelegramClient.d.ts b/src/lib/gramjs/client/TelegramClient.d.ts index ef9726852..8496c94ae 100644 --- a/src/lib/gramjs/client/TelegramClient.d.ts +++ b/src/lib/gramjs/client/TelegramClient.d.ts @@ -3,7 +3,7 @@ import { Api } from '..'; import { BotAuthParams, UserAuthParams } from './auth'; import { uploadFile, UploadFileParams } from './uploadFile'; import { downloadFile, DownloadFileParams } from './downloadFile'; -import { updateTwoFaSettings, TwoFaParams } from './2fa'; +import { TwoFaParams, updateTwoFaSettings } from './2fa'; declare class TelegramClient { constructor(...args: any) diff --git a/src/lib/gramjs/client/TelegramClient.js b/src/lib/gramjs/client/TelegramClient.js index 6fa0d8bf8..f4ffdd321 100644 --- a/src/lib/gramjs/client/TelegramClient.js +++ b/src/lib/gramjs/client/TelegramClient.js @@ -1,28 +1,34 @@ -const Logger = require('../extensions/Logger') -const { sleep } = require('../Helpers') -const errors = require('../errors') -const MemorySession = require('../sessions/Memory') -const Helpers = require('../Helpers') -const { BinaryWriter } = require('../extensions') -const utils = require('../Utils') -const Session = require('../sessions/Abstract') -const os = require('os') -const { LAYER } = require('../tl/AllTLObjects') -const { constructors, requests } = require('../tl') -const MTProtoSender = require('../network/MTProtoSender') -const { UpdateConnectionState } = require("../network") -const { ConnectionTCPObfuscated } = require('../network/connection/TCPObfuscated') -const { authFlow, checkAuthorization } = require('./auth') -const { downloadFile } = require('./downloadFile') -const { uploadFile } = require('./uploadFile') -const { updateTwoFaSettings } = require('./2fa') +const Logger = require('../extensions/Logger'); +const { sleep } = require('../Helpers'); +const errors = require('../errors'); +const MemorySession = require('../sessions/Memory'); +const Helpers = require('../Helpers'); +const { BinaryWriter } = require('../extensions'); +const utils = require('../Utils'); +const Session = require('../sessions/Abstract'); +const os = require('os'); +const { LAYER } = require('../tl/AllTLObjects'); +const { + constructors, + requests, +} = require('../tl'); +const MTProtoSender = require('../network/MTProtoSender'); +const { UpdateConnectionState } = require('../network'); +const { ConnectionTCPObfuscated } = require('../network/connection/TCPObfuscated'); +const { + authFlow, + checkAuthorization, +} = require('./auth'); +const { downloadFile } = require('./downloadFile'); +const { uploadFile } = require('./uploadFile'); +const { updateTwoFaSettings } = require('./2fa'); -const DEFAULT_DC_ID = 2 -const DEFAULT_IPV4_IP = 'venus.web.telegram.org' -const DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]' +const DEFAULT_DC_ID = 2; +const DEFAULT_IPV4_IP = 'venus.web.telegram.org'; +const DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]'; // All types -const sizeTypes = ['w', 'y', 'd', 'x', 'c', 'm', 'b', 'a', 's'] +const sizeTypes = ['w', 'y', 'd', 'x', 'c', 'm', 'b', 'a', 's']; class TelegramClient { @@ -45,7 +51,7 @@ class TelegramClient { baseLogger: 'gramjs', useWSS: false, additionalDcsDisabled: false, - } + }; /** * @@ -56,52 +62,52 @@ class TelegramClient { */ constructor(session, apiId, apiHash, opts = TelegramClient.DEFAULT_OPTIONS) { if (apiId === undefined || apiHash === undefined) { - throw Error('Your API ID or Hash are invalid. Please read "Requirements" on README.md') + throw Error('Your API ID or Hash are invalid. Please read "Requirements" on README.md'); } - const args = { ...TelegramClient.DEFAULT_OPTIONS, ...opts } - this.apiId = apiId - this.apiHash = apiHash - this._useIPV6 = args.useIPV6 + const args = { ...TelegramClient.DEFAULT_OPTIONS, ...opts }; + this.apiId = apiId; + this.apiHash = apiHash; + this._useIPV6 = args.useIPV6; // this._entityCache = new Set() if (typeof args.baseLogger == 'string') { - this._log = new Logger() + this._log = new Logger(); } else { - this._log = args.baseLogger + this._log = args.baseLogger; } // Determine what session we will use if (typeof session === 'string' || !session) { try { - throw new Error('not implemented') + throw new Error('not implemented'); } catch (e) { - session = new MemorySession() + session = new MemorySession(); } } else if (!(session instanceof Session)) { - throw new Error('The given session must be str or a session instance') + throw new Error('The given session must be str or a session instance'); } - this.floodSleepLimit = args.floodSleepLimit - this._eventBuilders = [] + this.floodSleepLimit = args.floodSleepLimit; + this._eventBuilders = []; - this._phoneCodeHash = {} - this.session = session + this._phoneCodeHash = {}; + this.session = session; // this._entityCache = EntityCache(); - this.apiId = parseInt(apiId) - this.apiHash = apiHash + this.apiId = parseInt(apiId); + this.apiHash = apiHash; - this._requestRetries = args.requestRetries - this._connectionRetries = args.connectionRetries - this._retryDelay = args.retryDelay || 0 + this._requestRetries = args.requestRetries; + this._connectionRetries = args.connectionRetries; + this._retryDelay = args.retryDelay || 0; if (args.proxy) { - this._log.warn('proxies are not supported') + this._log.warn('proxies are not supported'); } - this._proxy = args.proxy - this._timeout = args.timeout - this._autoReconnect = args.autoReconnect + this._proxy = args.proxy; + this._timeout = args.timeout; + this._autoReconnect = args.autoReconnect; - this._connection = args.connection + this._connection = args.connection; // TODO add proxy support - this._floodWaitedRequests = {} + this._floodWaitedRequests = {}; this._initWith = (x) => { return new requests.InvokeWithLayer({ @@ -119,14 +125,14 @@ class TelegramClient { query: x, proxy: null, // no proxies yet. }), - }) - } + }); + }; - this._args = args + this._args = args; // These will be set later - this._config = null - this.phoneCodeHashes = [] - this._borrowedSenderPromises = {} + this._config = null; + this.phoneCodeHashes = []; + this._borrowedSenderPromises = {}; this._additionalDcsDisabled = args.additionalDcsDisabled; } @@ -140,7 +146,7 @@ class TelegramClient { * @returns {Promise} */ async connect() { - await this._initSession() + await this._initSession(); this._sender = new MTProtoSender(this.session.getAuthKey(), { logger: this._log, @@ -152,45 +158,45 @@ class TelegramClient { authKeyCallback: this._authKeyCallback.bind(this), updateCallback: this._handleUpdate.bind(this), isMainSender: true, - }) + }); const connection = new this._connection( - this.session.serverAddress, this.session.port, this.session.dcId, this._log - ) + this.session.serverAddress, this.session.port, this.session.dcId, this._log, + ); - await this._sender.connect(connection) + await this._sender.connect(connection); - this.session.setAuthKey(this._sender.authKey) + this.session.setAuthKey(this._sender.authKey); await this._sender.send(this._initWith( new requests.help.GetConfig({}), - )) + )); - this._updateLoop() + this._updateLoop(); } async _initSession() { - await this.session.load() + await this.session.load(); if (!this.session.serverAddress || (this.session.serverAddress.includes(':') !== this._useIPV6)) { - this.session.setDC(DEFAULT_DC_ID, this._useIPV6 ? DEFAULT_IPV6_IP : DEFAULT_IPV4_IP, this._args.useWSS ? 443 : 80) + this.session.setDC(DEFAULT_DC_ID, this._useIPV6 ? DEFAULT_IPV6_IP : DEFAULT_IPV4_IP, this._args.useWSS ? 443 : 80); } } async _updateLoop() { while (this.isConnected()) { - await Helpers.sleep(3 * 1000) + await Helpers.sleep(3 * 1000); try { await attempts(() => { return timeout(this._sender.send(new requests.Ping({ - pingId: Helpers.getRandomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) - })), 1500) - }, 3, 100) + pingId: Helpers.getRandomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER), + })), 1500); + }, 3, 100); } catch (err) { - await this.disconnect() - this.connect() + await this.disconnect(); + this.connect(); - return + return; } // We need to send some content-related request at least hourly @@ -200,7 +206,7 @@ class TelegramClient { // TODO Call getDifference instead since it's more relevant if (new Date().getTime() - this._lastRequest > 30 * 60 * 1000) { try { - await this.invoke(new requests.updates.GetState()) + await this.invoke(new requests.updates.GetState()); } catch (e) { } @@ -214,20 +220,21 @@ class TelegramClient { */ async disconnect() { if (this._sender) { - await this._sender.disconnect() + await this._sender.disconnect(); } await Promise.all( - Object.values(this._borrowedSenderPromises).map((promise) => { - return promise.then((sender) => { - if (sender) { - return sender.disconnect() - } - }) - }) - ) + Object.values(this._borrowedSenderPromises) + .map((promise) => { + return promise.then((sender) => { + if (sender) { + return sender.disconnect(); + } + }); + }), + ); - this._borrowedSenderPromises = {} + this._borrowedSenderPromises = {}; } /** @@ -235,55 +242,55 @@ class TelegramClient { * @returns {Promise} */ async destroy() { - await this.disconnect() - this.session.delete() - this._eventBuilders = [] + await this.disconnect(); + this.session.delete(); + this._eventBuilders = []; } async _switchDC(newDc) { - this._log.info(`Reconnecting to new data center ${newDc}`) - const DC = utils.getDC(newDc) - this.session.setDC(newDc, DC.ipAddress, DC.port) + this._log.info(`Reconnecting to new data center ${newDc}`); + const DC = utils.getDC(newDc); + this.session.setDC(newDc, DC.ipAddress, DC.port); // authKey's are associated with a server, which has now changed // so it's not valid anymore. Set to None to force recreating it. - await this._sender.authKey.setKey(null) - this.session.setAuthKey(null) - await this.disconnect() - return this.connect() + await this._sender.authKey.setKey(null); + this.session.setAuthKey(null); + await this.disconnect(); + return this.connect(); } async _authKeyCallback(authKey, dcId) { - this.session.setAuthKey(authKey, dcId) + this.session.setAuthKey(authKey, dcId); } // endregion // export region _cleanupBorrowedSender(dcId) { - delete this._borrowedSenderPromises[dcId] + delete this._borrowedSenderPromises[dcId]; } async _borrowExportedSender(dcId) { if (this._additionalDcsDisabled) { - return undefined + return undefined; } - let senderPromise = this._borrowedSenderPromises[dcId] + let senderPromise = this._borrowedSenderPromises[dcId]; if (!senderPromise) { - senderPromise = this._createExportedSender(dcId) - this._borrowedSenderPromises[dcId] = senderPromise + senderPromise = this._createExportedSender(dcId); + this._borrowedSenderPromises[dcId] = senderPromise; senderPromise.then((sender) => { if (!sender) { - delete this._borrowedSenderPromises[dcId] + delete this._borrowedSenderPromises[dcId]; } }); } - return senderPromise + return senderPromise; } async _createExportedSender(dcId) { - const dc = utils.getDC(dcId) + const dc = utils.getDC(dcId); const sender = new MTProtoSender(this.session.getAuthKey(dcId), { logger: this._log, @@ -295,7 +302,7 @@ class TelegramClient { authKeyCallback: this._authKeyCallback.bind(this), isMainSender: dcId === this.session.dcId, onConnectionBreak: this._cleanupBorrowedSender.bind(this), - }) + }); for (let i = 0; i < 5; i++) { try { await sender.connect(new this._connection( @@ -303,24 +310,24 @@ class TelegramClient { dc.port, dcId, this._log, - )) + )); if (this.session.dcId !== dcId) { - this._log.info(`Exporting authorization for data center ${dc.ipAddress}`) - const auth = await this.invoke(new requests.auth.ExportAuthorization({ dcId: dcId })) + this._log.info(`Exporting authorization for data center ${dc.ipAddress}`); + const auth = await this.invoke(new requests.auth.ExportAuthorization({ dcId: dcId })); const req = this._initWith(new requests.auth.ImportAuthorization({ id: auth.id, bytes: auth.bytes, }, - )) - await sender.send(req) + )); + await sender.send(req); } - sender.dcId = dcId - return sender + sender.dcId = dcId; + return sender; } catch (e) { - await sender.disconnect() + await sender.disconnect(); } } - return null + return null; } // end region @@ -340,85 +347,85 @@ class TelegramClient { * @returns {Promise} */ async downloadFile(inputLocation, args = {}) { - return downloadFile(this, inputLocation, args) + return downloadFile(this, inputLocation, args); } async downloadMedia(messageOrMedia, args) { - let date - let media + let date; + let media; if (messageOrMedia instanceof constructors.Message) { - date = messageOrMedia.date - media = messageOrMedia.media + date = messageOrMedia.date; + media = messageOrMedia.media; } else { - date = new Date().getTime() - media = messageOrMedia + date = new Date().getTime(); + media = messageOrMedia; } if (typeof media == 'string') { - throw new Error('not implemented') + throw new Error('not implemented'); } if (media instanceof constructors.MessageMediaWebPage) { if (media.webpage instanceof constructors.WebPage) { - media = media.webpage.document || media.webpage.photo + media = media.webpage.document || media.webpage.photo; } } if (media instanceof constructors.MessageMediaPhoto || media instanceof constructors.Photo) { - return this._downloadPhoto(media, args) + return this._downloadPhoto(media, args); } else if (media instanceof constructors.MessageMediaDocument || media instanceof constructors.Document) { - return this._downloadDocument(media, args) + return this._downloadDocument(media, args); } else if (media instanceof constructors.MessageMediaContact) { - return this._downloadContact(media, args) + return this._downloadContact(media, args); } else if (media instanceof constructors.WebDocument || media instanceof constructors.WebDocumentNoProxy) { - return this._downloadWebDocument(media, args) + return this._downloadWebDocument(media, args); } } async downloadProfilePhoto(entity, isBig = false) { // ('User', 'Chat', 'UserFull', 'ChatFull') - const ENTITIES = [0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697] + const ENTITIES = [0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697]; // ('InputPeer', 'InputUser', 'InputChannel') // const INPUTS = [0xc91c90b6, 0xe669bf46, 0x40f202fd] // Todo account for input methods - const sizeType = isBig ? 'x' : 'm' - let photo + const sizeType = isBig ? 'x' : 'm'; + let photo; if (!(ENTITIES.includes(entity.SUBCLASS_OF_ID))) { - photo = entity + photo = entity; } else { if (!entity.photo) { // Special case: may be a ChatFull with photo:Photo if (!entity.chatPhoto) { - return null + return null; } return this._downloadPhoto( entity.chatPhoto, { sizeType }, - ) + ); } - photo = entity.photo + photo = entity.photo; } - let dcId - let loc + let dcId; + let loc; if (photo instanceof constructors.UserProfilePhoto || photo instanceof constructors.ChatPhoto) { - dcId = photo.dcId - const size = isBig ? photo.photoBig : photo.photoSmall + dcId = photo.dcId; + const size = isBig ? photo.photoBig : photo.photoSmall; loc = new constructors.InputPeerPhotoFileLocation({ peer: utils.getInputPeer(entity), localId: size.localId, volumeId: size.volumeId, big: isBig, - }) + }); } else { // It doesn't make any sense to check if `photo` can be used // as input location, because then this method would be able // to "download the profile photo of a message", i.e. its // media which should be done with `download_media` instead. - return null + return null; } try { return this.downloadFile(loc, { dcId: dcId, - }) + }); } catch (e) { // TODO this should never raise throw e; @@ -440,65 +447,65 @@ class TelegramClient { async downloadStickerSetThumb(stickerSet) { if (!stickerSet.thumb || !stickerSet.thumb.location) { - return undefined + return undefined; } - const { location } = stickerSet.thumb + const { location } = stickerSet.thumb; return this.downloadFile( new constructors.InputStickerSetThumb({ stickerset: new constructors.InputStickerSetID({ id: stickerSet.id, - accessHash: stickerSet.accessHash + accessHash: stickerSet.accessHash, }), localId: location.localId, volumeId: location.volumeId, }), - { dcId: stickerSet.thumbDcId } - ) + { dcId: stickerSet.thumbDcId }, + ); } _pickFileSize(sizes, sizeType) { if (!sizeType || !sizes || !sizes.length) { - return null + return null; } - const indexOfSize = sizeTypes.indexOf(sizeType) - let size + const indexOfSize = sizeTypes.indexOf(sizeType); + let size; for (let i = indexOfSize; i < sizeTypes.length; i++) { - size = sizes.find((s) => s.type === sizeTypes[i]) + size = sizes.find((s) => s.type === sizeTypes[i]); if (size) { - return size + return size; } } - return null + return null; } _downloadCachedPhotoSize(size) { // No need to download anything, simply write the bytes - let data + let data; if (size instanceof constructors.PhotoStrippedSize) { - data = utils.strippedPhotoToJpg(size.bytes) + data = utils.strippedPhotoToJpg(size.bytes); } else { - data = size.bytes + data = size.bytes; } - return data + return data; } async _downloadPhoto(photo, args) { if (photo instanceof constructors.MessageMediaPhoto) { - photo = photo.photo + photo = photo.photo; } if (!(photo instanceof constructors.Photo)) { - return + return; } - const size = this._pickFileSize(photo.sizes, args.sizeType) + const size = this._pickFileSize(photo.sizes, args.sizeType); if (!size || (size instanceof constructors.PhotoSizeEmpty)) { - return + return; } if (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize) { - return this._downloadCachedPhotoSize(size) + return this._downloadCachedPhotoSize(size); } return this.downloadFile( new constructors.InputPhotoFileLocation({ @@ -512,26 +519,26 @@ class TelegramClient { fileSize: size.size, progressCallback: args.progressCallback, }, - ) + ); } async _downloadDocument(doc, args) { if (doc instanceof constructors.MessageMediaDocument) { - doc = doc.document + doc = doc.document; } if (!(doc instanceof constructors.Document)) { - return + return; } - let size = null + let size = null; if (args.sizeType) { - size = doc.thumbs ? this._pickFileSize(doc.thumbs, args.sizeType) : null + size = doc.thumbs ? this._pickFileSize(doc.thumbs, args.sizeType) : null; if (!size && doc.mimeType.startsWith('video/')) { - return + return; } if (size && (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize)) { - return this._downloadCachedPhotoSize(size) + return this._downloadCachedPhotoSize(size); } } @@ -550,15 +557,15 @@ class TelegramClient { dcId: doc.dcId, workers: args.workers, }, - ) + ); } _downloadContact(media, args) { - throw new Error('not implemented') + throw new Error('not implemented'); } _downloadWebDocument(media, args) { - throw new Error('not implemented') + throw new Error('not implemented'); } // region Invoking Telegram request @@ -569,76 +576,76 @@ class TelegramClient { */ async invoke(request) { if (request.classType !== 'request') { - throw new Error('You can only invoke MTProtoRequests') + throw new Error('You can only invoke MTProtoRequests'); } // This causes issues for now because not enough utils // await request.resolve(this, utils) - this._lastRequest = new Date().getTime() - let attempt = 0 + this._lastRequest = new Date().getTime(); + let attempt = 0; for (attempt = 0; attempt < this._requestRetries; attempt++) { try { - const promise = this._sender.send(request) - const result = await promise + const promise = this._sender.send(request); + const result = await promise; //this.session.processEntities(result) // this._entityCache.add(result) - return result + return result; } catch (e) { if (e instanceof errors.ServerError || e.message === 'RPC_CALL_FAIL' || e.message === 'RPC_MCGET_FAIL') { - this._log.warn(`Telegram is having internal issues ${e.constructor.name}`) - await sleep(2000) + this._log.warn(`Telegram is having internal issues ${e.constructor.name}`); + await sleep(2000); } else if (e instanceof errors.FloodWaitError || e instanceof errors.FloodTestPhoneWaitError) { if (e.seconds <= this.floodSleepLimit) { - this._log.info(`Sleeping for ${e.seconds}s on flood wait`) - await sleep(e.seconds * 1000) + this._log.info(`Sleeping for ${e.seconds}s on flood wait`); + await sleep(e.seconds * 1000); } else { - throw e + throw e; } } else if (e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError || e instanceof errors.UserMigrateError) { - this._log.info(`Phone migrated to ${e.newDc}`) - const shouldRaise = e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError + this._log.info(`Phone migrated to ${e.newDc}`); + const shouldRaise = e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError; if (shouldRaise && await checkAuthorization(this)) { - throw e + throw e; } - await this._switchDC(e.newDc) + await this._switchDC(e.newDc); } else { - throw e + throw e; } } } - throw new Error(`Request was unsuccessful ${attempt} time(s)`) + throw new Error(`Request was unsuccessful ${attempt} time(s)`); } async getMe() { try { return (await this.invoke(new requests.users - .GetUsers({ id: [new constructors.InputUserSelf()] })))[0] + .GetUsers({ id: [new constructors.InputUserSelf()] })))[0]; } catch (e) { } } async start(authParams) { if (!this.isConnected()) { - await this.connect() + await this.connect(); } if (await checkAuthorization(this)) { - return + return; } const apiCredentials = { apiId: this.apiId, - apiHash: this.apiHash - } + apiHash: this.apiHash, + }; - await authFlow(this, apiCredentials, authParams) + await authFlow(this, apiCredentials, authParams); } uploadFile(fileParams) { - return uploadFile(this, fileParams) + return uploadFile(this, fileParams); } updateTwoFaSettings(params) { @@ -647,7 +654,7 @@ class TelegramClient { // event region addEventHandler(callback, event) { - this._eventBuilders.push([event, callback]) + this._eventBuilders.push([event, callback]); } _handleUpdate(update) { @@ -656,29 +663,29 @@ class TelegramClient { if (update instanceof constructors.Updates || update instanceof constructors.UpdatesCombined) { // TODO deal with entities - const entities = [] + const entities = []; for (const x of [...update.users, ...update.chats]) { - entities.push(x) + entities.push(x); } for (const u of update.updates) { - this._processUpdate(u, update.updates, entities) + this._processUpdate(u, update.updates, entities); } } else if (update instanceof constructors.UpdateShort) { - this._processUpdate(update.update, null) + this._processUpdate(update.update, null); } else { - this._processUpdate(update, null) + this._processUpdate(update, null); } // TODO add caching // this._stateCache.update(update) } _processUpdate(update, others, entities) { - update._entities = entities || [] + update._entities = entities || []; const args = { update: update, others: others, - } - this._dispatchUpdate(args) + }; + this._dispatchUpdate(args); } @@ -830,6 +837,7 @@ class TelegramClient { * @param peer * @returns {Promise<>} */ + /*CONTEST async getInputEntity(peer) { // Short-circuit if the input parameter directly maps to an InputPeer @@ -918,9 +926,9 @@ class TelegramClient { ptsDate: null, }) { for (const [builder, callback] of this._eventBuilders) { - const event = builder.build(args.update) + const event = builder.build(args.update); if (event) { - await callback(event) + await callback(event); } } } @@ -928,33 +936,34 @@ class TelegramClient { isConnected() { if (this._sender) { if (this._sender.isConnected()) { - return true + return true; } } - return false + return false; } } async function timeout(promise, ms) { return Promise.race([ promise, - Helpers.sleep(ms).then(() => Promise.reject(new Error('TIMEOUT'))), - ]) + Helpers.sleep(ms) + .then(() => Promise.reject(new Error('TIMEOUT'))), + ]); } async function attempts(cb, times, pause) { for (let i = 0; i < times; i++) { try { // We need to `return await` here so it can be caught locally - return await cb() + return await cb(); } catch (err) { if (i === times - 1) { - throw err + throw err; } - await Helpers.sleep(pause) + await Helpers.sleep(pause); } } } -module.exports = TelegramClient +module.exports = TelegramClient; diff --git a/src/lib/gramjs/client/auth.ts b/src/lib/gramjs/client/auth.ts index f2455d6da..f32d44382 100644 --- a/src/lib/gramjs/client/auth.ts +++ b/src/lib/gramjs/client/auth.ts @@ -169,7 +169,7 @@ async function signInUserWithQrCode( const inputPromise = (async () => { while (1) { - if (isScanningComplete){ + if (isScanningComplete) { break; } diff --git a/src/lib/gramjs/client/downloadFile.ts b/src/lib/gramjs/client/downloadFile.ts index b63951ada..675c92d18 100644 --- a/src/lib/gramjs/client/downloadFile.ts +++ b/src/lib/gramjs/client/downloadFile.ts @@ -4,13 +4,13 @@ import { getAppropriatedPartSize } from '../Utils'; import { sleep } from '../Helpers'; export interface progressCallback { + isCanceled?: boolean; + acceptsBuffer?: boolean; + ( progress: number, // Float between 0 and 1. ...args: any[] ): void; - - isCanceled?: boolean; - acceptsBuffer?: boolean; } export interface DownloadFileParams { diff --git a/src/lib/gramjs/client/uploadFile.ts b/src/lib/gramjs/client/uploadFile.ts index 0c33c8096..2ed9e940e 100644 --- a/src/lib/gramjs/client/uploadFile.ts +++ b/src/lib/gramjs/client/uploadFile.ts @@ -5,10 +5,10 @@ import { generateRandomBytes, readBigIntFromBuffer, sleep } from '../Helpers'; import { getAppropriatedPartSize } from '../Utils'; interface OnProgress { + isCanceled?: boolean; + // Float between 0 and 1. (progress: number): void; - - isCanceled?: boolean; } export interface UploadFileParams { diff --git a/src/lib/gramjs/crypto/AuthKey.js b/src/lib/gramjs/crypto/AuthKey.js index 316c77a48..b2a27f669 100644 --- a/src/lib/gramjs/crypto/AuthKey.js +++ b/src/lib/gramjs/crypto/AuthKey.js @@ -1,49 +1,54 @@ -const { sha1, toSignedLittleBuffer,readBufferFromBigInt, readBigIntFromBuffer } = require('../Helpers') -const BinaryReader = require('../extensions/BinaryReader') -const { sleep } = require('../Helpers') +const { + sha1, + toSignedLittleBuffer, + readBufferFromBigInt, + readBigIntFromBuffer, +} = require('../Helpers'); +const BinaryReader = require('../extensions/BinaryReader'); +const { sleep } = require('../Helpers'); class AuthKey { constructor(value, hash) { if (!hash || !value) { - return + return; } - this._key = value - this._hash = hash - const reader = new BinaryReader(hash) - this.auxHash = reader.readLong(false) - reader.read(4) - this.keyId = reader.readLong(false) + this._key = value; + this._hash = hash; + const reader = new BinaryReader(hash); + this.auxHash = reader.readLong(false); + reader.read(4); + this.keyId = reader.readLong(false); } async setKey(value) { if (!value) { - this._key = this.auxHash = this.keyId = this._hash = null - return + this._key = this.auxHash = this.keyId = this._hash = null; + return; } if (value instanceof AuthKey) { - this._key = value._key - this.auxHash = value.auxHash - this.keyId = value.keyId - this._hash = value._hash - return + this._key = value._key; + this.auxHash = value.auxHash; + this.keyId = value.keyId; + this._hash = value._hash; + return; } - this._key = value - this._hash = await sha1(this._key) - const reader = new BinaryReader(this._hash) - this.auxHash = reader.readLong(false) - reader.read(4) - this.keyId = reader.readLong(false) + this._key = value; + this._hash = await sha1(this._key); + const reader = new BinaryReader(this._hash); + this.auxHash = reader.readLong(false); + reader.read(4); + this.keyId = reader.readLong(false); } async waitForKey() { while (!this.keyId) { - await sleep(20) + await sleep(20); } } getKey() { - return this._key + return this._key; } // TODO : This doesn't really fit here, it's only used in authentication @@ -55,20 +60,21 @@ class AuthKey { * @returns {bigint} */ async calcNewNonceHash(newNonce, number) { - newNonce = toSignedLittleBuffer(newNonce, 32) - const n = Buffer.alloc(1) - n.writeUInt8(number, 0) + newNonce = toSignedLittleBuffer(newNonce, 32); + const n = Buffer.alloc(1); + n.writeUInt8(number, 0); const data = Buffer.concat([newNonce, - Buffer.concat([n, readBufferFromBigInt(this.auxHash, 8, true)])]) + Buffer.concat([n, readBufferFromBigInt(this.auxHash, 8, true)])]); // Calculates the message key from the given data - const shaData = (await sha1(data)).slice(4, 20) - return readBigIntFromBuffer(shaData, true, true) + const shaData = (await sha1(data)).slice(4, 20); + return readBigIntFromBuffer(shaData, true, true); } equals(other) { - return other instanceof this.constructor && this._key && other.getKey() && other.getKey().equals(this._key) + return other instanceof this.constructor && this._key && other.getKey() && other.getKey() + .equals(this._key); } } -module.exports = AuthKey +module.exports = AuthKey; diff --git a/src/lib/gramjs/crypto/CTR.js b/src/lib/gramjs/crypto/CTR.js index be84d9f48..262b2ef3b 100644 --- a/src/lib/gramjs/crypto/CTR.js +++ b/src/lib/gramjs/crypto/CTR.js @@ -1,17 +1,17 @@ -const crypto = require('./crypto') +const crypto = require('./crypto'); class CTR { constructor(key, iv) { if (!Buffer.isBuffer(key) || !Buffer.isBuffer(iv) || iv.length !== 16) { - throw new Error('Key and iv need to be a buffer') + throw new Error('Key and iv need to be a buffer'); } - this.cipher = crypto.createCipheriv('AES-256-CTR', key, iv) + this.cipher = crypto.createCipheriv('AES-256-CTR', key, iv); } encrypt(data) { - return Buffer.from(this.cipher.update(data)) + return Buffer.from(this.cipher.update(data)); } } -module.exports = CTR +module.exports = CTR; diff --git a/src/lib/gramjs/crypto/Factorizator.js b/src/lib/gramjs/crypto/Factorizator.js index cca5d75c7..0e0d226eb 100644 --- a/src/lib/gramjs/crypto/Factorizator.js +++ b/src/lib/gramjs/crypto/Factorizator.js @@ -1,5 +1,5 @@ -const BigInt = require('big-integer') -const { modExp } = require('../Helpers') +const BigInt = require('big-integer'); +const { modExp } = require('../Helpers'); class Factorizator { /** @@ -10,11 +10,11 @@ class Factorizator { */ static gcd(a, b) { while (b.neq(BigInt.zero)) { - let temp = b - b = a.remainder(b) - a = temp + let temp = b; + b = a.remainder(b); + a = temp; } - return a + return a; } /** @@ -23,57 +23,75 @@ class Factorizator { * @returns {{p: *, q: *}} */ static factorize(pq) { - if (pq.remainder(2).equals(BigInt.zero)) { - return { p: BigInt(2), q: pq.divide(BigInt(2)) } + if (pq.remainder(2) + .equals(BigInt.zero)) { + return { + p: BigInt(2), + q: pq.divide(BigInt(2)), + }; } - let y = BigInt.randBetween(BigInt(1),pq.minus(1)) - const c = BigInt.randBetween(BigInt(1),pq.minus(1)) - const m = BigInt.randBetween(BigInt(1),pq.minus(1)) + let y = BigInt.randBetween(BigInt(1), pq.minus(1)); + const c = BigInt.randBetween(BigInt(1), pq.minus(1)); + const m = BigInt.randBetween(BigInt(1), pq.minus(1)); - let g = BigInt.one - let r = BigInt.one - let q = BigInt.one - let x = BigInt.zero - let ys = BigInt.zero - let k + let g = BigInt.one; + let r = BigInt.one; + let q = BigInt.one; + let x = BigInt.zero; + let ys = BigInt.zero; + let k; while (g.eq(BigInt.one)) { - x = y - for (let i = 0; BigInt(i).lesser(r); i++) { - y = (modExp(y, BigInt(2), pq)).add(c).remainder(pq) + x = y; + for (let i = 0; BigInt(i) + .lesser(r); i++) { + y = (modExp(y, BigInt(2), pq)).add(c) + .remainder(pq); } - k = BigInt.zero + k = BigInt.zero; while (k.lesser(r) && g.eq(BigInt.one)) { - ys = y - let condition = BigInt.min(m, r.minus(k)) - for (let i = 0; BigInt(i).lesser(condition); i++) { - y = (modExp(y, BigInt(2), pq)).add(c).remainder(pq) - q = q.multiply(x.minus(y).abs()).remainder(pq) + ys = y; + let condition = BigInt.min(m, r.minus(k)); + for (let i = 0; BigInt(i) + .lesser(condition); i++) { + y = (modExp(y, BigInt(2), pq)).add(c) + .remainder(pq); + q = q.multiply(x.minus(y) + .abs()) + .remainder(pq); } - g = Factorizator.gcd(q, pq) - k = k.add(m) + g = Factorizator.gcd(q, pq); + k = k.add(m); } - r = r.multiply(2) + r = r.multiply(2); } if (g.eq(pq)) { while (true) { - ys = (modExp(ys, BigInt(2), pq)).add(c).remainder(pq) - g = Factorizator.gcd(x.minus(ys).abs(), pq) + ys = (modExp(ys, BigInt(2), pq)).add(c) + .remainder(pq); + g = Factorizator.gcd(x.minus(ys) + .abs(), pq); if (g.greater(1)) { - break + break; } } } - const p = g - q = pq.divide(g) - return p < q ? { p: p, q: q } : { p: q, q: p } + const p = g; + q = pq.divide(g); + return p < q ? { + p: p, + q: q, + } : { + p: q, + q: p, + }; } } -module.exports = Factorizator +module.exports = Factorizator; diff --git a/src/lib/gramjs/crypto/IGE.js b/src/lib/gramjs/crypto/IGE.js index 9e3cde551..0810ead02 100644 --- a/src/lib/gramjs/crypto/IGE.js +++ b/src/lib/gramjs/crypto/IGE.js @@ -1,10 +1,10 @@ -const Helpers = require("../Helpers"); +const Helpers = require('../Helpers'); -const {IGE:aes_ige} = require('@cryptography/aes'); +const { IGE: aes_ige } = require('@cryptography/aes'); class IGENEW { constructor(key, iv) { - this.ige = new aes_ige(key,iv); + this.ige = new aes_ige(key, iv); } /** @@ -22,9 +22,9 @@ class IGENEW { * @returns {Buffer} */ encryptIge(plainText) { - const padding = plainText.length % 16 + const padding = plainText.length % 16; if (padding) { - plainText = Buffer.concat([plainText, Helpers.generateRandomBytes(16 - padding)]) + plainText = Buffer.concat([plainText, Helpers.generateRandomBytes(16 - padding)]); } return Helpers.convertToLittle(this.ige.encrypt(plainText)); @@ -34,4 +34,4 @@ class IGENEW { } -module.exports = IGENEW +module.exports = IGENEW; diff --git a/src/lib/gramjs/crypto/RSA.js b/src/lib/gramjs/crypto/RSA.js index 8b36de0bb..19468e5d3 100644 --- a/src/lib/gramjs/crypto/RSA.js +++ b/src/lib/gramjs/crypto/RSA.js @@ -1,29 +1,39 @@ -const BigInt = require('big-integer') -const { readBigIntFromBuffer, readBufferFromBigInt, getByteArray, sha1, generateRandomBytes, modExp } = require('../Helpers') +const BigInt = require('big-integer'); +const { + readBigIntFromBuffer, + readBufferFromBigInt, + getByteArray, + sha1, + generateRandomBytes, + modExp, +} = require('../Helpers'); const PUBLIC_KEYS = [{ 'fingerprint': [40, 85, 94, 156, 117, 240, 61, 22, 65, 244, 169, 2, 33, 107, 232, 108, 2, 43, 180, 195], 'n': BigInt('24403446649145068056824081744112065346446136066297307473868293895086332508101251964919587745984311372853053253457835208829824428441874946556659953519213382748319518214765985662663680818277989736779506318868003755216402538945900388706898101286548187286716959100102939636333452457308619454821845196109544157601096359148241435922125602449263164512290854366930013825808102403072317738266383237191313714482187326643144603633877219028262697593882410403273959074350849923041765639673335775605842311578109726403165298875058941765362622936097839775380070572921007586266115476975819175319995527916042178582540628652481530373407'), - 'e': 65537 + 'e': 65537, }, { 'fingerprint': [140, 171, 9, 34, 146, 246, 166, 50, 10, 170, 229, 247, 155, 114, 28, 177, 29, 106, 153, 154], 'n': BigInt('25081407810410225030931722734886059247598515157516470397242545867550116598436968553551465554653745201634977779380884774534457386795922003815072071558370597290368737862981871277312823942822144802509055492512145589734772907225259038113414940384446493111736999668652848440655603157665903721517224934142301456312994547591626081517162758808439979745328030376796953660042629868902013177751703385501412640560275067171555763725421377065095231095517201241069856888933358280729674273422117201596511978645878544308102076746465468955910659145532699238576978901011112475698963666091510778777356966351191806495199073754705289253783'), - 'e': 65537 + 'e': 65537, }, { 'fingerprint': [243, 218, 109, 239, 16, 202, 176, 78, 167, 8, 255, 209, 120, 234, 205, 112, 111, 42, 91, 176], 'n': BigInt('22347337644621997830323797217583448833849627595286505527328214795712874535417149457567295215523199212899872122674023936713124024124676488204889357563104452250187725437815819680799441376434162907889288526863223004380906766451781702435861040049293189979755757428366240570457372226323943522935844086838355728767565415115131238950994049041950699006558441163206523696546297006014416576123345545601004508537089192869558480948139679182328810531942418921113328804749485349441503927570568778905918696883174575510385552845625481490900659718413892216221539684717773483326240872061786759868040623935592404144262688161923519030977'), - 'e': 65537 + 'e': 65537, }, { 'fingerprint': [128, 80, 214, 72, 77, 244, 98, 7, 201, 250, 37, 244, 227, 51, 96, 199, 182, 37, 224, 113], 'n': BigInt('24573455207957565047870011785254215390918912369814947541785386299516827003508659346069416840622922416779652050319196701077275060353178142796963682024347858398319926119639265555410256455471016400261630917813337515247954638555325280392998950756512879748873422896798579889820248358636937659872379948616822902110696986481638776226860777480684653756042166610633513404129518040549077551227082262066602286208338952016035637334787564972991208252928951876463555456715923743181359826124083963758009484867346318483872552977652588089928761806897223231500970500186019991032176060579816348322451864584743414550721639495547636008351'), - 'e': 65537 -}] + 'e': 65537, +}]; -const _serverKeys = {} +const _serverKeys = {}; -PUBLIC_KEYS.forEach(({ fingerprint, ...keyInfo }) => { - _serverKeys[readBigIntFromBuffer(fingerprint.slice(-8), true, true)] = keyInfo -}) +PUBLIC_KEYS.forEach(({ + fingerprint, + ...keyInfo +}) => { + _serverKeys[readBigIntFromBuffer(fingerprint.slice(-8), true, true)] = keyInfo; +}); /** * Encrypts the given data known the fingerprint to be used @@ -34,24 +44,24 @@ PUBLIC_KEYS.forEach(({ fingerprint, ...keyInfo }) => { * @returns {Buffer|*|undefined} the cipher text, or None if no key matching this fingerprint is found. */ async function encrypt(fingerprint, data) { - const key = _serverKeys[fingerprint] + const key = _serverKeys[fingerprint]; if (!key) { - return undefined + return undefined; } // len(sha1.digest) is always 20, so we're left with 255 - 20 - x padding - const rand = generateRandomBytes(235 - data.length) + const rand = generateRandomBytes(235 - data.length); - const toEncrypt = Buffer.concat([await sha1(data), data, rand]) + const toEncrypt = Buffer.concat([await sha1(data), data, rand]); // rsa module rsa.encrypt adds 11 bits for padding which we don't want // rsa module uses rsa.transform.bytes2int(to_encrypt), easier way: - const payload = readBigIntFromBuffer(toEncrypt, false) - const encrypted = modExp(payload, BigInt(key.e), key.n) + const payload = readBigIntFromBuffer(toEncrypt, false); + const encrypted = modExp(payload, BigInt(key.e), key.n); // rsa module uses transform.int2bytes(encrypted, keylength), easier: - return readBufferFromBigInt(encrypted, 256, false) + return readBufferFromBigInt(encrypted, 256, false); } module.exports = { encrypt, -} +}; diff --git a/src/lib/gramjs/crypto/crypto.js b/src/lib/gramjs/crypto/crypto.js index ebf622acf..29e17225d 100644 --- a/src/lib/gramjs/crypto/crypto.js +++ b/src/lib/gramjs/crypto/crypto.js @@ -1,24 +1,27 @@ const AES = require('@cryptography/aes').default; -const { i2ab, ab2i } = require('./converters'); +const { + i2ab, + ab2i, +} = require('./converters'); const { getWords } = require('./words'); class Counter { constructor(initialValue) { - this.setBytes(initialValue) + this.setBytes(initialValue); } setBytes(bytes) { - bytes = Buffer.from(bytes) - this._counter = bytes + bytes = Buffer.from(bytes); + this._counter = bytes; } increment() { for (let i = 15; i >= 0; i--) { if (this._counter[i] === 255) { - this._counter[i] = 0 + this._counter[i] = 0; } else { - this._counter[i]++ - break + this._counter[i]++; + break; } } } @@ -28,34 +31,34 @@ class CTR { constructor(key, counter) { if (!(counter instanceof Counter)) { - counter = new Counter(counter) + counter = new Counter(counter); } - this._counter = counter + this._counter = counter; - this._remainingCounter = null - this._remainingCounterIndex = 16 + this._remainingCounter = null; + this._remainingCounterIndex = 16; - this._aes = new AES(getWords(key)) + this._aes = new AES(getWords(key)); } update(plainText) { - return this.encrypt(plainText) + return this.encrypt(plainText); } encrypt(plainText) { - const encrypted = Buffer.from(plainText) + const encrypted = Buffer.from(plainText); for (let i = 0; i < encrypted.length; i++) { if (this._remainingCounterIndex === 16) { - this._remainingCounter = Buffer.from(i2ab(this._aes.encrypt(ab2i(this._counter._counter)))) - this._remainingCounterIndex = 0 - this._counter.increment() + this._remainingCounter = Buffer.from(i2ab(this._aes.encrypt(ab2i(this._counter._counter)))); + this._remainingCounterIndex = 0; + this._counter.increment(); } - encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++] + encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++]; } - return encrypted + return encrypted; } } @@ -64,7 +67,7 @@ function createDecipheriv(algorithm, key, iv) { if (algorithm.includes('ECB')) { throw new Error('Not supported'); } else { - return new CTR(key, iv) + return new CTR(key, iv); } } @@ -72,47 +75,49 @@ function createCipheriv(algorithm, key, iv) { if (algorithm.includes('ECB')) { throw new Error('Not supported'); } else { - return new CTR(key, iv) + return new CTR(key, iv); } } function randomBytes(count) { - const bytes = new Uint8Array(count) - crypto.getRandomValues(bytes) - return bytes + const bytes = new Uint8Array(count); + crypto.getRandomValues(bytes); + return bytes; } class Hash { constructor(algorithm) { - this.algorithm = algorithm + this.algorithm = algorithm; } update(data) { //We shouldn't be needing new Uint8Array but it doesn't //work without it - this.data = new Uint8Array(data) + this.data = new Uint8Array(data); } async digest() { if (this.algorithm === 'sha1') { - return Buffer.from(await self.crypto.subtle.digest('SHA-1', this.data)) + return Buffer.from(await self.crypto.subtle.digest('SHA-1', this.data)); } else if (this.algorithm === 'sha256') { - return Buffer.from(await self.crypto.subtle.digest('SHA-256', this.data)) + return Buffer.from(await self.crypto.subtle.digest('SHA-256', this.data)); } } } async function pbkdf2(password, salt, iterations) { const passwordKey = await crypto.subtle.importKey('raw', password, - {name: 'PBKDF2'}, false, ['deriveBits']) + { name: 'PBKDF2' }, false, ['deriveBits']); return Buffer.from(await crypto.subtle.deriveBits({ name: 'PBKDF2', - hash: 'SHA-512', salt, iterations + hash: 'SHA-512', + salt, + iterations, }, passwordKey, 512)); } function createHash(algorithm) { - return new Hash(algorithm) + return new Hash(algorithm); } module.exports = { @@ -120,5 +125,5 @@ module.exports = { createDecipheriv, randomBytes, createHash, - pbkdf2 -} + pbkdf2, +}; diff --git a/src/lib/gramjs/errors/Common.js b/src/lib/gramjs/errors/Common.js index 6e3ccac62..e0470e084 100644 --- a/src/lib/gramjs/errors/Common.js +++ b/src/lib/gramjs/errors/Common.js @@ -8,7 +8,7 @@ */ class ReadCancelledError extends Error { constructor() { - super('The read operation was cancelled.') + super('The read operation was cancelled.'); } } @@ -20,12 +20,12 @@ 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.length}`) + it should not be read. Remaining bytes: ${remaining.length}`); if (typeof alert !== 'undefined') { - alert(`Missing MTProto Entity: Please, make sure to add TL definition for ID ${invalidConstructorId}`) + alert(`Missing MTProto Entity: Please, make sure to add TL definition for ID ${invalidConstructorId}`); } - this.invalidConstructorId = invalidConstructorId - this.remaining = remaining + this.invalidConstructorId = invalidConstructorId; + this.remaining = remaining; } } @@ -35,9 +35,9 @@ class TypeNotFoundError extends Error { */ 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 + super(`Invalid checksum (${checksum} when ${validChecksum} was expected). This packet should be skipped.`); + this.checksum = checksum; + this.validChecksum = validChecksum; } } @@ -47,15 +47,15 @@ class InvalidChecksumError extends Error { */ class InvalidBufferError extends Error { constructor(payload) { - let code = null + let code = null; if (payload.length === 4) { - code = -payload.readInt32LE(0) - super(`Invalid response buffer (HTTP code ${code})`) + code = -payload.readInt32LE(0); + super(`Invalid response buffer (HTTP code ${code})`); } else { - super(`Invalid response buffer (too short ${payload})`) + super(`Invalid response buffer (too short ${payload})`); } - this.code = code - this.payload = payload + this.code = code; + this.payload = payload; } } @@ -65,9 +65,9 @@ class InvalidBufferError extends Error { class SecurityError extends Error { constructor(...args) { if (!args.length) { - args = ['A security check failed.'] + args = ['A security check failed.']; } - super(...args) + super(...args); } } @@ -77,7 +77,7 @@ class SecurityError extends Error { */ class CdnFileTamperedError extends SecurityError { constructor() { - super('The CDN file has been altered and its download cancelled.') + super('The CDN file has been altered and its download cancelled.'); } } @@ -123,15 +123,15 @@ class BadMessageError extends Error { 'the correct salt, and the message is to be re-sent with it).', 64: 'Invalid container.', - } + }; - constructor(request,code) { + constructor(request, code) { let errorMessage = BadMessageError.ErrorMessages[code] || - `Unknown error code (this should not happen): ${code}.` - errorMessage+= ` Caused by ${request.className}` - super(errorMessage) - this.message = errorMessage - this.code = code + `Unknown error code (this should not happen): ${code}.`; + errorMessage += ` Caused by ${request.className}`; + super(errorMessage); + this.message = errorMessage; + this.code = code; } } @@ -145,4 +145,4 @@ module.exports = { SecurityError, CdnFileTamperedError, BadMessageError, -} +}; diff --git a/src/lib/gramjs/errors/RPCBaseErrors.js b/src/lib/gramjs/errors/RPCBaseErrors.js index aae2ae341..8c6143c6f 100644 --- a/src/lib/gramjs/errors/RPCBaseErrors.js +++ b/src/lib/gramjs/errors/RPCBaseErrors.js @@ -7,18 +7,18 @@ class RPCError extends Error { 'RPCError {0}: {1}{2}' .replace('{0}', code) .replace('{1}', message) - .replace('{2}', RPCError._fmtRequest(request)) - ) - this.code = code - this.message = message + .replace('{2}', RPCError._fmtRequest(request)), + ); + this.code = code; + this.message = message; } static _fmtRequest(request) { // TODO fix this if (request) { - return ` (caused by ${request.className})` + return ` (caused by ${request.className})`; } else { - return '' + return ''; } } } @@ -28,9 +28,9 @@ class RPCError extends Error { */ class InvalidDCError extends RPCError { constructor(request, message, code) { - super(message, request, code) - this.code = code || 303 - this.message = message || 'ERROR_SEE_OTHER' + super(message, request, code); + this.code = code || 303; + this.message = message || 'ERROR_SEE_OTHER'; } } @@ -40,8 +40,8 @@ class InvalidDCError extends RPCError { * notified that the data must be corrected before the query is repeated. */ class BadRequestError extends RPCError { - code = 400 - message = 'BAD_REQUEST' + code = 400; + message = 'BAD_REQUEST'; } /** @@ -49,8 +49,8 @@ class BadRequestError extends RPCError { * to authorized users. */ class UnauthorizedError extends RPCError { - code = 401 - message = 'UNAUTHORIZED' + code = 401; + message = 'UNAUTHORIZED'; } /** @@ -58,16 +58,16 @@ class UnauthorizedError extends RPCError { * someone who has blacklisted the current user. */ class ForbiddenError extends RPCError { - code = 403 - message = 'FORBIDDEN' + 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' + code = 404; + message = 'NOT_FOUND'; } /** @@ -75,8 +75,8 @@ class NotFoundError extends RPCError { * AUTH_KEY_DUPLICATED which can cause the connection to fail. */ class AuthKeyError extends RPCError { - code = 406 - message = 'AUTH_KEY' + code = 406; + message = 'AUTH_KEY'; } /** @@ -86,8 +86,8 @@ class AuthKeyError extends RPCError { * phone number. */ class FloodError extends RPCError { - code = 420 - message = 'FLOOD' + code = 420; + message = 'FLOOD'; } /** @@ -96,8 +96,8 @@ class FloodError extends RPCError { * storage */ class ServerError extends RPCError { - code = 500 // Also witnessed as -500 - message = 'INTERNAL' + code = 500; // Also witnessed as -500 + message = 'INTERNAL'; } /** @@ -105,8 +105,8 @@ class ServerError extends RPCError { * call ``answerCallbackQuery`` will result in this "special" RPCError. */ class TimedOutError extends RPCError { - code = 503 // Only witnessed as -503 - message = 'Timeout' + code = 503; // Only witnessed as -503 + message = 'Timeout'; } module.exports = { @@ -120,4 +120,4 @@ module.exports = { FloodError, ServerError, TimedOutError, -} +}; diff --git a/src/lib/gramjs/errors/RPCErrorList.js b/src/lib/gramjs/errors/RPCErrorList.js index ff649ea96..651c0926c 100644 --- a/src/lib/gramjs/errors/RPCErrorList.js +++ b/src/lib/gramjs/errors/RPCErrorList.js @@ -1,75 +1,81 @@ -const { RPCError, InvalidDCError, FloodError, BadRequestError } = require('./RPCBaseErrors') +const { + RPCError, + InvalidDCError, + FloodError, + BadRequestError, +} = require('./RPCBaseErrors'); class UserMigrateError extends InvalidDCError { constructor(args) { - const newDc = Number(args.capture || 0) - super(`The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)) - this.message = `The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request) - this.newDc = newDc + const newDc = Number(args.capture || 0); + super(`The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)); + this.message = `The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request); + this.newDc = newDc; } } class PhoneMigrateError extends InvalidDCError { constructor(args) { - const newDc = Number(args.capture || 0) - super(`The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)) - this.message = `The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request) - this.newDc = newDc + const newDc = Number(args.capture || 0); + super(`The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)); + this.message = `The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request); + this.newDc = newDc; } } class SlowModeWaitError extends FloodError { constructor(args) { - const seconds = Number(args.capture || 0) - super(`A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request)) - this.message = `A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request) - this.seconds = seconds + const seconds = Number(args.capture || 0); + super(`A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request)); + this.message = `A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request); + this.seconds = seconds; } } class FloodWaitError extends FloodError { constructor(args) { - const seconds = Number(args.capture || 0) - super(`A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request)) - this.message = `A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request) - this.seconds = seconds + const seconds = Number(args.capture || 0); + super(`A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request)); + this.message = `A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request); + this.seconds = seconds; } } class FloodTestPhoneWaitError extends FloodError { constructor(args) { - const seconds = Number(args.capture || 0) - super(`A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request)) - this.message = `A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request) - this.seconds = seconds + const seconds = Number(args.capture || 0); + super(`A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request)); + this.message = `A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request); + this.seconds = seconds; } } class FileMigrateError extends InvalidDCError { constructor(args) { - const newDc = Number(args.capture || 0) - super(`The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request)) - this.message = `The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request) - this.newDc = newDc + const newDc = Number(args.capture || 0); + super(`The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request)); + this.message = `The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request); + this.newDc = newDc; } } class NetworkMigrateError extends InvalidDCError { constructor(args) { - const newDc = Number(args.capture || 0) - super(`The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)) - this.message = `The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request) - this.newDc = newDc + const newDc = Number(args.capture || 0); + super(`The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)); + this.message = `The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request); + this.newDc = newDc; } } + class EmailUnconfirmedError extends BadRequestError { constructor(args) { - const codeLength = Number(args.capture || 0) - super(`Email unconfirmed, the length of the code must be ${codeLength}` + RPCError._fmtRequest(args.request)) - this.message = `Email unconfirmed, the length of the code must be ${codeLength}` + RPCError._fmtRequest(args.request) - this.codeLength = codeLength + const codeLength = Number(args.capture || 0); + super(`Email unconfirmed, the length of the code must be ${codeLength}` + RPCError._fmtRequest(args.request)); + this.message = `Email unconfirmed, the length of the code must be ${codeLength}` + RPCError._fmtRequest(args.request); + this.codeLength = codeLength; } } @@ -83,7 +89,7 @@ const rpcErrorRe = [ [/NETWORK_MIGRATE_(\d+)/, NetworkMigrateError], [/EMAIL_UNCONFIRMED_(\d+)/, EmailUnconfirmedError], -] +]; module.exports = { rpcErrorRe, FileMigrateError, @@ -93,5 +99,5 @@ module.exports = { SlowModeWaitError, UserMigrateError, NetworkMigrateError, - EmailUnconfirmedError -} + EmailUnconfirmedError, +}; diff --git a/src/lib/gramjs/errors/index.js b/src/lib/gramjs/errors/index.js index 5459de764..c6e848f08 100644 --- a/src/lib/gramjs/errors/index.js +++ b/src/lib/gramjs/errors/index.js @@ -4,19 +4,22 @@ * @param request the request that caused this error * @constructor the RPCError as a Python exception that represents this error */ -const { RPCError } = require("./RPCBaseErrors") -const { rpcErrorRe } = require('./RPCErrorList') +const { RPCError } = require('./RPCBaseErrors'); +const { rpcErrorRe } = require('./RPCErrorList'); function RPCMessageToError(rpcError, request) { for (const [msgRegex, Cls] of rpcErrorRe) { - const m = rpcError.errorMessage.match(msgRegex) + const m = rpcError.errorMessage.match(msgRegex); if (m) { - const capture = m.length === 2 ? parseInt(m[1]) : null - return new Cls({ request: request, capture: capture }) + const capture = m.length === 2 ? parseInt(m[1]) : null; + return new Cls({ + request: request, + capture: capture, + }); } } - return new RPCError(rpcError.errorMessage, request) + return new RPCError(rpcError.errorMessage, request); } module.exports = { @@ -24,4 +27,4 @@ module.exports = { ...require('./Common'), ...require('./RPCBaseErrors'), ...require('./RPCErrorList'), -} +}; diff --git a/src/lib/gramjs/events/Raw.js b/src/lib/gramjs/events/Raw.js index bf9a243ac..b379b831d 100644 --- a/src/lib/gramjs/events/Raw.js +++ b/src/lib/gramjs/events/Raw.js @@ -1,21 +1,21 @@ -const { EventBuilder } = require('./common') +const { EventBuilder } = require('./common'); class Raw extends EventBuilder { constructor(args = { types: null, func: null, }) { - super() + super(); if (!args.types) { - this.types = true + this.types = true; } else { - this.types = args.types + this.types = args.types; } } build(update, others = null) { - return update + return update; } } -module.exports = Raw +module.exports = Raw; diff --git a/src/lib/gramjs/events/common.js b/src/lib/gramjs/events/common.js index 9f18f8236..bb3334d83 100644 --- a/src/lib/gramjs/events/common.js +++ b/src/lib/gramjs/events/common.js @@ -1,12 +1,14 @@ class EventBuilder { constructor(args = { - chats: null, blacklistChats: null, func: null, - }, + chats: null, + blacklistChats: null, + func: null, + }, ) { - this.chats = args.chats - this.blacklistChats = Boolean(args.blacklistChats) - this.resolved = false - this.func = args.func + this.chats = args.chats; + this.blacklistChats = Boolean(args.blacklistChats); + this.resolved = false; + this.func = args.func; } build(update, others = null) { @@ -19,4 +21,7 @@ class EventCommon { } -module.exports = { EventBuilder, EventCommon } +module.exports = { + EventBuilder, + EventCommon, +}; diff --git a/src/lib/gramjs/events/index.js b/src/lib/gramjs/events/index.js index d0da5d8cf..04a9c8c5d 100644 --- a/src/lib/gramjs/events/index.js +++ b/src/lib/gramjs/events/index.js @@ -1,8 +1,12 @@ -const NewMessage = require('./NewMessage') -const Raw = require('./Raw') +const NewMessage = require('./NewMessage'); +const Raw = require('./Raw'); class StopPropagation extends Error { } -module.exports = { NewMessage, StopPropagation, Raw } +module.exports = { + NewMessage, + StopPropagation, + Raw, +}; diff --git a/src/lib/gramjs/extensions/AsyncQueue.js b/src/lib/gramjs/extensions/AsyncQueue.js index a748be84e..f88255e0e 100644 --- a/src/lib/gramjs/extensions/AsyncQueue.js +++ b/src/lib/gramjs/extensions/AsyncQueue.js @@ -1,30 +1,30 @@ class AsyncQueue { constructor() { - this._queue = [] + this._queue = []; this.canGet = new Promise((resolve) => { - this.resolveGet = resolve - }) - this.canPush = true + this.resolveGet = resolve; + }); + this.canPush = true; } async push(value) { - await this.canPush - this._queue.push(value) - this.resolveGet(true) + await this.canPush; + this._queue.push(value); + this.resolveGet(true); this.canPush = new Promise((resolve) => { - this.resolvePush = resolve - }) + this.resolvePush = resolve; + }); } async pop() { - await this.canGet - const returned = this._queue.pop() - this.resolvePush(true) + await this.canGet; + const returned = this._queue.pop(); + this.resolvePush(true); this.canGet = new Promise((resolve) => { - this.resolveGet = resolve - }) - return returned + this.resolveGet = resolve; + }); + return returned; } } -module.exports = AsyncQueue +module.exports = AsyncQueue; diff --git a/src/lib/gramjs/extensions/BinaryReader.js b/src/lib/gramjs/extensions/BinaryReader.js index 6a01a4a95..38e503efb 100644 --- a/src/lib/gramjs/extensions/BinaryReader.js +++ b/src/lib/gramjs/extensions/BinaryReader.js @@ -1,7 +1,7 @@ -const { TypeNotFoundError } = require('../errors/Common') -const { coreObjects } = require('../tl/core') -const { tlobjects } = require('../tl/AllTLObjects') -const { readBigIntFromBuffer } = require('../Helpers') +const { TypeNotFoundError } = require('../errors/Common'); +const { coreObjects } = require('../tl/core'); +const { tlobjects } = require('../tl/AllTLObjects'); +const { readBigIntFromBuffer } = require('../Helpers'); class BinaryReader { /** @@ -9,9 +9,9 @@ class BinaryReader { * @param data {Buffer} */ constructor(data) { - this.stream = data - this._last = null - this.offset = 0 + this.stream = data; + this._last = null; + this.offset = 0; } // region Reading @@ -22,7 +22,7 @@ class BinaryReader { * Reads a single byte value. */ readByte() { - return this.read(1)[0] + return this.read(1)[0]; } /** @@ -30,14 +30,14 @@ class BinaryReader { * @param signed {Boolean} */ readInt(signed = true) { - let res + let res; if (signed) { - res = this.stream.readInt32LE(this.offset) + res = this.stream.readInt32LE(this.offset); } else { - res = this.stream.readUInt32LE(this.offset) + res = this.stream.readUInt32LE(this.offset); } - this.offset += 4 - return res + this.offset += 4; + return res; } /** @@ -46,7 +46,7 @@ class BinaryReader { * @returns {BigInteger} */ readLong(signed = true) { - return this.readLargeInt(64, signed) + return this.readLargeInt(64, signed); } /** @@ -54,7 +54,8 @@ class BinaryReader { * @returns {number} */ readFloat() { - return this.read(4).readFloatLE(0) + return this.read(4) + .readFloatLE(0); } /** @@ -63,7 +64,8 @@ class BinaryReader { */ readDouble() { // was this a bug ? it should have been 0) { - padding = 4 - padding - this.read(padding) + padding = 4 - padding; + this.read(padding); } - return data + return data; } /** @@ -137,7 +139,8 @@ class BinaryReader { * @returns {string} */ tgReadString() { - return this.tgReadBytes().toString('utf-8') + return this.tgReadBytes() + .toString('utf-8'); } /** @@ -145,15 +148,15 @@ class BinaryReader { * @returns {boolean} */ tgReadBool() { - const value = this.readInt(false) + const value = this.readInt(false); if (value === 0x997275b5) { // boolTrue - return true + return true; } else if (value === 0xbc799737) { // boolFalse - return false + return false; } else { - throw new Error(`Invalid boolean code ${value.toString('16')}`) + throw new Error(`Invalid boolean code ${value.toString('16')}`); } } @@ -163,50 +166,50 @@ class BinaryReader { * @returns {Date} */ tgReadDate() { - const value = this.readInt() - return new Date(value * 1000) + const value = this.readInt(); + return new Date(value * 1000); } /** * Reads a Telegram object. */ tgReadObject() { - const constructorId = this.readInt(false) - let clazz = tlobjects[constructorId] + const constructorId = this.readInt(false); + let clazz = tlobjects[constructorId]; if (clazz === undefined) { /** * The class was None, but there's still a * chance of it being a manually parsed value like bool! */ - const value = constructorId + const value = constructorId; if (value === 0x997275b5) { // boolTrue - return true + return true; } else if (value === 0xbc799737) { // boolFalse - return false + return false; } else if (value === 0x1cb5c415) { // Vector - const temp = [] - const length = this.readInt() + const temp = []; + const length = this.readInt(); for (let i = 0; i < length; i++) { - temp.push(this.tgReadObject()) + temp.push(this.tgReadObject()); } - return temp + return temp; } - clazz = coreObjects[constructorId] + clazz = coreObjects[constructorId]; if (clazz === undefined) { // If there was still no luck, give up - this.seek(-4) // Go back - const pos = this.tellPosition() - const error = new TypeNotFoundError(constructorId, this.read()) - this.setPosition(pos) - throw error + this.seek(-4); // Go back + const pos = this.tellPosition(); + const error = new TypeNotFoundError(constructorId, this.read()); + this.setPosition(pos); + throw error; } } - return clazz.fromReader(this) + return clazz.fromReader(this); } /** @@ -215,14 +218,14 @@ class BinaryReader { */ tgReadVector() { if (this.readInt(false) !== 0x1cb5c415) { - throw new Error('Invalid constructor code, vector was expected') + throw new Error('Invalid constructor code, vector was expected'); } - const count = this.readInt() - const temp = [] + const count = this.readInt(); + const temp = []; for (let i = 0; i < count; i++) { - temp.push(this.tgReadObject()) + temp.push(this.tgReadObject()); } - return temp + return temp; } // endregion @@ -231,7 +234,7 @@ class BinaryReader { * Closes the reader. */ close() { - this.stream = null + this.stream = null; } // region Position related @@ -241,7 +244,7 @@ class BinaryReader { * @returns {number} */ tellPosition() { - return this.offset + return this.offset; } /** @@ -249,7 +252,7 @@ class BinaryReader { * @param position */ setPosition(position) { - this.offset = position + this.offset = position; } /** @@ -258,10 +261,10 @@ class BinaryReader { * @param offset */ seek(offset) { - this.offset += offset + this.offset += offset; } // endregion } -module.exports = BinaryReader +module.exports = BinaryReader; diff --git a/src/lib/gramjs/extensions/BinaryWriter.js b/src/lib/gramjs/extensions/BinaryWriter.js index 5db899ead..4fb4767c8 100644 --- a/src/lib/gramjs/extensions/BinaryWriter.js +++ b/src/lib/gramjs/extensions/BinaryWriter.js @@ -1,15 +1,15 @@ class BinaryWriter { constructor(stream) { - this._stream = stream + this._stream = stream; } write(buffer) { - this._stream = Buffer.concat([this._stream, buffer]) + this._stream = Buffer.concat([this._stream, buffer]); } getValue() { - return this._stream + return this._stream; } } -module.exports = BinaryWriter +module.exports = BinaryWriter; diff --git a/src/lib/gramjs/extensions/Logger.js b/src/lib/gramjs/extensions/Logger.js index 5e2c9dc82..c70fb1895 100644 --- a/src/lib/gramjs/extensions/Logger.js +++ b/src/lib/gramjs/extensions/Logger.js @@ -1,17 +1,17 @@ -let _level = null +let _level = null; class Logger { - static levels = ['error', 'warn', 'info', 'debug'] + static levels = ['error', 'warn', 'info', 'debug']; constructor(level) { if (!_level) { - _level = level || 'debug' + _level = level || 'debug'; } this.isBrowser = typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || - process.__nwjs + process.__nwjs; if (!this.isBrowser) { this.colors = { start: '\x1b[2m', @@ -20,7 +20,7 @@ class Logger { debug: '\x1b[36m', error: '\x1b[31m', end: '\x1b[0m', - } + }; } else { this.colors = { start: '%c', @@ -29,9 +29,13 @@ class Logger { debug: 'color : #00ffff', error: 'color : #ff0000', end: '', - } + }; } - this.messageFormat = '[%t] [%l] - [%m]' + this.messageFormat = '[%t] [%l] - [%m]'; + } + + static setLevel(level) { + _level = level; } /** @@ -40,45 +44,41 @@ class Logger { * @returns {boolean} */ canSend(level) { - return (Logger.levels.indexOf(_level) >= Logger.levels.indexOf(level)) + return (Logger.levels.indexOf(_level) >= Logger.levels.indexOf(level)); } /** * @param message {string} */ warn(message) { - this._log('warn', message, this.colors.warn) + this._log('warn', message, this.colors.warn); } /** * @param message {string} */ info(message) { - this._log('info', message, this.colors.info) + this._log('info', message, this.colors.info); } /** * @param message {string} */ debug(message) { - this._log('debug', message, this.colors.debug) + this._log('debug', message, this.colors.debug); } /** * @param message {string} */ error(message) { - this._log('error', message, this.colors.error) + this._log('error', message, this.colors.error); } format(message, level) { return this.messageFormat.replace('%t', new Date().toISOString()) .replace('%l', level.toUpperCase()) - .replace('%m', message) - } - - static setLevel(level) { - _level = level; + .replace('%m', message); } /** @@ -87,14 +87,14 @@ class Logger { * @param color {string} */ _log(level, message, color) { - if (!_level){ - return + if (!_level) { + return; } if (this.canSend(level)) { if (!this.isBrowser) { - console.log(color + this.format(message, level) + this.colors.end) + console.log(color + this.format(message, level) + this.colors.end); } else { - console.log(this.colors.start + this.format(message, level), color) + console.log(this.colors.start + this.format(message, level), color); } } else { @@ -102,4 +102,4 @@ class Logger { } } -module.exports = Logger +module.exports = Logger; diff --git a/src/lib/gramjs/extensions/MessagePacker.js b/src/lib/gramjs/extensions/MessagePacker.js index 8ef74a685..c94b371c1 100644 --- a/src/lib/gramjs/extensions/MessagePacker.js +++ b/src/lib/gramjs/extensions/MessagePacker.js @@ -1,112 +1,116 @@ -const MessageContainer = require('../tl/core/MessageContainer') -const TLMessage = require('../tl/core/TLMessage') -const BinaryWriter = require('../extensions/BinaryWriter') +const MessageContainer = require('../tl/core/MessageContainer'); +const TLMessage = require('../tl/core/TLMessage'); +const BinaryWriter = require('../extensions/BinaryWriter'); class MessagePacker { constructor(state, logger) { - this._state = state - this._queue = [] - this._pendingStates = [] + this._state = state; + this._queue = []; + this._pendingStates = []; this._ready = new Promise(((resolve) => { - this.setReady = resolve - })) - this._log = logger + this.setReady = resolve; + })); + this._log = logger; } values() { - return this._queue + return this._queue; } append(state) { - this._queue.push(state) - this.setReady(true) + this._queue.push(state); + this.setReady(true); if (state) { - this._pendingStates.push(state) + this._pendingStates.push(state); state.promise // Using finally causes triggering `unhandledrejection` event - .catch(() => {}) - .finally(() => { - this._pendingStates = this._pendingStates.filter((s) => s !== state) + .catch(() => { }) + .finally(() => { + this._pendingStates = this._pendingStates.filter((s) => s !== state); + }); } } extend(states) { for (const state of states) { - this._queue.push(state) + this._queue.push(state); } - this.setReady(true) + this.setReady(true); } async get() { if (!this._queue.length) { this._ready = new Promise(((resolve) => { - this.setReady = resolve - })) - await this._ready + this.setReady = resolve; + })); + await this._ready; } if (!this._queue[this._queue.length - 1]) { - this._queue = [] - return + this._queue = []; + return; } - let data - let buffer = new BinaryWriter(Buffer.alloc(0)) + let data; + let buffer = new BinaryWriter(Buffer.alloc(0)); - const batch = [] - let size = 0 + const batch = []; + let size = 0; while (this._queue.length && batch.length <= MessageContainer.MAXIMUM_LENGTH) { - const state = this._queue.shift() - size += state.data.length + TLMessage.SIZE_OVERHEAD + const state = this._queue.shift(); + size += state.data.length + TLMessage.SIZE_OVERHEAD; if (size <= MessageContainer.MAXIMUM_SIZE) { - let afterId + let afterId; if (state.after) { - afterId = state.after.msgId + afterId = state.after.msgId; } state.msgId = await this._state.writeDataAsMessage( buffer, state.data, state.request.classType === 'request', afterId, - ) - this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.className || state.request.constructor.name}`) - batch.push(state) - continue + ); + this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.className || state.request.constructor.name}`); + batch.push(state); + continue; } if (batch.length) { - this._queue.unshift(state) - break + this._queue.unshift(state); + break; } - this._log.warn(`Message payload for ${state.request.className || state.request.constructor.name} is too long ${state.data.length} and cannot be sent`) - state.reject('Request Payload is too big') - size = 0 - continue + this._log.warn(`Message payload for ${state.request.className || state.request.constructor.name} is too long ${state.data.length} and cannot be sent`); + state.reject('Request Payload is too big'); + size = 0; + } if (!batch.length) { - return null + return null; } if (batch.length > 1) { - const b = Buffer.alloc(8) - b.writeUInt32LE(MessageContainer.CONSTRUCTOR_ID, 0) - b.writeInt32LE(batch.length, 4) - data = Buffer.concat([b, buffer.getValue()]) - buffer = new BinaryWriter(Buffer.alloc(0)) + const b = Buffer.alloc(8); + b.writeUInt32LE(MessageContainer.CONSTRUCTOR_ID, 0); + b.writeInt32LE(batch.length, 4); + data = Buffer.concat([b, buffer.getValue()]); + buffer = new BinaryWriter(Buffer.alloc(0)); const containerId = await this._state.writeDataAsMessage( buffer, data, false, - ) + ); for (const s of batch) { - s.containerId = containerId + s.containerId = containerId; } } - data = buffer.getValue() - return { batch, data } + data = buffer.getValue(); + return { + batch, + data, + }; } rejectAll() { this._pendingStates.forEach((requestState) => { - requestState.reject(new Error('Disconnect')) - }) + requestState.reject(new Error('Disconnect')); + }); } } -module.exports = MessagePacker +module.exports = MessagePacker; diff --git a/src/lib/gramjs/extensions/PromisedWebSockets.js b/src/lib/gramjs/extensions/PromisedWebSockets.js index c41e7939e..a009367c6 100644 --- a/src/lib/gramjs/extensions/PromisedWebSockets.js +++ b/src/lib/gramjs/extensions/PromisedWebSockets.js @@ -1,9 +1,9 @@ -const Mutex = require('async-mutex').Mutex -const mutex = new Mutex() +const Mutex = require('async-mutex').Mutex; +const mutex = new Mutex(); -const WebSocketClient = require('websocket').w3cwebsocket +const WebSocketClient = require('websocket').w3cwebsocket; -const closeError = new Error('WebSocket was closed') +const closeError = new Error('WebSocket was closed'); class PromisedWebSockets { constructor() { @@ -14,117 +14,117 @@ class PromisedWebSockets { process.__nwjs */ - this.client = null - this.closed = true + this.client = null; + this.closed = true; } async readExactly(number) { - let readData = Buffer.alloc(0) + let readData = Buffer.alloc(0); while (true) { - const thisTime = await this.read(number) - readData = Buffer.concat([readData, thisTime]) - number = number - thisTime.length + const thisTime = await this.read(number); + readData = Buffer.concat([readData, thisTime]); + number = number - thisTime.length; if (!number) { - return readData + return readData; } } } async read(number) { if (this.closed) { - throw closeError + throw closeError; } - await this.canRead + await this.canRead; if (this.closed) { - throw closeError + throw closeError; } - const toReturn = this.stream.slice(0, number) - this.stream = this.stream.slice(number) + const toReturn = this.stream.slice(0, number); + this.stream = this.stream.slice(number); if (this.stream.length === 0) { this.canRead = new Promise((resolve) => { - this.resolveRead = resolve - }) + this.resolveRead = resolve; + }); } - return toReturn + return toReturn; } async readAll() { if (this.closed || !await this.canRead) { - throw closeError + throw closeError; } - const toReturn = this.stream - this.stream = Buffer.alloc(0) + const toReturn = this.stream; + this.stream = Buffer.alloc(0); this.canRead = new Promise((resolve) => { - this.resolveRead = resolve - }) + this.resolveRead = resolve; + }); - return toReturn + return toReturn; } getWebSocketLink(ip, port) { if (port === 443) { - return `wss://${ip}:${port}/apiws` + return `wss://${ip}:${port}/apiws`; } else { - return `ws://${ip}:${port}/apiws` + return `ws://${ip}:${port}/apiws`; } } async connect(port, ip) { - this.stream = Buffer.alloc(0) + this.stream = Buffer.alloc(0); this.canRead = new Promise((resolve) => { - this.resolveRead = resolve - }) - this.closed = false - this.website = this.getWebSocketLink(ip, port) - this.client = new WebSocketClient(this.website, 'binary') + this.resolveRead = resolve; + }); + this.closed = false; + this.website = this.getWebSocketLink(ip, port); + this.client = new WebSocketClient(this.website, 'binary'); return new Promise((resolve, reject) => { this.client.onopen = () => { - this.receive() - resolve(this) - } + this.receive(); + resolve(this); + }; this.client.onerror = (error) => { - reject(error) - } + reject(error); + }; this.client.onclose = () => { - this.resolveRead(false) - this.closed = true - } + this.resolveRead(false); + this.closed = true; + }; //CONTEST // Seems to not be working, at least in a web worker self.addEventListener('offline', async () => { - await this.close() - this.resolveRead(false) - }) - }) + await this.close(); + this.resolveRead(false); + }); + }); } write(data) { if (this.closed) { - throw closeError + throw closeError; } - this.client.send(data) + this.client.send(data); } async close() { - await this.client.close() - this.closed = true + await this.client.close(); + this.closed = true; } async receive() { this.client.onmessage = async (message) => { - const release = await mutex.acquire() + const release = await mutex.acquire(); try { const data = message.data instanceof ArrayBuffer ? Buffer.from(message.data) - : Buffer.from(await new Response(message.data).arrayBuffer()) - this.stream = Buffer.concat([this.stream, data]) - this.resolveRead(true) + : Buffer.from(await new Response(message.data).arrayBuffer()); + this.stream = Buffer.concat([this.stream, data]); + this.resolveRead(true); } finally { - release() + release(); } - } + }; } } -module.exports = PromisedWebSockets +module.exports = PromisedWebSockets; diff --git a/src/lib/gramjs/extensions/index.js b/src/lib/gramjs/extensions/index.js index fde192c5b..d7ef2bb4b 100644 --- a/src/lib/gramjs/extensions/index.js +++ b/src/lib/gramjs/extensions/index.js @@ -1,9 +1,9 @@ -const Logger = require('./Logger') -const BinaryWriter = require('./BinaryWriter') -const BinaryReader = require('./BinaryReader') -const PromisedWebSockets = require('./PromisedWebSockets') -const MessagePacker = require('./MessagePacker') -const AsyncQueue = require('./AsyncQueue') +const Logger = require('./Logger'); +const BinaryWriter = require('./BinaryWriter'); +const BinaryReader = require('./BinaryReader'); +const PromisedWebSockets = require('./PromisedWebSockets'); +const MessagePacker = require('./MessagePacker'); +const AsyncQueue = require('./AsyncQueue'); module.exports = { BinaryWriter, BinaryReader, @@ -11,4 +11,4 @@ module.exports = { AsyncQueue, Logger, PromisedWebSockets, -} +}; diff --git a/src/lib/gramjs/index.js b/src/lib/gramjs/index.js index ff532b81d..ee11b1bc9 100644 --- a/src/lib/gramjs/index.js +++ b/src/lib/gramjs/index.js @@ -1,16 +1,25 @@ -const Api = require('./tl/api') -const TelegramClient = require('./client/TelegramClient') -const connection = require('./network') -const tl = require('./tl') -const version = require('./Version') -const events = require('./events') -const utils = require('./Utils') -const errors = require('./errors') -const sessions = require('./sessions') -const extensions = require('./extensions') -const helpers = require('./Helpers') +const Api = require('./tl/api'); +const TelegramClient = require('./client/TelegramClient'); +const connection = require('./network'); +const tl = require('./tl'); +const version = require('./Version'); +const events = require('./events'); +const utils = require('./Utils'); +const errors = require('./errors'); +const sessions = require('./sessions'); +const extensions = require('./extensions'); +const helpers = require('./Helpers'); module.exports = { - Api, TelegramClient, sessions, connection, extensions, - tl, version, events, utils, errors, helpers, -} + Api, + TelegramClient, + sessions, + connection, + extensions, + tl, + version, + events, + utils, + errors, + helpers, +}; diff --git a/src/lib/gramjs/network/Authenticator.js b/src/lib/gramjs/network/Authenticator.js index 37cb753cc..de28cf0c6 100644 --- a/src/lib/gramjs/network/Authenticator.js +++ b/src/lib/gramjs/network/Authenticator.js @@ -1,12 +1,15 @@ -const BigInt = require('big-integer') -const IGE = require('../crypto/IGE') -const AuthKey = require('../crypto/AuthKey') -const Factorizator = require('../crypto/Factorizator') -const RSA = require('../crypto/RSA') -const Helpers = require('../Helpers') -const { constructors, requests } = require('../tl') -const BinaryReader = require('../extensions/BinaryReader') -const { SecurityError } = require("../errors/Common") +const BigInt = require('big-integer'); +const IGE = require('../crypto/IGE'); +const AuthKey = require('../crypto/AuthKey'); +const Factorizator = require('../crypto/Factorizator'); +const RSA = require('../crypto/RSA'); +const Helpers = require('../Helpers'); +const { + constructors, + requests, +} = require('../tl'); +const BinaryReader = require('../extensions/BinaryReader'); +const { SecurityError } = require('../errors/Common'); /** * Executes the authentication process with the Telegram servers. @@ -16,31 +19,34 @@ const { SecurityError } = require("../errors/Common") */ async function doAuthentication(sender, log) { // Step 1 sending: PQ Request, endianness doesn't matter since it's random - let bytes = Helpers.generateRandomBytes(16) + let bytes = Helpers.generateRandomBytes(16); - const nonce = Helpers.readBigIntFromBuffer(bytes, false, true) + const nonce = Helpers.readBigIntFromBuffer(bytes, false, true); - const resPQ = await sender.send(new requests.ReqPqMulti({ nonce: nonce })) - log.debug('Starting authKey generation step 1') + const resPQ = await sender.send(new requests.ReqPqMulti({ nonce: nonce })); + log.debug('Starting authKey generation step 1'); if (!(resPQ instanceof constructors.ResPQ)) { - throw new Error(`Step 1 answer was ${resPQ}`) + throw new Error(`Step 1 answer was ${resPQ}`); } if (resPQ.nonce.neq(nonce)) { - throw new SecurityError('Step 1 invalid nonce from server') + throw new SecurityError('Step 1 invalid nonce from server'); } - const pq = Helpers.readBigIntFromBuffer(resPQ.pq, false, true) - log.debug('Finished authKey generation step 1') - log.debug('Starting authKey generation step 2') + const pq = Helpers.readBigIntFromBuffer(resPQ.pq, false, true); + log.debug('Finished authKey generation step 1'); + log.debug('Starting authKey generation step 2'); // Step 2 sending: DH Exchange - let { p, q } = Factorizator.factorize(pq) + let { + p, + q, + } = Factorizator.factorize(pq); // TODO Bring back after `Factorizator` fix. - p = Helpers.getByteArray(p) - q = Helpers.getByteArray(q) + p = Helpers.getByteArray(p); + q = Helpers.getByteArray(q); - bytes = Helpers.generateRandomBytes(32) - const newNonce = Helpers.readBigIntFromBuffer(bytes, true, true) + bytes = Helpers.generateRandomBytes(32); + const newNonce = Helpers.readBigIntFromBuffer(bytes, true, true); const pqInnerData = new constructors.PQInnerData({ pq: Helpers.getByteArray(pq), // unsigned @@ -49,20 +55,20 @@ async function doAuthentication(sender, log) { nonce: resPQ.nonce, serverNonce: resPQ.serverNonce, newNonce: newNonce, - }) + }); // sha_digest + data + random_bytes - let cipherText = null - let targetFingerprint = null + let cipherText = null; + let targetFingerprint = null; for (const fingerprint of resPQ.serverPublicKeyFingerprints) { - cipherText = await RSA.encrypt(fingerprint.toString(), pqInnerData.getBytes()) + cipherText = await RSA.encrypt(fingerprint.toString(), pqInnerData.getBytes()); if (cipherText !== null && cipherText !== undefined) { - targetFingerprint = fingerprint - break + targetFingerprint = fingerprint; + break; } } if (cipherText === null || cipherText === undefined) { - throw new SecurityError('Step 2 could not find a valid key for fingerprints') + throw new SecurityError('Step 2 could not find a valid key for fingerprints'); } const serverDhParams = await sender.send( @@ -74,58 +80,62 @@ async function doAuthentication(sender, log) { publicKeyFingerprint: targetFingerprint, encryptedData: cipherText, }), - ) + ); if (!(serverDhParams instanceof constructors.ServerDHParamsOk || serverDhParams instanceof constructors.ServerDHParamsFail)) { - throw new Error(`Step 2.1 answer was ${serverDhParams}`) + throw new Error(`Step 2.1 answer was ${serverDhParams}`); } if (serverDhParams.nonce.neq(resPQ.nonce)) { - throw new SecurityError('Step 2 invalid nonce from server') + throw new SecurityError('Step 2 invalid nonce from server'); } if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) { - throw new SecurityError('Step 2 invalid server nonce from server') + throw new SecurityError('Step 2 invalid server nonce from server'); } if (serverDhParams instanceof constructors.ServerDHParamsFail) { - const sh = await Helpers.sha1(Helpers.toSignedLittleBuffer(newNonce, 32).slice(4, 20)) - const nnh = Helpers.readBigIntFromBuffer(sh, true, true) + const sh = await Helpers.sha1(Helpers.toSignedLittleBuffer(newNonce, 32) + .slice(4, 20)); + const nnh = Helpers.readBigIntFromBuffer(sh, true, true); if (serverDhParams.newNonceHash.neq(nnh)) { - throw new SecurityError('Step 2 invalid DH fail nonce from server') + throw new SecurityError('Step 2 invalid DH fail nonce from server'); } } if (!(serverDhParams instanceof constructors.ServerDHParamsOk)) { - throw new Error(`Step 2.2 answer was ${serverDhParams}`) + throw new Error(`Step 2.2 answer was ${serverDhParams}`); } - log.debug('Finished authKey generation step 2') - log.debug('Starting authKey generation step 3') + log.debug('Finished authKey generation step 2'); + log.debug('Starting authKey generation step 3'); // Step 3 sending: Complete DH Exchange - const { key, iv } = await Helpers.generateKeyDataFromNonce(resPQ.serverNonce, newNonce) + const { + key, + iv, + } = await Helpers.generateKeyDataFromNonce(resPQ.serverNonce, newNonce); if (serverDhParams.encryptedAnswer.length % 16 !== 0) { // See PR#453 - throw new SecurityError('Step 3 AES block size mismatch') + throw new SecurityError('Step 3 AES block size mismatch'); } - const ige = new IGE(key,iv) - const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer) - const reader = new BinaryReader(plainTextAnswer) - reader.read(20) // hash sum - const serverDhInner = reader.tgReadObject() + const ige = new IGE(key, iv); + const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer); + const reader = new BinaryReader(plainTextAnswer); + reader.read(20); // hash sum + const serverDhInner = reader.tgReadObject(); if (!(serverDhInner instanceof constructors.ServerDHInnerData)) { - throw new Error(`Step 3 answer was ${serverDhInner}`) + throw new Error(`Step 3 answer was ${serverDhInner}`); } if (serverDhInner.nonce.neq(resPQ.nonce)) { - throw new SecurityError('Step 3 Invalid nonce in encrypted answer') + throw new SecurityError('Step 3 Invalid nonce in encrypted answer'); } if (serverDhInner.serverNonce.neq(resPQ.serverNonce)) { - throw new SecurityError('Step 3 Invalid server nonce in encrypted answer') + throw new SecurityError('Step 3 Invalid server nonce in encrypted answer'); } - const dhPrime = Helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false) - const ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false) - const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000) - const b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false) - const gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime) - const gab = Helpers.modExp(ga, b, dhPrime) + const dhPrime = Helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false); + const ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false); + const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000); + const b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false); + const gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime); + const gab = Helpers.modExp(ga, b, dhPrime); // Prepare client DH Inner Data const clientDhInner = new constructors.ClientDHInnerData({ @@ -133,49 +143,52 @@ async function doAuthentication(sender, log) { serverNonce: resPQ.serverNonce, retryId: 0, // TODO Actual retry ID gB: Helpers.getByteArray(gb, false), - }).getBytes() + }).getBytes(); - const clientDdhInnerHashed = Buffer.concat([await Helpers.sha1(clientDhInner), clientDhInner]) + const clientDdhInnerHashed = Buffer.concat([await Helpers.sha1(clientDhInner), clientDhInner]); // Encryption - const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed) + const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed); const dhGen = await sender.send( new requests.SetClientDHParams({ nonce: resPQ.nonce, serverNonce: resPQ.serverNonce, encryptedData: clientDhEncrypted, }), - ) - const nonceTypes = [constructors.DhGenOk, constructors.DhGenRetry, constructors.DhGenFail] + ); + const nonceTypes = [constructors.DhGenOk, constructors.DhGenRetry, constructors.DhGenFail]; if (!(dhGen instanceof nonceTypes[0] || dhGen instanceof nonceTypes[1] || dhGen instanceof nonceTypes[2])) { - throw new Error(`Step 3.1 answer was ${dhGen}`) + throw new Error(`Step 3.1 answer was ${dhGen}`); } - const { name } = dhGen.constructor + const { name } = dhGen.constructor; if (dhGen.nonce.neq(resPQ.nonce)) { - throw new SecurityError(`Step 3 invalid ${name} nonce from server`) + throw new SecurityError(`Step 3 invalid ${name} nonce from server`); } if (dhGen.serverNonce.neq(resPQ.serverNonce)) { - throw new SecurityError(`Step 3 invalid ${name} server nonce from server`) + throw new SecurityError(`Step 3 invalid ${name} server nonce from server`); } - const authKey = new AuthKey() - await authKey.setKey(Helpers.getByteArray(gab)) + const authKey = new AuthKey(); + await authKey.setKey(Helpers.getByteArray(gab)); - const nonceNumber = 1 + nonceTypes.indexOf(dhGen.constructor) + const nonceNumber = 1 + nonceTypes.indexOf(dhGen.constructor); - const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber) - const dhHash = dhGen[`newNonceHash${nonceNumber}`] + const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber); + const dhHash = dhGen[`newNonceHash${nonceNumber}`]; if (dhHash.neq(newNonceHash)) { - throw new SecurityError('Step 3 invalid new nonce hash') + throw new SecurityError('Step 3 invalid new nonce hash'); } if (!(dhGen instanceof constructors.DhGenOk)) { - throw new Error(`Step 3.2 answer was ${dhGen}`) + throw new Error(`Step 3.2 answer was ${dhGen}`); } - log.debug('Finished authKey generation step 3') + log.debug('Finished authKey generation step 3'); - return { authKey, timeOffset } + return { + authKey, + timeOffset, + }; } -module.exports = doAuthentication +module.exports = doAuthentication; diff --git a/src/lib/gramjs/network/MTProtoPlainSender.js b/src/lib/gramjs/network/MTProtoPlainSender.js index fe8a9ef5c..24bbe8e6e 100644 --- a/src/lib/gramjs/network/MTProtoPlainSender.js +++ b/src/lib/gramjs/network/MTProtoPlainSender.js @@ -2,12 +2,12 @@ * This module contains the class used to communicate with Telegram's servers * in plain text, when no authorization key has been created yet. */ -const Helpers = require('../Helpers') -const MTProtoState = require('./MTProtoState') -const BinaryReader = require('../extensions/BinaryReader') -const { InvalidBufferError } = require('../errors/Common') -const BigInt = require('big-integer') -const { toSignedLittleBuffer } = require("../Helpers") +const Helpers = require('../Helpers'); +const MTProtoState = require('./MTProtoState'); +const BinaryReader = require('../extensions/BinaryReader'); +const { InvalidBufferError } = require('../errors/Common'); +const BigInt = require('big-integer'); +const { toSignedLittleBuffer } = require('../Helpers'); /** * MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages) @@ -20,8 +20,8 @@ class MTProtoPlainSender { * @param loggers */ constructor(connection, loggers) { - this._state = new MTProtoState(connection, loggers) - this._connection = connection + this._state = new MTProtoState(connection, loggers); + this._connection = connection; } /** @@ -30,27 +30,27 @@ class MTProtoPlainSender { */ async send(request) { - let body = request.getBytes() - let msgId = this._state._getNewMsgId() - const m = toSignedLittleBuffer(msgId, 8) - const b = Buffer.alloc(4) - b.writeInt32LE(body.length, 0) + let body = request.getBytes(); + let msgId = this._state._getNewMsgId(); + const m = toSignedLittleBuffer(msgId, 8); + const b = Buffer.alloc(4); + b.writeInt32LE(body.length, 0); - const res = Buffer.concat([Buffer.concat([Buffer.alloc(8), m, b]), body]) + const res = Buffer.concat([Buffer.concat([Buffer.alloc(8), m, b]), body]); - await this._connection.send(res) - body = await this._connection.recv() + await this._connection.send(res); + body = await this._connection.recv(); if (body.length < 8) { - throw new InvalidBufferError(body) + throw new InvalidBufferError(body); } - const reader = new BinaryReader(body) - const authKeyId = reader.readLong() + const reader = new BinaryReader(body); + const authKeyId = reader.readLong(); if (authKeyId.neq(BigInt(0))) { - throw new Error('Bad authKeyId') + throw new Error('Bad authKeyId'); } - msgId = reader.readLong() + msgId = reader.readLong(); if (msgId.eq(BigInt(0))) { - throw new Error('Bad msgId') + 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 @@ -58,18 +58,18 @@ class MTProtoPlainSender { * be the case, which would cause endless assertion errors. */ - const length = reader.readInt() + const length = reader.readInt(); if (length <= 0) { - throw new Error('Bad length') + 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() + return reader.tgReadObject(); } } -module.exports = MTProtoPlainSender +module.exports = MTProtoPlainSender; diff --git a/src/lib/gramjs/network/MTProtoSender.js b/src/lib/gramjs/network/MTProtoSender.js index 3984faeef..e307cf59c 100644 --- a/src/lib/gramjs/network/MTProtoSender.js +++ b/src/lib/gramjs/network/MTProtoSender.js @@ -1,17 +1,22 @@ -const MtProtoPlainSender = require('./MTProtoPlainSender') -const MTProtoState = require('./MTProtoState') -const Helpers = require('../Helpers') -const AuthKey = require('../crypto/AuthKey') -const doAuthentication = require('./Authenticator') -const RPCResult = require('../tl/core/RPCResult') -const MessageContainer = require('../tl/core/MessageContainer') -const GZIPPacked = require('../tl/core/GZIPPacked') -const RequestState = require('./RequestState') -const { MsgsAck, upload, MsgsStateInfo, Pong } = require('../tl').constructors -const MessagePacker = require('../extensions/MessagePacker') -const BinaryReader = require('../extensions/BinaryReader') -const { UpdateConnectionState } = require("./index"); -const { BadMessageError } = require("../errors/Common") +const MtProtoPlainSender = require('./MTProtoPlainSender'); +const MTProtoState = require('./MTProtoState'); +const Helpers = require('../Helpers'); +const AuthKey = require('../crypto/AuthKey'); +const doAuthentication = require('./Authenticator'); +const RPCResult = require('../tl/core/RPCResult'); +const MessageContainer = require('../tl/core/MessageContainer'); +const GZIPPacked = require('../tl/core/GZIPPacked'); +const RequestState = require('./RequestState'); +const { + MsgsAck, + upload, + MsgsStateInfo, + Pong, +} = require('../tl').constructors; +const MessagePacker = require('../extensions/MessagePacker'); +const BinaryReader = require('../extensions/BinaryReader'); +const { UpdateConnectionState } = require('./index'); +const { BadMessageError } = require('../errors/Common'); const { BadServerSalt, BadMsgNotification, @@ -22,12 +27,12 @@ const { MsgsStateReq, MsgResendReq, MsgsAllInfo, -} = require('../tl').constructors -const { SecurityError } = require('../errors/Common') -const { InvalidBufferError } = require('../errors/Common') -const { LogOut } = require('../tl').requests.auth -const { RPCMessageToError } = require('../errors') -const { TypeNotFoundError } = require('../errors/Common') +} = require('../tl').constructors; +const { SecurityError } = require('../errors/Common'); +const { InvalidBufferError } = require('../errors/Common'); +const { LogOut } = require('../tl').requests.auth; +const { RPCMessageToError } = require('../errors'); +const { TypeNotFoundError } = require('../errors/Common'); /** @@ -55,24 +60,24 @@ class MTProtoSender { autoReconnectCallback: null, isMainSender: null, onConnectionBreak: null, - } + }; /** * @param authKey * @param opts */ constructor(authKey, opts) { - const args = { ...MTProtoSender.DEFAULT_OPTIONS, ...opts } - this._connection = null - this._log = args.logger - this._dcId = args.dcId - this._retries = args.retries - this._delay = args.delay - this._autoReconnect = args.autoReconnect - this._connectTimeout = args.connectTimeout - this._authKeyCallback = args.authKeyCallback - this._updateCallback = args.updateCallback - this._autoReconnectCallback = args.autoReconnectCallback + const args = { ...MTProtoSender.DEFAULT_OPTIONS, ...opts }; + this._connection = null; + this._log = args.logger; + this._dcId = args.dcId; + this._retries = args.retries; + this._delay = args.delay; + this._autoReconnect = args.autoReconnect; + this._connectTimeout = args.connectTimeout; + this._authKeyCallback = args.authKeyCallback; + this._updateCallback = args.updateCallback; + this._autoReconnectCallback = args.autoReconnectCallback; this._isMainSender = args.isMainSender; this._onConnectionBreak = args.onConnectionBreak; @@ -84,44 +89,44 @@ class MTProtoSender { * be cleared but on explicit user disconnection all the * pending futures should be cancelled. */ - this._user_connected = false - this._reconnecting = false - this._disconnected = true + this._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 + 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() - this._state = new MTProtoState(this.authKey, this._log) + this.authKey = authKey || new AuthKey(); + this._state = new MTProtoState(this.authKey, this._log); /** * Outgoing messages are put in a queue and sent in a batch. * Note that here we're also storing their ``_RequestState``. */ - this._send_queue = new MessagePacker(this._state, this._log) + this._send_queue = new MessagePacker(this._state, this._log); /** * Sent states are remembered until a response is received. */ - this._pending_state = {} + this._pending_state = {}; /** * Responses must be acknowledged, and we can also batch these. */ - this._pending_ack = new Set() + 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 = [] + this._last_acks = []; /** * Jump table from response ID to method that handles it @@ -142,7 +147,7 @@ class MTProtoSender { [MsgsStateReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this), [MsgResendReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this), [MsgsAllInfo.CONSTRUCTOR_ID]: this._handleMsgAll.bind(this), - } + }; } // Public API @@ -155,30 +160,30 @@ class MTProtoSender { */ async connect(connection, force) { if (this._user_connected && !force) { - this._log.info('User is already connected!') - return false + this._log.info('User is already connected!'); + return false; } - this._connection = connection + this._connection = connection; for (let attempt = 0; attempt < this._retries; attempt++) { try { - await this._connect() + await this._connect(); if (this._updateCallback) { - this._updateCallback(new UpdateConnectionState(UpdateConnectionState.connected)) + this._updateCallback(new UpdateConnectionState(UpdateConnectionState.connected)); } - break + break; } catch (err) { - if (this._updateCallback && attempt===0){ - this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected)) + if (this._updateCallback && attempt === 0) { + this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected)); } - this._log.error('WebSocket connection failed attempt: ' + (attempt + 1)) - await Helpers.sleep(this._delay) + this._log.error('WebSocket connection failed attempt: ' + (attempt + 1)); + await Helpers.sleep(this._delay); } } } isConnected() { - return this._user_connected + return this._user_connected; } /** @@ -186,7 +191,7 @@ class MTProtoSender { * all pending requests, and closes the send and receive loops. */ async disconnect() { - await this._disconnect() + await this._disconnect(); } /** @@ -216,12 +221,12 @@ class MTProtoSender { */ send(request) { if (!this._user_connected) { - throw new Error('Cannot send requests while disconnected') + throw new Error('Cannot send requests while disconnected'); } //CONTEST - const state = new RequestState(request) - this._send_queue.append(state) - return state.promise + const state = new RequestState(request); + this._send_queue.append(state); + return state.promise; /* if (!Helpers.isArrayLike(request)) { const state = new RequestState(request) @@ -240,18 +245,18 @@ class MTProtoSender { * @private */ async _connect() { - this._log.info('Connecting to {0}...'.replace('{0}', this._connection)) - await this._connection.connect() - this._log.debug('Connection success!') + this._log.info('Connecting to {0}...'.replace('{0}', this._connection)); + await this._connection.connect(); + this._log.debug('Connection success!'); //process.exit(0) if (!this.authKey.getKey()) { - const plain = new MtProtoPlainSender(this._connection, this._log) - this._log.debug('New auth_key attempt ...') - const res = await doAuthentication(plain, this._log) - this._log.debug('Generated new auth_key successfully') - await this.authKey.setKey(res.authKey) + const plain = new MtProtoPlainSender(this._connection, this._log); + this._log.debug('New auth_key attempt ...'); + const res = await doAuthentication(plain, this._log); + this._log.debug('Generated new auth_key successfully'); + await this.authKey.setKey(res.authKey); - this._state.time_offset = res.timeOffset + this._state.time_offset = res.timeOffset; /** * This is *EXTREMELY* important since we don't control @@ -260,41 +265,41 @@ class MTProtoSender { * switch to different data centers. */ if (this._authKeyCallback) { - await this._authKeyCallback(this.authKey, this._dcId) + await this._authKeyCallback(this.authKey, this._dcId); } } else { - this._log.debug('Already have an auth key ...') + this._log.debug('Already have an auth key ...'); } - this._user_connected = true - this._reconnecting = false + this._user_connected = true; + this._reconnecting = false; - this._log.debug('Starting send loop') - this._send_loop_handle = this._sendLoop() + this._log.debug('Starting send loop'); + this._send_loop_handle = this._sendLoop(); - this._log.debug('Starting receive loop') - this._recv_loop_handle = this._recvLoop() + this._log.debug('Starting receive loop'); + this._recv_loop_handle = this._recvLoop(); // _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!'.replace('%s', this._connection.toString())) + this._log.info('Connection to %s complete!'.replace('%s', this._connection.toString())); } async _disconnect(error = null) { - this._send_queue.rejectAll() + this._send_queue.rejectAll(); if (this._connection === null) { - this._log.info('Not disconnecting (already have no connection)') - return + this._log.info('Not disconnecting (already have no connection)'); + return; } - if (this._updateCallback){ - this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected)) + if (this._updateCallback) { + this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected)); } - this._log.info('Disconnecting from %s...'.replace('%s', this._connection.toString())) - this._user_connected = false - this._log.debug('Closing current connection...') - await this._connection.disconnect() + this._log.info('Disconnecting from %s...'.replace('%s', this._connection.toString())); + this._user_connected = false; + this._log.debug('Closing current connection...'); + await this._connection.disconnect(); } /** @@ -305,92 +310,92 @@ class MTProtoSender { * @private */ async _sendLoop() { - this._send_queue = new MessagePacker(this._state, this._log) + this._send_queue = new MessagePacker(this._state, this._log); while (this._user_connected && !this._reconnecting) { if (this._pending_ack.size) { - const ack = new RequestState(new MsgsAck({ msgIds: Array(...this._pending_ack) })) - this._send_queue.append(ack) - this._last_acks.push(ack) - this._pending_ack.clear() + const ack = new RequestState(new MsgsAck({ msgIds: Array(...this._pending_ack) })); + this._send_queue.append(ack); + this._last_acks.push(ack); + this._pending_ack.clear(); } - this._log.debug('Waiting for messages to send...'+this._reconnecting) + this._log.debug('Waiting for messages to send...' + this._reconnecting); // TODO Wait for the connection send queue to be empty? // This means that while it's not empty we can wait for // more messages to be added to the send queue. - const res = await this._send_queue.get() + const res = await this._send_queue.get(); if (this._reconnecting) { - return + return; } if (!res) { - continue + continue; } - let data = res.data - const batch = res.batch - this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`) + let data = res.data; + const batch = res.batch; + this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`); - data = await this._state.encryptMessageData(data) + data = await this._state.encryptMessageData(data); try { - await this._connection.send(data) + await this._connection.send(data); } catch (e) { - this._log.error(e) - this._log.info('Connection closed while sending data') - return + this._log.error(e); + this._log.info('Connection closed while sending data'); + return; } for (const state of batch) { if (!Array.isArray(state)) { if (state.request.classType === 'request') { - this._pending_state[state.msgId] = state + this._pending_state[state.msgId] = state; } } else { for (const s of state) { if (s.request.classType === 'request') { - this._pending_state[s.msgId] = s + this._pending_state[s.msgId] = s; } } } } - this._log.debug('Encrypted messages put in a queue to be sent') + this._log.debug('Encrypted messages put in a queue to be sent'); } } async _recvLoop() { - let body - let message + let body; + let message; while (this._user_connected && !this._reconnecting) { // this._log.debug('Receiving items from the network...'); - this._log.debug('Receiving items from the network...') + this._log.debug('Receiving items from the network...'); try { - body = await this._connection.recv() + body = await this._connection.recv(); } catch (e) { // this._log.info('Connection closed while receiving data'); - this._log.warn('Connection closed while receiving data') - this.reconnect() - return + this._log.warn('Connection closed while receiving data'); + this.reconnect(); + return; } try { - message = await this._state.decryptMessageData(body) + message = await this._state.decryptMessageData(body); } catch (e) { if (e instanceof TypeNotFoundError) { // Received object which we don't know how to deserialize - this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`) - continue + 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 + this._log.warn(`Security error while unpacking a received message: ${e}`); + continue; } else if (e instanceof InvalidBufferError) { - this._log.info('Broken authorization key; resetting') - if (this._updateCallback && this._isMainSender){ - this._updateCallback(new UpdateConnectionState(UpdateConnectionState.broken)) - } else if (this._onConnectionBreak && !this._isMainSender){ + this._log.info('Broken authorization key; resetting'); + if (this._updateCallback && this._isMainSender) { + this._updateCallback(new UpdateConnectionState(UpdateConnectionState.broken)); + } else if (this._onConnectionBreak && !this._isMainSender) { // Deletes the current sender from the object - this._onConnectionBreak(this._dcId) + this._onConnectionBreak(this._dcId); } // We don't really need to do this if we're going to sign in again @@ -402,19 +407,19 @@ class MTProtoSender { // We can disconnect at sign in /* await this.disconnect() */ - return + return; } else { - this._log.error('Unhandled error while receiving data') - this._log.error(e) - this.reconnect() - return + this._log.error('Unhandled error while receiving data'); + this._log.error(e); + this.reconnect(); + return; } } try { - await this._processMessage(message) + await this._processMessage(message); } catch (e) { - this._log.error('Unhandled error while receiving data') - this._log.error(e) + this._log.error('Unhandled error while receiving data'); + this._log.error(e); } } } @@ -430,15 +435,15 @@ class MTProtoSender { * @private */ async _processMessage(message) { - this._pending_ack.add(message.msgId) + this._pending_ack.add(message.msgId); // eslint-disable-next-line require-atomic-updates - message.obj = await message.obj - let handler = this._handlers[message.obj.CONSTRUCTOR_ID] + message.obj = await message.obj; + let handler = this._handlers[message.obj.CONSTRUCTOR_ID]; if (!handler) { - handler = this._handleUpdate.bind(this) + handler = this._handleUpdate.bind(this); } - await handler(message) + await handler(message); } /** @@ -449,36 +454,36 @@ class MTProtoSender { * @private */ _popStates(msgId) { - let state = this._pending_state[msgId] + let state = this._pending_state[msgId]; if (state) { - delete this._pending_state[msgId] - return [state] + delete this._pending_state[msgId]; + return [state]; } - const toPop = [] + const toPop = []; for (state of Object.values(this._pending_state)) { if (state.containerId && state.containerId.equals(msgId)) { - toPop.push(state.msgId) + toPop.push(state.msgId); } } if (toPop.length) { - const temp = [] + const temp = []; for (const x of toPop) { - temp.push(this._pending_state[x]) - delete this._pending_state[x] + temp.push(this._pending_state[x]); + delete this._pending_state[x]; } - return temp + return temp; } for (const ack of this._last_acks) { if (ack.msgId === msgId) { - return [ack] + return [ack]; } } - return [] + return []; } /** @@ -490,12 +495,12 @@ class MTProtoSender { * @private */ _handleRPCResult(message) { - const RPCResult = message.obj - const state = this._pending_state[RPCResult.reqMsgId] + const RPCResult = message.obj; + const state = this._pending_state[RPCResult.reqMsgId]; if (state) { - delete this._pending_state[RPCResult.reqMsgId] + delete this._pending_state[RPCResult.reqMsgId]; } - this._log.debug(`Handling RPC result for message ${RPCResult.reqMsgId}`) + this._log.debug(`Handling RPC result for message ${RPCResult.reqMsgId}`); if (!state) { // TODO We should not get responses to things we never sent @@ -503,29 +508,29 @@ class MTProtoSender { // See #658, #759 and #958. They seem to happen in a container // which contain the real response right after. try { - const reader = new BinaryReader(RPCResult.body) + const reader = new BinaryReader(RPCResult.body); if (!(reader.tgReadObject() instanceof upload.File)) { - throw new TypeNotFoundError('Not an upload.File') + throw new TypeNotFoundError('Not an upload.File'); } } catch (e) { - this._log.error(e) + this._log.error(e); if (e instanceof TypeNotFoundError) { - this._log.info(`Received response without parent request: ${RPCResult.body}`) - return + this._log.info(`Received response without parent request: ${RPCResult.body}`); + return; } else { - throw e + throw e; } } } if (RPCResult.error) { // eslint-disable-next-line new-cap - const error = RPCMessageToError(RPCResult.error, state.request) - this._send_queue.append(new RequestState(new MsgsAck({ msgIds: [state.msgId] }))) - state.reject(error) + const error = RPCMessageToError(RPCResult.error, state.request); + this._send_queue.append(new RequestState(new MsgsAck({ msgIds: [state.msgId] }))); + state.reject(error); } else { - const reader = new BinaryReader(RPCResult.body) - const read = state.request.readResult(reader) - state.resolve(read) + const reader = new BinaryReader(RPCResult.body); + const read = state.request.readResult(reader); + state.resolve(read); } } @@ -537,9 +542,9 @@ class MTProtoSender { * @private */ async _handleContainer(message) { - this._log.debug('Handling container') + this._log.debug('Handling container'); for (const innerMessage of message.obj.messages) { - await this._processMessage(innerMessage) + await this._processMessage(innerMessage); } } @@ -551,21 +556,21 @@ class MTProtoSender { * @private */ async _handleGzipPacked(message) { - this._log.debug('Handling gzipped data') - const reader = new BinaryReader(message.obj.data) - message.obj = reader.tgReadObject() - await this._processMessage(message) + this._log.debug('Handling gzipped data'); + const 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') - this._log.warn(`Note: ${message.obj.className} is not an update, not dispatching it`) - return + this._log.warn(`Note: ${message.obj.className} is not an update, not dispatching it`); + return; } - this._log.debug('Handling update ' + message.obj.className) + this._log.debug('Handling update ' + message.obj.className); if (this._updateCallback) { - this._updateCallback(message.obj) + this._updateCallback(message.obj); } } @@ -578,14 +583,14 @@ class MTProtoSender { * @private */ async _handlePong(message) { - const pong = message.obj - this._log.debug(`Handling pong for message ${pong.msgId}`) - const state = this._pending_state[pong.msgId] - delete this._pending_state[pong.msgId] + const pong = message.obj; + this._log.debug(`Handling pong for message ${pong.msgId}`); + const state = this._pending_state[pong.msgId]; + delete this._pending_state[pong.msgId]; // Todo Check result if (state) { - state.resolve(pong) + state.resolve(pong); } } @@ -599,12 +604,12 @@ class MTProtoSender { * @private */ async _handleBadServerSalt(message) { - const badSalt = message.obj - this._log.debug(`Handling bad salt for message ${badSalt.badMsgId}`) - this._state.salt = badSalt.newServerSalt - const states = this._popStates(badSalt.badMsgId) - this._send_queue.extend(states) - this._log.debug(`${states.length} message(s) will be resent`) + const badSalt = message.obj; + this._log.debug(`Handling bad salt for message ${badSalt.badMsgId}`); + this._state.salt = badSalt.newServerSalt; + const states = this._popStates(badSalt.badMsgId); + this._send_queue.extend(states); + this._log.debug(`${states.length} message(s) will be resent`); } /** @@ -617,32 +622,32 @@ class MTProtoSender { * @private */ async _handleBadNotification(message) { - const badMsg = message.obj - const states = this._popStates(badMsg.badMsgId) - this._log.debug(`Handling bad msg ${JSON.stringify(badMsg)}`) + const badMsg = message.obj; + const states = this._popStates(badMsg.badMsgId); + this._log.debug(`Handling bad msg ${JSON.stringify(badMsg)}`); if ([16, 17].includes(badMsg.errorCode)) { // Sent msg_id too low or too high (respectively). // Use the current msg_id to determine the right time offset. - const to = this._state.updateTimeOffset(message.msgId) - this._log.info(`System clock is wrong, set time offset to ${to}s`) + const 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 + 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 + this._state._sequence -= 16; } else { for (const state of states) { - state.reject(new BadMessageError(state.request, badMsg.errorCode)) + state.reject(new BadMessageError(state.request, badMsg.errorCode)); } - return + 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`) + this._send_queue.extend(states); + this._log.debug(`${states.length} messages will be resent due to bad msg`); } /** @@ -655,9 +660,9 @@ class MTProtoSender { */ async _handleDetailedInfo(message) { // TODO https://goo.gl/VvpCC6 - const msgId = message.obj.answerMsgId - this._log.debug(`Handling detailed info for message ${msgId}`) - this._pending_ack.add(msgId) + const msgId = message.obj.answerMsgId; + this._log.debug(`Handling detailed info for message ${msgId}`); + this._pending_ack.add(msgId); } /** @@ -670,9 +675,9 @@ class MTProtoSender { */ async _handleNewDetailedInfo(message) { // TODO https://goo.gl/VvpCC6 - const msgId = message.obj.answerMsgId - this._log.debug(`Handling new detailed info for message ${msgId}`) - this._pending_ack.add(msgId) + const msgId = message.obj.answerMsgId; + this._log.debug(`Handling new detailed info for message ${msgId}`); + this._pending_ack.add(msgId); } /** @@ -685,8 +690,8 @@ class MTProtoSender { */ async _handleNewSessionCreated(message) { // TODO https://goo.gl/LMyN7A - this._log.debug('Handling new session created') - this._state.salt = message.obj.serverSalt + this._log.debug('Handling new session created'); + this._state.salt = message.obj.serverSalt; } /** @@ -708,13 +713,13 @@ class MTProtoSender { * @private */ async _handleAck(message) { - const ack = message.obj - this._log.debug(`Handling acknowledge for ${ack.msgIds}`) + const ack = message.obj; + this._log.debug(`Handling acknowledge for ${ack.msgIds}`); for (const msgId of ack.msgIds) { - const state = this._pending_state[msgId] + const state = this._pending_state[msgId]; if (state && state.request instanceof LogOut) { - delete this._pending_state[msgId] - state.resolve(true) + delete this._pending_state[msgId]; + state.resolve(true); } } } @@ -731,12 +736,12 @@ class MTProtoSender { 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}`) - const state = this._pending_state[message.msgId] + this._log.debug(`Handling future salts for message ${message.msgId}`); + const state = this._pending_state[message.msgId]; if (state) { - delete this._pending_state[message] - state.resolve(message.obj) + delete this._pending_state[message]; + state.resolve(message.obj); } } @@ -749,8 +754,9 @@ class MTProtoSender { */ async _handleStateForgotten(message) { this._send_queue.append( - new RequestState(new MsgsStateInfo(message.msgId, String.fromCharCode(1).repeat(message.obj.msgIds))), - ) + new RequestState(new MsgsStateInfo(message.msgId, String.fromCharCode(1) + .repeat(message.obj.msgIds))), + ); } /** @@ -764,35 +770,35 @@ class MTProtoSender { async reconnect() { if (this._user_connected && !this._reconnecting) { - this._reconnecting = true + this._reconnecting = true; // TODO Should we set this? // this._user_connected = false - this._log.info("Started reconnecting") - this._reconnect() + this._log.info('Started reconnecting'); + this._reconnect(); } } async _reconnect() { - this._log.debug('Closing current connection...') + this._log.debug('Closing current connection...'); try { - await this.disconnect() + await this.disconnect(); } catch (err) { - this._log.warn(err) + this._log.warn(err); } - this._send_queue.append(null) - this._state.reset() + this._send_queue.append(null); + this._state.reset(); - await this.connect(this._connection, true) + await this.connect(this._connection, true); - this._reconnecting = false + this._reconnecting = false; // uncomment this if you want to resend //this._send_queue.extend(Object.values(this._pending_state)) - this._pending_state = {} + this._pending_state = {}; if (this._autoReconnectCallback) { - await this._autoReconnectCallback() + await this._autoReconnectCallback(); } } } -module.exports = MTProtoSender +module.exports = MTProtoSender; diff --git a/src/lib/gramjs/network/MTProtoState.js b/src/lib/gramjs/network/MTProtoState.js index 6ab5df99e..9f66f007c 100644 --- a/src/lib/gramjs/network/MTProtoState.js +++ b/src/lib/gramjs/network/MTProtoState.js @@ -1,13 +1,19 @@ -const Helpers = require('../Helpers') -const IGE = require('../crypto/IGE') -const BinaryReader = require('../extensions/BinaryReader') -const GZIPPacked = require('../tl/core/GZIPPacked') -const { TLMessage } = require('../tl/core') -const { SecurityError, InvalidBufferError } = require('../errors/Common') -const { InvokeAfterMsg } = require('../tl').requests -const BigInt = require('big-integer') -const { toSignedLittleBuffer,readBufferFromBigInt } = require("../Helpers") -const { readBigIntFromBuffer } = require("../Helpers") +const Helpers = require('../Helpers'); +const IGE = require('../crypto/IGE'); +const BinaryReader = require('../extensions/BinaryReader'); +const GZIPPacked = require('../tl/core/GZIPPacked'); +const { TLMessage } = require('../tl/core'); +const { + SecurityError, + InvalidBufferError, +} = require('../errors/Common'); +const { InvokeAfterMsg } = require('../tl').requests; +const BigInt = require('big-integer'); +const { + toSignedLittleBuffer, + readBufferFromBigInt, +} = require('../Helpers'); +const { readBigIntFromBuffer } = require('../Helpers'); class MTProtoState { /** @@ -36,13 +42,13 @@ class MTProtoState { * @param loggers */ constructor(authKey, loggers) { - this.authKey = authKey - this._log = loggers - this.timeOffset = 0 - this.salt = 0 + this.authKey = authKey; + this._log = loggers; + this.timeOffset = 0; + this.salt = 0; - this.id = this._sequence = this._lastMsgId = null - this.reset() + this.id = this._sequence = this._lastMsgId = null; + this.reset(); } /** @@ -50,9 +56,9 @@ class MTProtoState { */ reset() { // Session IDs can be random on every connection - this.id = Helpers.generateRandomLong(true) - this._sequence = 0 - this._lastMsgId = BigInt(0) + this.id = Helpers.generateRandomLong(true); + this._sequence = 0; + this._lastMsgId = BigInt(0); } /** @@ -61,7 +67,7 @@ class MTProtoState { * @param message */ updateMessageId(message) { - message.msgId = this._getNewMsgId() + message.msgId = this._getNewMsgId(); } /** @@ -72,14 +78,17 @@ class MTProtoState { * @returns {{iv: Buffer, key: Buffer}} */ async _calcKey(authKey, msgKey, client) { - const x = client === true ? 0 : 8 - const [sha256a , sha256b] = await Promise.all([ + const x = client === true ? 0 : 8; + const [sha256a, sha256b] = await Promise.all([ Helpers.sha256(Buffer.concat([msgKey, authKey.slice(x, x + 36)])), - Helpers.sha256(Buffer.concat([authKey.slice(x + 40, x + 76), msgKey])) - ]) - const key = Buffer.concat([sha256a.slice(0, 8), sha256b.slice(8, 24), sha256a.slice(24, 32)]) - const iv = Buffer.concat([sha256b.slice(0, 8), sha256a.slice(8, 24), sha256b.slice(24, 32)]) - return { key, iv } + Helpers.sha256(Buffer.concat([authKey.slice(x + 40, x + 76), msgKey])), + ]); + const key = Buffer.concat([sha256a.slice(0, 8), sha256b.slice(8, 24), sha256a.slice(24, 32)]); + const iv = Buffer.concat([sha256b.slice(0, 8), sha256a.slice(8, 24), sha256b.slice(24, 32)]); + return { + key, + iv, + }; } /** @@ -91,22 +100,22 @@ class MTProtoState { * @param afterId */ async writeDataAsMessage(buffer, data, contentRelated, afterId) { - const msgId = this._getNewMsgId() - const seqNo = this._getSeqNo(contentRelated) - let body + const msgId = this._getNewMsgId(); + const seqNo = this._getSeqNo(contentRelated); + let body; if (!afterId) { - body = await GZIPPacked.gzipIfSmaller(contentRelated, data) + body = await GZIPPacked.gzipIfSmaller(contentRelated, data); } else { - body = await GZIPPacked.gzipIfSmaller(contentRelated, new InvokeAfterMsg(afterId, data).getBytes()) + body = await GZIPPacked.gzipIfSmaller(contentRelated, new InvokeAfterMsg(afterId, data).getBytes()); } - const s = Buffer.alloc(4) - s.writeInt32LE(seqNo, 0) - const b = Buffer.alloc(4) - b.writeInt32LE(body.length, 0) - const m = toSignedLittleBuffer(msgId, 8) - buffer.write(Buffer.concat([m, s, b])) - buffer.write(body) - return msgId + const s = Buffer.alloc(4); + s.writeInt32LE(seqNo, 0); + const b = Buffer.alloc(4); + b.writeInt32LE(body.length, 0); + const m = toSignedLittleBuffer(msgId, 8); + buffer.write(Buffer.concat([m, s, b])); + buffer.write(body); + return msgId; } /** @@ -115,21 +124,25 @@ class MTProtoState { * @param data */ async encryptMessageData(data) { - await this.authKey.waitForKey() - const s = toSignedLittleBuffer(this.salt,8) - const i = toSignedLittleBuffer(this.id,8) - data = Buffer.concat([Buffer.concat([s,i]), data]) - const padding = Helpers.generateRandomBytes(Helpers.mod(-(data.length + 12), 16) + 12) + await this.authKey.waitForKey(); + const s = toSignedLittleBuffer(this.salt, 8); + const i = toSignedLittleBuffer(this.id, 8); + data = Buffer.concat([Buffer.concat([s, i]), data]); + const padding = Helpers.generateRandomBytes(Helpers.mod(-(data.length + 12), 16) + 12); // Being substr(what, offset, length); x = 0 for client // "msg_key_large = SHA256(substr(auth_key, 88+x, 32) + pt + padding)" - const msgKeyLarge = await Helpers.sha256(Buffer.concat([this.authKey.getKey().slice(88, 88 + 32), data, padding])) + const msgKeyLarge = await Helpers.sha256(Buffer.concat([this.authKey.getKey() + .slice(88, 88 + 32), data, padding])); // "msg_key = substr (msg_key_large, 8, 16)" - const msgKey = msgKeyLarge.slice(8, 24) + const msgKey = msgKeyLarge.slice(8, 24); - const { iv, key } = await this._calcKey(this.authKey.getKey(), msgKey, true) + const { + iv, + key, + } = await this._calcKey(this.authKey.getKey(), msgKey, true); - const keyId = Helpers.readBufferFromBigInt(this.authKey.keyId, 8) - return Buffer.concat([keyId, msgKey, new IGE(key,iv).encryptIge(Buffer.concat([data, padding]))]) + const keyId = Helpers.readBufferFromBigInt(this.authKey.keyId, 8); + return Buffer.concat([keyId, msgKey, new IGE(key, iv).encryptIge(Buffer.concat([data, padding]))]); } /** @@ -138,45 +151,49 @@ class MTProtoState { */ async decryptMessageData(body) { if (body.length < 8) { - throw new InvalidBufferError(body) + throw new InvalidBufferError(body); } // TODO Check salt,sessionId, and sequenceNumber - const keyId = Helpers.readBigIntFromBuffer(body.slice(0, 8)) + const keyId = Helpers.readBigIntFromBuffer(body.slice(0, 8)); if (keyId.neq(this.authKey.keyId)) { - throw new SecurityError('Server replied with an invalid auth key') + throw new SecurityError('Server replied with an invalid auth key'); } - const msgKey = body.slice(8, 24) - const { iv, key } = await this._calcKey(this.authKey.getKey(), msgKey, false) - body = new IGE(key,iv).decryptIge(body.slice(24)) + const msgKey = body.slice(8, 24); + const { + iv, + key, + } = await this._calcKey(this.authKey.getKey(), msgKey, false); + body = new IGE(key, iv).decryptIge(body.slice(24)); // https://core.telegram.org/mtproto/security_guidelines // Sections "checking sha256 hash" and "message length" - const ourKey = await Helpers.sha256(Buffer.concat([this.authKey.getKey().slice(96, 96 + 32), body])) + const ourKey = await Helpers.sha256(Buffer.concat([this.authKey.getKey() + .slice(96, 96 + 32), body])); if (!msgKey.equals(ourKey.slice(8, 24))) { - throw new SecurityError('Received msg_key doesn\'t match with expected one') + throw new SecurityError('Received msg_key doesn\'t match with expected one'); } - const reader = new BinaryReader(body) - reader.readLong() // removeSalt - const serverId = reader.readLong() + const reader = new BinaryReader(body); + reader.readLong(); // removeSalt + const serverId = reader.readLong(); if (serverId !== this.id) { // throw new SecurityError('Server replied with a wrong session ID'); } - const remoteMsgId = reader.readLong() - const remoteSequence = reader.readInt() - reader.readInt() // msgLen for the inner object, padding ignored + const remoteMsgId = reader.readLong(); + const remoteSequence = reader.readInt(); + reader.readInt(); // msgLen for the inner object, padding ignored // We could read msg_len bytes and use those in a new reader to read // the next TLObject without including the padding, but since the // reader isn't used for anything else after this, it's unnecessary. - const obj = reader.tgReadObject() + const obj = reader.tgReadObject(); - return new TLMessage(remoteMsgId, remoteSequence, obj) + return new TLMessage(remoteMsgId, remoteSequence, obj); } /** @@ -185,14 +202,16 @@ class MTProtoState { * @private */ _getNewMsgId() { - const now = new Date().getTime() / 1000 + this.timeOffset - const nanoseconds = Math.floor((now - Math.floor(now)) * 1e9) - let newMsgId = (BigInt(Math.floor(now)).shiftLeft(BigInt(32))).or(BigInt(nanoseconds).shiftLeft(BigInt(2))) + const now = new Date().getTime() / 1000 + this.timeOffset; + const nanoseconds = Math.floor((now - Math.floor(now)) * 1e9); + let newMsgId = (BigInt(Math.floor(now)) + .shiftLeft(BigInt(32))).or(BigInt(nanoseconds) + .shiftLeft(BigInt(2))); if (this._lastMsgId.greaterOrEquals(newMsgId)) { - newMsgId = this._lastMsgId.add(BigInt(4)) + newMsgId = this._lastMsgId.add(BigInt(4)); } - this._lastMsgId = newMsgId - return newMsgId + this._lastMsgId = newMsgId; + return newMsgId; } /** @@ -201,20 +220,20 @@ class MTProtoState { * @param correctMsgId {BigInteger} */ updateTimeOffset(correctMsgId) { - const bad = this._getNewMsgId() - const old = this.timeOffset - const now = Math.floor(new Date().getTime() / 1000) - const correct = correctMsgId.shiftRight(BigInt(32)) - this.timeOffset = correct - now + const bad = this._getNewMsgId(); + const old = this.timeOffset; + const now = Math.floor(new Date().getTime() / 1000); + const correct = correctMsgId.shiftRight(BigInt(32)); + this.timeOffset = correct - now; if (this.timeOffset !== old) { - this._lastMsgId = BigInt(0) + this._lastMsgId = BigInt(0); this._log.debug( `Updated time offset (old offset ${old}, bad ${bad}, good ${correctMsgId}, new ${this.timeOffset})`, - ) + ); } - return this.timeOffset + return this.timeOffset; } /** @@ -225,13 +244,13 @@ class MTProtoState { */ _getSeqNo(contentRelated) { if (contentRelated) { - const result = this._sequence * 2 + 1 - this._sequence += 1 - return result + const result = this._sequence * 2 + 1; + this._sequence += 1; + return result; } else { - return this._sequence * 2 + return this._sequence * 2; } } } -module.exports = MTProtoState +module.exports = MTProtoState; diff --git a/src/lib/gramjs/network/RequestState.js b/src/lib/gramjs/network/RequestState.js index 48997eadd..0d9297ad3 100644 --- a/src/lib/gramjs/network/RequestState.js +++ b/src/lib/gramjs/network/RequestState.js @@ -1,16 +1,16 @@ class RequestState { constructor(request, after = null) { - this.containerId = null - this.msgId = null - this.request = request - this.data = request.getBytes() - this.after = after - this.result = null + this.containerId = null; + this.msgId = null; + this.request = request; + this.data = request.getBytes(); + this.after = after; + this.result = null; this.promise = new Promise((resolve, reject) => { - this.resolve = resolve - this.reject = reject - }) + this.resolve = resolve; + this.reject = reject; + }); } } -module.exports = RequestState +module.exports = RequestState; diff --git a/src/lib/gramjs/network/connection/Connection.js b/src/lib/gramjs/network/connection/Connection.js index fc5265c11..faed5fb81 100644 --- a/src/lib/gramjs/network/connection/Connection.js +++ b/src/lib/gramjs/network/connection/Connection.js @@ -1,5 +1,5 @@ -const PromisedWebSockets = require('../../extensions/PromisedWebSockets') -const AsyncQueue = require('../../extensions/AsyncQueue') +const PromisedWebSockets = require('../../extensions/PromisedWebSockets'); +const AsyncQueue = require('../../extensions/AsyncQueue'); /** * The `Connection` class is a wrapper around ``asyncio.open_connection``. @@ -13,155 +13,155 @@ const AsyncQueue = require('../../extensions/AsyncQueue') * the client is disconnected (includes remote disconnections). */ class Connection { - PacketCodecClass = null + PacketCodecClass = null; constructor(ip, port, dcId, loggers) { - this._ip = ip - this._port = port - this._dcId = dcId - this._log = loggers - this._connected = false - this._sendTask = null - this._recvTask = null - this._codec = null - this._obfuscation = null // TcpObfuscated and MTProxy - this._sendArray = new AsyncQueue() - this._recvArray = new AsyncQueue() + this._ip = ip; + this._port = port; + this._dcId = dcId; + this._log = loggers; + this._connected = false; + this._sendTask = null; + this._recvTask = null; + this._codec = null; + this._obfuscation = null; // TcpObfuscated and MTProxy + this._sendArray = new AsyncQueue(); + this._recvArray = new AsyncQueue(); //this.socket = new PromiseSocket(new Socket()) - this.socket = new PromisedWebSockets() + this.socket = new PromisedWebSockets(); } async _connect() { - this._log.debug('Connecting') - this._codec = new this.PacketCodecClass(this) - await this.socket.connect(this._port, this._ip, this) - this._log.debug('Finished connecting') + this._log.debug('Connecting'); + this._codec = new this.PacketCodecClass(this); + await this.socket.connect(this._port, this._ip, this); + this._log.debug('Finished connecting'); // await this.socket.connect({host: this._ip, port: this._port}); - await this._initConn() + await this._initConn(); } async connect() { - await this._connect() - this._connected = true + await this._connect(); + this._connected = true; if (!this._sendTask) { - this._sendTask = this._sendLoop() + this._sendTask = this._sendLoop(); } - this._recvTask = this._recvLoop() + this._recvTask = this._recvLoop(); } async disconnect() { - this._connected = false - await this._recvArray.push(null) - await this.socket.close() + this._connected = false; + await this._recvArray.push(null); + await this.socket.close(); } async send(data) { if (!this._connected) { - throw new Error('Not connected') + throw new Error('Not connected'); } - await this._sendArray.push(data) + await this._sendArray.push(data); } async recv() { while (this._connected) { - const result = await this._recvArray.pop() + const result = await this._recvArray.pop(); // null = sentinel value = keep trying if (result) { - return result + return result; } } - throw new Error('Not connected') + throw new Error('Not connected'); } async _sendLoop() { // TODO handle errors try { while (this._connected) { - const data = await this._sendArray.pop() + const data = await this._sendArray.pop(); if (!data) { - this._sendTask = null - return + this._sendTask = null; + return; } - await this._send(data) + await this._send(data); } } catch (e) { - this._log.info('The server closed the connection while sending') + this._log.info('The server closed the connection while sending'); } } async _recvLoop() { - let data + let data; while (this._connected) { try { - data = await this._recv() + data = await this._recv(); if (!data) { - throw new Error("no data received") + throw new Error('no data received'); } } catch (e) { - this._log.info('connection closed') + this._log.info('connection closed'); //await this._recvArray.push() - this.disconnect() - return + this.disconnect(); + return; } - await this._recvArray.push(data) + await this._recvArray.push(data); } } async _initConn() { if (this._codec.tag) { - await this.socket.write(this._codec.tag) + await this.socket.write(this._codec.tag); } } async _send(data) { - const encodedPacket = this._codec.encodePacket(data) - this.socket.write(encodedPacket) + const encodedPacket = this._codec.encodePacket(data); + this.socket.write(encodedPacket); } async _recv() { - return await this._codec.readPacket(this.socket) + return await this._codec.readPacket(this.socket); } toString() { - return `${this._ip}:${this._port}/${this.constructor.name.replace('Connection', '')}` + return `${this._ip}:${this._port}/${this.constructor.name.replace('Connection', '')}`; } } class ObfuscatedConnection extends Connection { - ObfuscatedIO = null + ObfuscatedIO = null; async _initConn() { - this._obfuscation = new this.ObfuscatedIO(this) - this.socket.write(this._obfuscation.header) + this._obfuscation = new this.ObfuscatedIO(this); + this.socket.write(this._obfuscation.header); } _send(data) { - this._obfuscation.write(this._codec.encodePacket(data)) + this._obfuscation.write(this._codec.encodePacket(data)); } async _recv() { - return await this._codec.readPacket(this._obfuscation) + return await this._codec.readPacket(this._obfuscation); } } class PacketCodec { constructor(connection) { - this._conn = connection + this._conn = connection; } encodePacket(data) { - throw new Error('Not Implemented') + throw new Error('Not Implemented'); // Override } async readPacket(reader) { // override - throw new Error('Not Implemented') + throw new Error('Not Implemented'); } } @@ -169,4 +169,4 @@ module.exports = { Connection, PacketCodec, ObfuscatedConnection, -} +}; diff --git a/src/lib/gramjs/network/connection/TCPAbridged.js b/src/lib/gramjs/network/connection/TCPAbridged.js index f589897bf..c8b12dee9 100644 --- a/src/lib/gramjs/network/connection/TCPAbridged.js +++ b/src/lib/gramjs/network/connection/TCPAbridged.js @@ -1,38 +1,41 @@ -const { readBufferFromBigInt } = require('../../Helpers') -const { Connection, PacketCodec } = require('./Connection') -const BigInt = require('big-integer') +const { readBufferFromBigInt } = require('../../Helpers'); +const { + Connection, + PacketCodec, +} = require('./Connection'); +const BigInt = require('big-integer'); class AbridgedPacketCodec extends PacketCodec { - static tag = Buffer.from('ef', 'hex') - static obfuscateTag = Buffer.from('efefefef', 'hex') + static tag = Buffer.from('ef', 'hex'); + static obfuscateTag = Buffer.from('efefefef', 'hex'); constructor(props) { - super(props) - this.tag = AbridgedPacketCodec.tag - this.obfuscateTag = AbridgedPacketCodec.obfuscateTag + super(props); + this.tag = AbridgedPacketCodec.tag; + this.obfuscateTag = AbridgedPacketCodec.obfuscateTag; } encodePacket(data) { - let length = data.length >> 2 + let length = data.length >> 2; if (length < 127) { - const b = Buffer.alloc(1) - b.writeUInt8(length, 0) - length = b + const b = Buffer.alloc(1); + b.writeUInt8(length, 0); + length = b; } else { - length = Buffer.concat([Buffer.from('7f', 'hex'), readBufferFromBigInt(BigInt(length), 3)]) + length = Buffer.concat([Buffer.from('7f', 'hex'), readBufferFromBigInt(BigInt(length), 3)]); } - return Buffer.concat([length, data]) + return Buffer.concat([length, data]); } async readPacket(reader) { - const readData = await reader.read(1) - let length = readData[0] + const readData = await reader.read(1); + let length = readData[0]; if (length >= 127) { length = Buffer.concat([await reader.read(3), Buffer.alloc(1)]) - .readInt32LE(0) + .readInt32LE(0); } - return await reader.read(length << 2) + return await reader.read(length << 2); } } @@ -42,10 +45,10 @@ class AbridgedPacketCodec extends PacketCodec { * 508 bytes (127 << 2, which is very common). */ class ConnectionTCPAbridged extends Connection { - PacketCodecClass = AbridgedPacketCodec + PacketCodecClass = AbridgedPacketCodec; } module.exports = { ConnectionTCPAbridged, AbridgedPacketCodec, -} +}; diff --git a/src/lib/gramjs/network/connection/TCPObfuscated.js b/src/lib/gramjs/network/connection/TCPObfuscated.js index 1cfb3464e..312772ac9 100644 --- a/src/lib/gramjs/network/connection/TCPObfuscated.js +++ b/src/lib/gramjs/network/connection/TCPObfuscated.js @@ -1,78 +1,85 @@ -const { generateRandomBytes } = require('../../Helpers') -const { ObfuscatedConnection } = require('./Connection') -const { AbridgedPacketCodec } = require('./TCPAbridged') -const CTR = require('../../crypto/CTR') +const { generateRandomBytes } = require('../../Helpers'); +const { ObfuscatedConnection } = require('./Connection'); +const { AbridgedPacketCodec } = require('./TCPAbridged'); +const CTR = require('../../crypto/CTR'); class ObfuscatedIO { - header = null + header = null; constructor(connection) { - this.connection = connection.socket - const res = this.initHeader(connection.PacketCodecClass) - this.header = res.random + this.connection = connection.socket; + const res = this.initHeader(connection.PacketCodecClass); + this.header = res.random; - this._encrypt = res.encryptor - this._decrypt = res.decryptor + this._encrypt = res.encryptor; + this._decrypt = res.decryptor; } initHeader(packetCodec) { // Obfuscated messages secrets cannot start with any of these const keywords = [Buffer.from('50567247', 'hex'), Buffer.from('474554', 'hex'), - Buffer.from('504f5354', 'hex'), Buffer.from('eeeeeeee', 'hex')] - let random + Buffer.from('504f5354', 'hex'), Buffer.from('eeeeeeee', 'hex')]; + let random; // eslint-disable-next-line no-constant-condition while (true) { - random = generateRandomBytes(64) - if (random[0] !== 0xef && !(random.slice(4, 8).equals(Buffer.alloc(4)))) { - let ok = true + random = generateRandomBytes(64); + if (random[0] !== 0xef && !(random.slice(4, 8) + .equals(Buffer.alloc(4)))) { + let ok = true; for (const key of keywords) { if (key.equals(random.slice(0, 4))) { - ok = false - break + ok = false; + break; } } if (ok) { - break + break; } } } - random = random.toJSON().data + random = random.toJSON().data; - const randomReversed = Buffer.from(random.slice(8, 56)).reverse() + const randomReversed = Buffer.from(random.slice(8, 56)) + .reverse(); // Encryption has "continuous buffer" enabled - const encryptKey = Buffer.from(random.slice(8, 40)) - const encryptIv = Buffer.from(random.slice(40, 56)) - const decryptKey = Buffer.from(randomReversed.slice(0, 32)) - const decryptIv = Buffer.from(randomReversed.slice(32, 48)) - const encryptor = new CTR(encryptKey, encryptIv) - const decryptor = new CTR(decryptKey, decryptIv) + const encryptKey = Buffer.from(random.slice(8, 40)); + const encryptIv = Buffer.from(random.slice(40, 56)); + const decryptKey = Buffer.from(randomReversed.slice(0, 32)); + const decryptIv = Buffer.from(randomReversed.slice(32, 48)); + const encryptor = new CTR(encryptKey, encryptIv); + const decryptor = new CTR(decryptKey, decryptIv); random = Buffer.concat([ Buffer.from(random.slice(0, 56)), packetCodec.obfuscateTag, Buffer.from(random.slice(60)), - ]) + ]); random = Buffer.concat([ - Buffer.from(random.slice(0, 56)), Buffer.from(encryptor.encrypt(random).slice(56, 64)),Buffer.from(random.slice(64)) , - ]) - return { random, encryptor, decryptor } + Buffer.from(random.slice(0, 56)), Buffer.from(encryptor.encrypt(random) + .slice(56, 64)), Buffer.from(random.slice(64)), + ]); + return { + random, + encryptor, + decryptor, + }; } async read(n) { - const data = await this.connection.readExactly(n) - return this._decrypt.encrypt(data) + const data = await this.connection.readExactly(n); + return this._decrypt.encrypt(data); } write(data) { - this.connection.write(this._encrypt.encrypt(data)) + this.connection.write(this._encrypt.encrypt(data)); } } class ConnectionTCPObfuscated extends ObfuscatedConnection { - ObfuscatedIO = ObfuscatedIO - PacketCodecClass = AbridgedPacketCodec + ObfuscatedIO = ObfuscatedIO; + PacketCodecClass = AbridgedPacketCodec; } module.exports = { ConnectionTCPObfuscated, -} +}; diff --git a/src/lib/gramjs/network/connection/index.js b/src/lib/gramjs/network/connection/index.js index 71811e807..737623155 100644 --- a/src/lib/gramjs/network/connection/index.js +++ b/src/lib/gramjs/network/connection/index.js @@ -1,11 +1,11 @@ -const { Connection } = require('./Connection') -const { ConnectionTCPFull } = require('./TCPFull') -const { ConnectionTCPAbridged } = require('./TCPAbridged') -const { ConnectionTCPObfuscated } = require('./TCPObfuscated') +const { Connection } = require('./Connection'); +const { ConnectionTCPFull } = require('./TCPFull'); +const { ConnectionTCPAbridged } = require('./TCPAbridged'); +const { ConnectionTCPObfuscated } = require('./TCPObfuscated'); module.exports = { Connection, ConnectionTCPFull, ConnectionTCPAbridged, ConnectionTCPObfuscated, -} +}; diff --git a/src/lib/gramjs/network/index.js b/src/lib/gramjs/network/index.js index c0edbf809..5b6938527 100644 --- a/src/lib/gramjs/network/index.js +++ b/src/lib/gramjs/network/index.js @@ -1,14 +1,14 @@ -const MTProtoPlainSender = require('./MTProtoPlainSender') -const doAuthentication = require('./Authenticator') -const MTProtoSender = require('./MTProtoSender') +const MTProtoPlainSender = require('./MTProtoPlainSender'); +const doAuthentication = require('./Authenticator'); +const MTProtoSender = require('./MTProtoSender'); class UpdateConnectionState { - static disconnected = -1 - static connected = 1 - static broken = 0 + static disconnected = -1; + static connected = 1; + static broken = 0; constructor(state) { - this.state = state + this.state = state; } } @@ -17,7 +17,7 @@ const { ConnectionTCPFull, ConnectionTCPAbridged, ConnectionTCPObfuscated, -} = require('./connection') +} = require('./connection'); module.exports = { Connection, ConnectionTCPFull, @@ -27,4 +27,4 @@ module.exports = { doAuthentication, MTProtoSender, UpdateConnectionState, -} +}; diff --git a/src/lib/gramjs/sessions/Abstract.js b/src/lib/gramjs/sessions/Abstract.js index 5768e8dc7..e2f779d04 100644 --- a/src/lib/gramjs/sessions/Abstract.js +++ b/src/lib/gramjs/sessions/Abstract.js @@ -8,11 +8,49 @@ class Session { * @param toInstance {Session|null} * @returns {Session} */ + /* CONTEST clone(toInstance = null) { return toInstance || new this.constructor() }*/ + /** + * Returns the currently-used data center ID. + */ + get dcId() { + throw new Error('Not Implemented'); + } + + /** + * Returns the server address where the library should connect to. + */ + get serverAddress() { + throw new Error('Not Implemented'); + } + + /** + * Returns the port to which the library should connect to. + */ + get port() { + throw new Error('Not Implemented'); + } + + /** + * Returns an ``AuthKey`` instance associated with the saved + * data center, or `None` if a new one should be generated. + */ + get authKey() { + throw new Error('Not Implemented'); + } + + /** + * Sets the ``AuthKey`` to be used for the saved data center. + * @param value + */ + set authKey(value) { + throw new Error('Not Implemented'); + } + /** * Sets the information of the data center address and port that * the library should connect to, as well as the data center ID, @@ -22,44 +60,7 @@ class Session { * @param port {number} */ setDC(dcId, serverAddress, port) { - throw new Error('Not implemented') - } - - /** - * Returns the currently-used data center ID. - */ - get dcId() { - throw new Error('Not Implemented') - } - - /** - * Returns the server address where the library should connect to. - */ - get serverAddress() { - throw new Error('Not Implemented') - } - - /** - * Returns the port to which the library should connect to. - */ - get port() { - throw new Error('Not Implemented') - } - - /** - * Returns an ``AuthKey`` instance associated with the saved - * data center, or `None` if a new one should be generated. - */ - get authKey() { - throw new Error('Not Implemented') - } - - /** - * Sets the ``AuthKey`` to be used for the saved data center. - * @param value - */ - set authKey(value) { - throw new Error('Not Implemented') + throw new Error('Not implemented'); } /** @@ -112,6 +113,7 @@ class Session { * Called on client disconnection. Should be used to * free any used resources. Can be left empty if none. */ + /*CONTEST close() { @@ -124,7 +126,7 @@ class Session { * make persist the relevant session information to disk. */ save() { - throw new Error('Not Implemented') + throw new Error('Not Implemented'); } /** @@ -133,7 +135,7 @@ class Session { */ delete() { - throw new Error('Not Implemented') + throw new Error('Not Implemented'); } /** @@ -172,4 +174,4 @@ class Session { */ } -module.exports = Session +module.exports = Session; diff --git a/src/lib/gramjs/sessions/CacheApiSession.js b/src/lib/gramjs/sessions/CacheApiSession.js index de31929f4..99d7ca3bb 100644 --- a/src/lib/gramjs/sessions/CacheApiSession.js +++ b/src/lib/gramjs/sessions/CacheApiSession.js @@ -1,27 +1,27 @@ -const StorageSession = require('./StorageSession') +const StorageSession = require('./StorageSession'); -const CACHE_NAME = 'GramJs' +const CACHE_NAME = 'GramJs'; class CacheApiSession extends StorageSession { async _delete() { - const request = new Request(this._storageKey) - const cache = await self.caches.open(CACHE_NAME) - return cache.delete(request) + const request = new Request(this._storageKey); + const cache = await self.caches.open(CACHE_NAME); + return cache.delete(request); } async _fetchFromCache() { - const request = new Request(this._storageKey) - const cache = await self.caches.open(CACHE_NAME) - const cached = await cache.match(request) - return cached ? cached.text() : null + const request = new Request(this._storageKey); + const cache = await self.caches.open(CACHE_NAME); + const cached = await cache.match(request); + return cached ? cached.text() : null; } async _saveToCache(data) { - const request = new Request(this._storageKey) - const response = new Response(data) - const cache = await self.caches.open(CACHE_NAME) - return cache.put(request, response) + const request = new Request(this._storageKey); + const response = new Response(data); + const cache = await self.caches.open(CACHE_NAME); + return cache.put(request, response); } } -module.exports = CacheApiSession +module.exports = CacheApiSession; diff --git a/src/lib/gramjs/sessions/IdbSession.js b/src/lib/gramjs/sessions/IdbSession.js index 490494100..92a356bb2 100644 --- a/src/lib/gramjs/sessions/IdbSession.js +++ b/src/lib/gramjs/sessions/IdbSession.js @@ -1,20 +1,20 @@ -const StorageSession = require('./StorageSession') -const idb = require('idb-keyval') +const StorageSession = require('./StorageSession'); +const idb = require('idb-keyval'); -const CACHE_NAME = 'GramJs' +const CACHE_NAME = 'GramJs'; class IdbSession extends StorageSession { async _delete() { - return idb.del(`${CACHE_NAME}:${this._storageKey}`) + return idb.del(`${CACHE_NAME}:${this._storageKey}`); } async _fetchFromCache() { - return idb.get(`${CACHE_NAME}:${this._storageKey}`) + return idb.get(`${CACHE_NAME}:${this._storageKey}`); } async _saveToCache(data) { - return idb.set(`${CACHE_NAME}:${this._storageKey}`, data) + return idb.set(`${CACHE_NAME}:${this._storageKey}`, data); } } -module.exports = IdbSession +module.exports = IdbSession; diff --git a/src/lib/gramjs/sessions/LocalStorageSession.js b/src/lib/gramjs/sessions/LocalStorageSession.js index 77b522368..c042c975e 100644 --- a/src/lib/gramjs/sessions/LocalStorageSession.js +++ b/src/lib/gramjs/sessions/LocalStorageSession.js @@ -1,17 +1,17 @@ -const StorageSession = require('./StorageSession') +const StorageSession = require('./StorageSession'); class LocalStorageSession extends StorageSession { async _delete() { - return localStorage.removeItem(this._storageKey) + return localStorage.removeItem(this._storageKey); } async _fetchFromCache(key) { - return localStorage.getItem(this._storageKey) + return localStorage.getItem(this._storageKey); } async _saveToCache(key, data) { - return localStorage.setItem(this._storageKey, data) + return localStorage.setItem(this._storageKey, data); } } -module.exports = LocalStorageSession +module.exports = LocalStorageSession; diff --git a/src/lib/gramjs/sessions/Memory.js b/src/lib/gramjs/sessions/Memory.js index 487aa1ea2..cf89442a9 100644 --- a/src/lib/gramjs/sessions/Memory.js +++ b/src/lib/gramjs/sessions/Memory.js @@ -1,45 +1,46 @@ -const utils = require('../Utils') -const types = require('../tl').constructors -const Session = require('./Abstract') +const utils = require('../Utils'); +const types = require('../tl').constructors; +const Session = require('./Abstract'); class MemorySession extends Session { constructor() { - super() + super(); - this._serverAddress = null - this._dcId = 0 - this._port = null - this._takeoutId = null + this._serverAddress = null; + this._dcId = 0; + this._port = null; + this._takeoutId = null; - this._entities = new Set() - this._updateStates = {} - } - - setDC(dcId, serverAddress, port) { - this._dcId = dcId | 0 - this._serverAddress = serverAddress - this._port = port + this._entities = new Set(); + this._updateStates = {}; } get dcId() { - return this._dcId + return this._dcId; } get serverAddress() { - return this._serverAddress + return this._serverAddress; } get port() { - return this._port + return this._port; } get authKey() { - return this._authKey + return this._authKey; } set authKey(value) { - this._authKey = value + this._authKey = value; } + + setDC(dcId, serverAddress, port) { + this._dcId = dcId | 0; + this._serverAddress = serverAddress; + this._port = port; + } + /* CONTEST get takeoutId() { return this._takeoutId @@ -253,4 +254,4 @@ class MemorySession extends Session { }*/ } -module.exports = MemorySession +module.exports = MemorySession; diff --git a/src/lib/gramjs/sessions/StorageSession.js b/src/lib/gramjs/sessions/StorageSession.js index 4c87029be..1dd9ac590 100644 --- a/src/lib/gramjs/sessions/StorageSession.js +++ b/src/lib/gramjs/sessions/StorageSession.js @@ -1,135 +1,144 @@ -const MemorySession = require('./Memory') -const AuthKey = require('../crypto/AuthKey') -const utils = require('../Utils') +const MemorySession = require('./Memory'); +const AuthKey = require('../crypto/AuthKey'); +const utils = require('../Utils'); -const STORAGE_KEY_BASE = 'GramJs-session-' +const STORAGE_KEY_BASE = 'GramJs-session-'; class StorageSession extends MemorySession { constructor(sessionId) { - super() - this._storageKey = sessionId - this._authKeys = {} + super(); + this._storageKey = sessionId; + this._authKeys = {}; + } + + get authKey() { + throw new Error('Not supported'); + } + + set authKey(value) { + throw new Error('Not supported'); } async load() { if (!this._storageKey) { - return + return; } try { - const json = await this._fetchFromCache() - const { mainDcId, keys, hashes } = JSON.parse(json) - const { ipAddress, port } = utils.getDC(mainDcId) + const json = await this._fetchFromCache(); + const { + mainDcId, + keys, + hashes, + } = JSON.parse(json); + const { + ipAddress, + port, + } = utils.getDC(mainDcId); - this.setDC(mainDcId, ipAddress, port, true) + this.setDC(mainDcId, ipAddress, port, true); - Object.keys(keys).forEach((dcId) => { - if (keys[dcId] && hashes[dcId]){ - this._authKeys[dcId] = new AuthKey( - Buffer.from(keys[dcId].data), - Buffer.from(hashes[dcId].data) - ) - } - }) + Object.keys(keys) + .forEach((dcId) => { + if (keys[dcId] && hashes[dcId]) { + this._authKeys[dcId] = new AuthKey( + Buffer.from(keys[dcId].data), + Buffer.from(hashes[dcId].data), + ); + } + }); } catch (err) { - console.warn('Failed to retrieve or parse session from storage') - console.warn(err) + console.warn('Failed to retrieve or parse session from storage'); + console.warn(err); } } setDC(dcId, serverAddress, port, skipUpdateStorage = false) { - this._dcId = dcId - this._serverAddress = serverAddress - this._port = port + this._dcId = dcId; + this._serverAddress = serverAddress; + this._port = port; - delete this._authKeys[dcId] + delete this._authKeys[dcId]; if (!skipUpdateStorage) { - void this._updateStorage() + void this._updateStorage(); } } async save() { if (!this._storageKey) { - this._storageKey = generateStorageKey() + this._storageKey = generateStorageKey(); } - await this._updateStorage() + await this._updateStorage(); - return this._storageKey - } - - get authKey() { - throw new Error('Not supported') - } - - set authKey(value) { - throw new Error('Not supported') + return this._storageKey; } getAuthKey(dcId = this._dcId) { - return this._authKeys[dcId] + return this._authKeys[dcId]; } setAuthKey(authKey, dcId = this._dcId) { - this._authKeys[dcId] = authKey + this._authKeys[dcId] = authKey; - void this._updateStorage() + void this._updateStorage(); } async _updateStorage() { if (!this._storageKey) { - return + return; } const sessionData = { mainDcId: this._dcId, keys: {}, - hashes: {} - } + hashes: {}, + }; - Object.keys(this._authKeys).map((dcId) => { - const authKey = this._authKeys[dcId] - sessionData.keys[dcId] = authKey._key - sessionData.hashes[dcId] = authKey._hash - }) + Object.keys(this._authKeys) + .map((dcId) => { + const authKey = this._authKeys[dcId]; + sessionData.keys[dcId] = authKey._key; + sessionData.hashes[dcId] = authKey._hash; + }); try { - await this._saveToCache(JSON.stringify(sessionData)) + await this._saveToCache(JSON.stringify(sessionData)); } catch (err) { - console.warn('Failed to update session in storage') - console.warn(err) + console.warn('Failed to update session in storage'); + console.warn(err); } } async delete() { try { - return await this._delete() + return await this._delete(); } catch (err) { - console.warn('Failed to delete session from storage') - console.warn(err) + console.warn('Failed to delete session from storage'); + console.warn(err); } } // @abstract async _delete() { - throw new Error('Not Implemented') + throw new Error('Not Implemented'); } // @abstract async _fetchFromCache() { - throw new Error('Not Implemented') + throw new Error('Not Implemented'); } // @abstract async _saveToCache(data) { - throw new Error('Not Implemented') + throw new Error('Not Implemented'); } } function generateStorageKey() { // Creating two sessions at the same moment is not expected nor supported. - return `${STORAGE_KEY_BASE}${Date.now()}` + return `${STORAGE_KEY_BASE}${Date.now()}`; } -module.exports = StorageSession +module.exports = StorageSession; diff --git a/src/lib/gramjs/sessions/StringSession.js b/src/lib/gramjs/sessions/StringSession.js index c321c5db8..78357ab58 100644 --- a/src/lib/gramjs/sessions/StringSession.js +++ b/src/lib/gramjs/sessions/StringSession.js @@ -1,7 +1,7 @@ -const MemorySession = require('./Memory') -const AuthKey = require('../crypto/AuthKey') -const BinaryReader = require('../extensions/BinaryReader') -const CURRENT_VERSION = '1' +const MemorySession = require('./Memory'); +const AuthKey = require('../crypto/AuthKey'); +const BinaryReader = require('../extensions/BinaryReader'); +const CURRENT_VERSION = '1'; class StringSession extends MemorySession { @@ -21,22 +21,22 @@ class StringSession extends MemorySession { * @param session {string|null} */ constructor(session = null) { - super() + super(); if (session) { if (session[0] !== CURRENT_VERSION) { - throw new Error('Not a valid string') + throw new Error('Not a valid string'); } - session = session.slice(1) - const r = StringSession.decode(session) - const reader = new BinaryReader(r) + session = session.slice(1); + const r = StringSession.decode(session); + const reader = new BinaryReader(r); this._dcId = reader.read(1) - .readUInt8(0) + .readUInt8(0); const serverAddressLen = reader.read(2) - .readInt16BE(0) - this._serverAddress = String(reader.read(serverAddressLen)) + .readInt16BE(0); + this._serverAddress = String(reader.read(serverAddressLen)); this._port = reader.read(2) - .readInt16BE(0) - this._key = reader.read(-1) + .readInt16BE(0); + this._key = reader.read(-1); } } @@ -45,7 +45,7 @@ class StringSession extends MemorySession { * @returns {string} */ static encode(x) { - return x.toString('base64') + return x.toString('base64'); } /** @@ -53,26 +53,26 @@ class StringSession extends MemorySession { * @returns {Buffer} */ static decode(x) { - return Buffer.from(x, 'base64') + return Buffer.from(x, 'base64'); } async load() { if (this._key) { - this._authKey = new AuthKey() - await this._authKey.setKey(this._key) + this._authKey = new AuthKey(); + await this._authKey.setKey(this._key); } } save() { if (!this.authKey) { - return '' + return ''; } - const dcBuffer = Buffer.from([this.dcId]) - const addressBuffer = Buffer.from(this.serverAddress) - const addressLengthBuffer = Buffer.alloc(2) - addressLengthBuffer.writeInt16BE(addressBuffer.length, 0) - const portBuffer = Buffer.alloc(2) - portBuffer.writeInt16BE(this.port, 0) + const dcBuffer = Buffer.from([this.dcId]); + const addressBuffer = Buffer.from(this.serverAddress); + const addressLengthBuffer = Buffer.alloc(2); + addressLengthBuffer.writeInt16BE(addressBuffer.length, 0); + const portBuffer = Buffer.alloc(2); + portBuffer.writeInt16BE(this.port, 0); return CURRENT_VERSION + StringSession.encode(Buffer.concat([ dcBuffer, @@ -80,26 +80,26 @@ class StringSession extends MemorySession { addressBuffer, portBuffer, this.authKey.getKey(), - ])) + ])); } getAuthKey(dcId) { if (dcId && dcId !== this.dcId) { // Not supported. - return undefined + return undefined; } - return this.authKey + return this.authKey; } setAuthKey(authKey, dcId) { if (dcId && dcId !== this.dcId) { // Not supported. - return undefined + return undefined; } - this.authKey = authKey + this.authKey = authKey; } } -module.exports = StringSession +module.exports = StringSession; diff --git a/src/lib/gramjs/sessions/index.js b/src/lib/gramjs/sessions/index.js index 2e5b88078..8e5bcd82d 100644 --- a/src/lib/gramjs/sessions/index.js +++ b/src/lib/gramjs/sessions/index.js @@ -1,8 +1,8 @@ -const Memory = require('./Memory') -const StringSession = require('./StringSession') -const CacheApiSession = require('./CacheApiSession') -const LocalStorageSession = require('./LocalStorageSession') -const IdbSession = require('./IdbSession') +const Memory = require('./Memory'); +const StringSession = require('./StringSession'); +const CacheApiSession = require('./CacheApiSession'); +const LocalStorageSession = require('./LocalStorageSession'); +const IdbSession = require('./IdbSession'); module.exports = { Memory, @@ -10,4 +10,4 @@ module.exports = { CacheApiSession, LocalStorageSession, IdbSession, -} +}; diff --git a/src/lib/gramjs/tl/AllTLObjects.js b/src/lib/gramjs/tl/AllTLObjects.js index 4a4e45ded..869caa5f7 100644 --- a/src/lib/gramjs/tl/AllTLObjects.js +++ b/src/lib/gramjs/tl/AllTLObjects.js @@ -1,19 +1,19 @@ -const api = require('./api') -const LAYER = 121 -const tlobjects = {} +const api = require('./api'); +const LAYER = 121; +const tlobjects = {}; for (const tl of Object.values(api)) { if (tl.CONSTRUCTOR_ID) { - tlobjects[tl.CONSTRUCTOR_ID] = tl + tlobjects[tl.CONSTRUCTOR_ID] = tl; } else { for (const sub of Object.values(tl)) { - tlobjects[sub.CONSTRUCTOR_ID] = sub + tlobjects[sub.CONSTRUCTOR_ID] = sub; } } } module.exports = { LAYER, - tlobjects -} + tlobjects, +}; diff --git a/src/lib/gramjs/tl/MTProtoRequest.js b/src/lib/gramjs/tl/MTProtoRequest.js index 5dfee464a..727ed1886 100644 --- a/src/lib/gramjs/tl/MTProtoRequest.js +++ b/src/lib/gramjs/tl/MTProtoRequest.js @@ -1,42 +1,44 @@ class MTProtoRequest { constructor() { - this.sent = false - this.msgId = 0 // long - this.sequence = 0 + this.sent = false; + this.msgId = 0; // long + this.sequence = 0; - this.dirty = false - this.sendTime = 0 - this.confirmReceived = false + this.dirty = false; + this.sendTime = 0; + this.confirmReceived = false; // These should be overrode - this.constructorId = 0 - this.confirmed = false - this.responded = false + this.constructorId = 0; + this.confirmed = false; + this.responded = false; } // these should not be overrode onSendSuccess() { - this.sendTime = new Date().getTime() - this.sent = true + this.sendTime = new Date().getTime(); + this.sent = true; } onConfirm() { - this.confirmReceived = true + this.confirmReceived = true; } needResend() { - return this.dirty || (this.confirmed && !this.confirmReceived && new Date().getTime() - this.sendTime > 3000) + return this.dirty || (this.confirmed && !this.confirmReceived && new Date().getTime() - this.sendTime > 3000); } // These should be overrode onSend() { - throw Error('Not overload ' + this.constructor.name) + throw Error('Not overload ' + this.constructor.name); } - onResponse(buffer) {} + onResponse(buffer) { + } - onException(exception) {} + onException(exception) { + } } -module.exports = MTProtoRequest +module.exports = MTProtoRequest; diff --git a/src/lib/gramjs/tl/api.js b/src/lib/gramjs/tl/api.js index 72da67171..29c4e59d7 100644 --- a/src/lib/gramjs/tl/api.js +++ b/src/lib/gramjs/tl/api.js @@ -1,12 +1,15 @@ const { parseTl, serializeBytes, - serializeDate -} = require('./generationHelpers') -const { readBufferFromBigInt,toSignedLittleBuffer } = require('../Helpers') + serializeDate, +} = require('./generationHelpers'); +const { + readBufferFromBigInt, + toSignedLittleBuffer, +} = require('../Helpers'); -const tlContent = require('./apiTl.js') -const schemeContent = require('./schemaTl.js') +const tlContent = require('./apiTl.js'); +const schemeContent = require('./schemaTl.js'); /*CONTEST const NAMED_AUTO_CASTS = new Set([ @@ -29,98 +32,103 @@ const AUTO_CASTS = new Set([ ]) */ -const CACHING_SUPPORTED = typeof self !== 'undefined' && self.localStorage !== undefined +const CACHING_SUPPORTED = typeof self !== 'undefined' && self.localStorage !== undefined; -const CACHE_KEY = 'GramJs:apiCache' +const CACHE_KEY = 'GramJs:apiCache'; function buildApiFromTlSchema() { let definitions; - const fromCache = CACHING_SUPPORTED && loadFromCache() + const fromCache = CACHING_SUPPORTED && loadFromCache(); if (fromCache) { - definitions = fromCache + definitions = fromCache; } else { - definitions = loadFromTlSchemas() + definitions = loadFromTlSchemas(); if (CACHING_SUPPORTED) { - localStorage.setItem(CACHE_KEY, JSON.stringify(definitions)) + localStorage.setItem(CACHE_KEY, JSON.stringify(definitions)); } } return mergeWithNamespaces( - createClasses('constructor', definitions.constructors), - createClasses('request', definitions.requests) - ) + createClasses('constructor', definitions.constructors), + createClasses('request', definitions.requests), + ); } function loadFromCache() { - const jsonCache = localStorage.getItem(CACHE_KEY) - return jsonCache && JSON.parse(jsonCache) + const jsonCache = localStorage.getItem(CACHE_KEY); + return jsonCache && JSON.parse(jsonCache); } function loadFromTlSchemas() { - const [constructorParamsApi, functionParamsApi] = extractParams(tlContent) - const [constructorParamsSchema, functionParamsSchema] = extractParams(schemeContent) - const constructors = [].concat(constructorParamsApi, constructorParamsSchema) - const requests = [].concat(functionParamsApi, functionParamsSchema) + const [constructorParamsApi, functionParamsApi] = extractParams(tlContent); + const [constructorParamsSchema, functionParamsSchema] = extractParams(schemeContent); + const constructors = [].concat(constructorParamsApi, constructorParamsSchema); + const requests = [].concat(functionParamsApi, functionParamsSchema); - return { constructors, requests } + return { + constructors, + requests, + }; } function mergeWithNamespaces(obj1, obj2) { - const result = { ...obj1 } + const result = { ...obj1 }; - Object.keys(obj2).forEach((key) => { - if (typeof obj2[key] === 'function' || !result[key]) { - result[key] = obj2[key] - } else { - Object.assign(result[key], obj2[key]) - } - }) + Object.keys(obj2) + .forEach((key) => { + if (typeof obj2[key] === 'function' || !result[key]) { + result[key] = obj2[key]; + } else { + Object.assign(result[key], obj2[key]); + } + }); - return result + return result; } function extractParams(fileContent) { - const f = parseTl(fileContent, 109) - const constructors = [] - const functions = [] + const f = parseTl(fileContent, 109); + const constructors = []; + const functions = []; for (const d of f) { - d.isFunction ? functions.push(d) : constructors.push(d) + d.isFunction ? functions.push(d) : constructors.push(d); } - return [constructors, functions] + return [constructors, functions]; } function argToBytes(x, type) { switch (type) { case 'int': - const i = Buffer.alloc(4) - i.writeInt32LE(x, 0) - return i + const i = Buffer.alloc(4); + i.writeInt32LE(x, 0); + return i; case 'long': - return toSignedLittleBuffer(x, 8) + return toSignedLittleBuffer(x, 8); case 'int128': - return toSignedLittleBuffer(x, 16) + return toSignedLittleBuffer(x, 16); case 'int256': - return toSignedLittleBuffer(x, 32) + return toSignedLittleBuffer(x, 32); case 'double': - const d = Buffer.alloc(8) - d.writeDoubleLE(x, 0) - return d + const d = Buffer.alloc(8); + d.writeDoubleLE(x, 0); + return d; case 'string': - return serializeBytes(x) + return serializeBytes(x); case 'Bool': - return x ? Buffer.from('b5757299', 'hex') : Buffer.from('379779bc', 'hex') + return x ? Buffer.from('b5757299', 'hex') : Buffer.from('379779bc', 'hex'); case 'true': - return Buffer.alloc(0) + return Buffer.alloc(0); case 'bytes': - return serializeBytes(x) + return serializeBytes(x); case 'date': - return serializeDate(x) + return serializeDate(x); default: - return x.getBytes() + return x.getBytes(); } } + /* CONTEST async function getInputFromResolve(utils, client, peer, peerType) { @@ -155,181 +163,189 @@ async function getInputFromResolve(utils, client, peer, peerType) { function getArgFromReader(reader, arg) { if (arg.isVector) { if (arg.useVectorId) { - reader.readInt() + reader.readInt(); } - const temp = [] - const len = reader.readInt() - arg.isVector = false + const temp = []; + const len = reader.readInt(); + arg.isVector = false; for (let i = 0; i < len; i++) { - temp.push(getArgFromReader(reader, arg)) + temp.push(getArgFromReader(reader, arg)); } - arg.isVector = true - return temp + arg.isVector = true; + return temp; } else if (arg.flagIndicator) { - return reader.readInt() + return reader.readInt(); } else { switch (arg.type) { case 'int': - return reader.readInt() + return reader.readInt(); case 'long': - return reader.readLong() + return reader.readLong(); case 'int128': - return reader.readLargeInt(128) + return reader.readLargeInt(128); case 'int256': - return reader.readLargeInt(256) + return reader.readLargeInt(256); case 'double': - return reader.readDouble() + return reader.readDouble(); case 'string': - return reader.tgReadString() + return reader.tgReadString(); case 'Bool': - return reader.tgReadBool() + return reader.tgReadBool(); case 'true': - return true + return true; case 'bytes': - return reader.tgReadBytes() + return reader.tgReadBytes(); case 'date': - return reader.tgReadDate() + return reader.tgReadDate(); default: if (!arg.skipConstructorId) { - return reader.tgReadObject() + return reader.tgReadObject(); } else { - return api.constructors[arg.type].fromReader(reader) + return api.constructors[arg.type].fromReader(reader); } } } } function createClasses(classesType, params) { - const classes = {} + const classes = {}; for (const classParams of params) { - const { name, constructorId, subclassOfId, argsConfig, namespace, result } = classParams - const fullName = [namespace, name].join('.').replace(/^\./, '') + const { + name, + constructorId, + subclassOfId, + argsConfig, + namespace, + result, + } = classParams; + const fullName = [namespace, name].join('.') + .replace(/^\./, ''); class VirtualClass { - static CONSTRUCTOR_ID = constructorId - static SUBCLASS_OF_ID = subclassOfId - static className = fullName - static classType = classesType + static CONSTRUCTOR_ID = constructorId; + static SUBCLASS_OF_ID = subclassOfId; + static className = fullName; + static classType = classesType; - CONSTRUCTOR_ID = constructorId - SUBCLASS_OF_ID = subclassOfId - className = fullName - classType = classesType + CONSTRUCTOR_ID = constructorId; + SUBCLASS_OF_ID = subclassOfId; + className = fullName; + classType = classesType; constructor(args) { - args = args || {} + args = args || {}; Object.keys(args) - .forEach((argName) => { - this[argName] = args[argName] - }) + .forEach((argName) => { + this[argName] = args[argName]; + }); } static fromReader(reader) { - const args = {} + const args = {}; for (const argName in argsConfig) { if (argsConfig.hasOwnProperty(argName)) { - const arg = argsConfig[argName] + const arg = argsConfig[argName]; if (arg.isFlag) { if (arg.type === 'true') { - args[argName] = Boolean(args['flags'] & 1 << arg.flagIndex) - continue + args[argName] = Boolean(args['flags'] & 1 << arg.flagIndex); + continue; } if (args['flags'] & 1 << arg.flagIndex) { - args[argName] = getArgFromReader(reader, arg) + args[argName] = getArgFromReader(reader, arg); } else { - args[argName] = null + args[argName] = null; } } else { if (arg.flagIndicator) { - arg.name = 'flags' + arg.name = 'flags'; } - args[argName] = getArgFromReader(reader, arg) + args[argName] = getArgFromReader(reader, arg); } } } - return new VirtualClass(args) + return new VirtualClass(args); } getBytes() { // The next is pseudo-code: - const idForBytes = this.CONSTRUCTOR_ID - const c = Buffer.alloc(4) - c.writeUInt32LE(idForBytes, 0) - const buffers = [c] + const idForBytes = this.CONSTRUCTOR_ID; + const c = Buffer.alloc(4); + c.writeUInt32LE(idForBytes, 0); + const buffers = [c]; for (const arg in argsConfig) { if (argsConfig.hasOwnProperty(arg)) { if (argsConfig[arg].isFlag) { - if ((this[arg]===false&&argsConfig[arg].type==="true") || this[arg]===null || this[arg]===undefined){ - continue - } + if ((this[arg] === false && argsConfig[arg].type === 'true') || this[arg] === null || this[arg] === undefined) { + continue; + } } if (argsConfig[arg].isVector) { if (argsConfig[arg].useVectorId) { - buffers.push(Buffer.from('15c4b51c', 'hex')) + buffers.push(Buffer.from('15c4b51c', 'hex')); } - const l = Buffer.alloc(4) - l.writeInt32LE(this[arg].length, 0) - buffers.push(l, Buffer.concat(this[arg].map(x => argToBytes(x, argsConfig[arg].type)))) + const l = Buffer.alloc(4); + l.writeInt32LE(this[arg].length, 0); + buffers.push(l, Buffer.concat(this[arg].map(x => argToBytes(x, argsConfig[arg].type)))); } else if (argsConfig[arg].flagIndicator) { if (!Object.values(argsConfig) - .some((f) => f.isFlag)) { - buffers.push(Buffer.alloc(4)) + .some((f) => f.isFlag)) { + buffers.push(Buffer.alloc(4)); } else { - let flagCalculate = 0 + let flagCalculate = 0; for (const f in argsConfig) { if (argsConfig[f].isFlag) { - if ((this[f]===false&&argsConfig[f].type==="true") || this[f]===undefined || this[f]===null) { - flagCalculate |= 0 + if ((this[f] === false && argsConfig[f].type === 'true') || this[f] === undefined || this[f] === null) { + flagCalculate |= 0; } else { - flagCalculate |= 1 << argsConfig[f].flagIndex + flagCalculate |= 1 << argsConfig[f].flagIndex; } } } - const f = Buffer.alloc(4) - f.writeUInt32LE(flagCalculate, 0) - buffers.push(f) + const f = Buffer.alloc(4); + f.writeUInt32LE(flagCalculate, 0); + buffers.push(f); } } else { - buffers.push(argToBytes(this[arg], argsConfig[arg].type)) + buffers.push(argToBytes(this[arg], argsConfig[arg].type)); if (this[arg] && typeof this[arg].getBytes === 'function') { - let boxed = (argsConfig[arg].type.charAt(argsConfig[arg].type.indexOf('.') + 1)) - boxed = boxed === boxed.toUpperCase() + let boxed = (argsConfig[arg].type.charAt(argsConfig[arg].type.indexOf('.') + 1)); + boxed = boxed === boxed.toUpperCase(); if (!boxed) { - buffers.shift() + buffers.shift(); } } } } } - return Buffer.concat(buffers) + return Buffer.concat(buffers); } readResult(reader) { if (classesType !== 'request') { - throw new Error('`readResult()` called for non-request instance') + throw new Error('`readResult()` called for non-request instance'); } - const m = result.match(/Vector<(int|long)>/) + const m = result.match(/Vector<(int|long)>/); if (m) { - reader.readInt() - let temp = [] - let len = reader.readInt() + reader.readInt(); + let temp = []; + let len = reader.readInt(); if (m[1] === 'int') { for (let i = 0; i < len; i++) { - temp.push(reader.readInt()) + temp.push(reader.readInt()); } } else { for (let i = 0; i < len; i++) { - temp.push(reader.readLong()) + temp.push(reader.readLong()); } } - return temp + return temp; } else { - return reader.tgReadObject() + return reader.tgReadObject(); } } @@ -368,16 +384,16 @@ function createClasses(classesType, params) { if (namespace) { if (!classes[namespace]) { - classes[namespace] = {} + classes[namespace] = {}; } - classes[namespace][name] = VirtualClass + classes[namespace][name] = VirtualClass; } else { - classes[name] = VirtualClass + classes[name] = VirtualClass; } } - return classes + return classes; } -module.exports = buildApiFromTlSchema() +module.exports = buildApiFromTlSchema(); diff --git a/src/lib/gramjs/tl/core/GZIPPacked.js b/src/lib/gramjs/tl/core/GZIPPacked.js index b2833cfe4..6586c1630 100644 --- a/src/lib/gramjs/tl/core/GZIPPacked.js +++ b/src/lib/gramjs/tl/core/GZIPPacked.js @@ -1,57 +1,58 @@ -const { serializeBytes } = require('../index') -const { inflate } = require('pako/dist/pako_inflate') +const { serializeBytes } = require('../index'); +const { inflate } = require('pako/dist/pako_inflate'); + //CONTEST const { deflate } = require('pako/dist/pako_deflate') class GZIPPacked { - static CONSTRUCTOR_ID = 0x3072cfa1 - static classType = 'constructor' + static CONSTRUCTOR_ID = 0x3072cfa1; + static classType = 'constructor'; constructor(data) { - this.data = data - this.CONSTRUCTOR_ID = 0x3072cfa1 - this.classType = 'constructor' + this.data = data; + this.CONSTRUCTOR_ID = 0x3072cfa1; + this.classType = 'constructor'; } static async gzipIfSmaller(contentRelated, data) { if (contentRelated && data.length > 512) { - const gzipped = await (new GZIPPacked(data)).toBytes() + const gzipped = await (new GZIPPacked(data)).toBytes(); if (gzipped.length < data.length) { - return gzipped + return gzipped; } } - return data + return data; } static gzip(input) { - return Buffer.from(input) + return Buffer.from(input); // TODO this usually makes it faster for large requests //return Buffer.from(deflate(input, { level: 9, gzip: true })) } static ungzip(input) { - return Buffer.from(inflate(input)) - } - - async toBytes() { - const g = Buffer.alloc(4) - g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0) - return Buffer.concat([ - g, - serializeBytes(await GZIPPacked.gzip(this.data)), - ]) + return Buffer.from(inflate(input)); } static async read(reader) { - const constructor = reader.readInt(false) + const constructor = reader.readInt(false); if (constructor !== GZIPPacked.CONSTRUCTOR_ID) { - throw new Error('not equal') + throw new Error('not equal'); } - return await GZIPPacked.gzip(reader.tgReadBytes()) + return await GZIPPacked.gzip(reader.tgReadBytes()); } static async fromReader(reader) { - return new GZIPPacked(await GZIPPacked.ungzip(reader.tgReadBytes())) + return new GZIPPacked(await GZIPPacked.ungzip(reader.tgReadBytes())); + } + + async toBytes() { + const g = Buffer.alloc(4); + g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0); + return Buffer.concat([ + g, + serializeBytes(await GZIPPacked.gzip(this.data)), + ]); } } -module.exports = GZIPPacked +module.exports = GZIPPacked; diff --git a/src/lib/gramjs/tl/core/MessageContainer.js b/src/lib/gramjs/tl/core/MessageContainer.js index 75f41eb57..412894ded 100644 --- a/src/lib/gramjs/tl/core/MessageContainer.js +++ b/src/lib/gramjs/tl/core/MessageContainer.js @@ -1,8 +1,8 @@ -const TLMessage = require('./TLMessage') +const TLMessage = require('./TLMessage'); class MessageContainer { static CONSTRUCTOR_ID = 0x73f1f8dc; - static classType = "constructor" + static classType = 'constructor'; // Maximum size in bytes for the inner payload of the container. // Telegram will close the connection if the payload is bigger. // The overhead of the container itself is subtracted. @@ -20,26 +20,26 @@ class MessageContainer { constructor(messages) { - this.CONSTRUCTOR_ID = 0x73f1f8dc - this.messages = messages - this.classType = "constructor" + this.CONSTRUCTOR_ID = 0x73f1f8dc; + this.messages = messages; + this.classType = 'constructor'; } static async fromReader(reader) { - const messages = [] - const length = reader.readInt() + const messages = []; + const length = reader.readInt(); for (let x = 0; x < length; x++) { - const msgId = reader.readLong() - const seqNo = reader.readInt() - const length = reader.readInt() - const before = reader.tellPosition() - const obj = reader.tgReadObject() - reader.setPosition(before + length) - const tlMessage = new TLMessage(msgId, seqNo, obj) - messages.push(tlMessage) + const msgId = reader.readLong(); + const seqNo = reader.readInt(); + const length = reader.readInt(); + const before = reader.tellPosition(); + const obj = reader.tgReadObject(); + reader.setPosition(before + length); + const tlMessage = new TLMessage(msgId, seqNo, obj); + messages.push(tlMessage); } - return new MessageContainer(messages) + return new MessageContainer(messages); } } -module.exports = MessageContainer +module.exports = MessageContainer; diff --git a/src/lib/gramjs/tl/core/RPCResult.js b/src/lib/gramjs/tl/core/RPCResult.js index ba8b80545..11729cfa9 100644 --- a/src/lib/gramjs/tl/core/RPCResult.js +++ b/src/lib/gramjs/tl/core/RPCResult.js @@ -1,33 +1,33 @@ -const { RpcError } = require('../index').constructors -const GZIPPacked = require('./GZIPPacked') +const { RpcError } = require('../index').constructors; +const GZIPPacked = require('./GZIPPacked'); class RPCResult { static CONSTRUCTOR_ID = 0xf35c6d01; - static classType = "constructor" + static classType = 'constructor'; constructor(reqMsgId, body, error) { - this.CONSTRUCTOR_ID = 0xf35c6d01 - this.reqMsgId = reqMsgId - this.body = body - this.error = error - this.classType = "constructor" + this.CONSTRUCTOR_ID = 0xf35c6d01; + this.reqMsgId = reqMsgId; + this.body = body; + this.error = error; + this.classType = 'constructor'; } static async fromReader(reader) { - const msgId = reader.readLong() - const innerCode = reader.readInt(false) + const msgId = reader.readLong(); + const innerCode = reader.readInt(false); if (innerCode === RpcError.CONSTRUCTOR_ID) { - return new RPCResult(msgId, null, RpcError.fromReader(reader)) + return new RPCResult(msgId, null, RpcError.fromReader(reader)); } if (innerCode === GZIPPacked.CONSTRUCTOR_ID) { - return new RPCResult(msgId, (await GZIPPacked.fromReader(reader)).data) + return new RPCResult(msgId, (await GZIPPacked.fromReader(reader)).data); } - reader.seek(-4) + 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) + return new RPCResult(msgId, reader.read(), null); } } -module.exports = RPCResult +module.exports = RPCResult; diff --git a/src/lib/gramjs/tl/core/TLMessage.js b/src/lib/gramjs/tl/core/TLMessage.js index 9acf9f549..17875e396 100644 --- a/src/lib/gramjs/tl/core/TLMessage.js +++ b/src/lib/gramjs/tl/core/TLMessage.js @@ -1,14 +1,13 @@ - class TLMessage { static SIZE_OVERHEAD = 12; - static classType = "constructor" + static classType = 'constructor'; constructor(msgId, seqNo, obj) { - this.msgId = msgId - this.seqNo = seqNo - this.obj = obj - this.classType = "constructor" + this.msgId = msgId; + this.seqNo = seqNo; + this.obj = obj; + this.classType = 'constructor'; } } -module.exports = TLMessage +module.exports = TLMessage; diff --git a/src/lib/gramjs/tl/core/index.js b/src/lib/gramjs/tl/core/index.js index 0a7440de6..a0ba99845 100644 --- a/src/lib/gramjs/tl/core/index.js +++ b/src/lib/gramjs/tl/core/index.js @@ -1,12 +1,12 @@ -const TLMessage = require('./TLMessage') -const RPCResult = require('./RPCResult') -const MessageContainer = require('./MessageContainer') -const GZIPPacked = require('./GZIPPacked') +const TLMessage = require('./TLMessage'); +const RPCResult = require('./RPCResult'); +const MessageContainer = require('./MessageContainer'); +const GZIPPacked = require('./GZIPPacked'); const coreObjects = { [RPCResult.CONSTRUCTOR_ID]: RPCResult, [GZIPPacked.CONSTRUCTOR_ID]: GZIPPacked, [MessageContainer.CONSTRUCTOR_ID]: MessageContainer, -} +}; module.exports = { TLMessage, @@ -14,4 +14,4 @@ module.exports = { MessageContainer, GZIPPacked, coreObjects, -} +}; diff --git a/src/lib/gramjs/tl/generationHelpers.js b/src/lib/gramjs/tl/generationHelpers.js index 400fcf043..f3efd1af2 100644 --- a/src/lib/gramjs/tl/generationHelpers.js +++ b/src/lib/gramjs/tl/generationHelpers.js @@ -1,21 +1,21 @@ const snakeToCamelCase = (name) => { - const result = name.replace(/(?:^|_)([a-z])/g, (_, g) => g.toUpperCase()) - return result.replace(/_/g, '') -} + const result = name.replace(/(?:^|_)([a-z])/g, (_, g) => g.toUpperCase()); + return result.replace(/_/g, ''); +}; const variableSnakeToCamelCase = (str) => str.replace( /([-_][a-z])/g, (group) => group.toUpperCase() .replace('-', '') - .replace('_', '') -) + .replace('_', ''), +); const CORE_TYPES = new Set([ 0xbc799737, // boolFalse#bc799737 = Bool; 0x997275b5, // boolTrue#997275b5 = Bool; 0x3fedd339, // true#3fedd339 = True; 0xc4b9f9bb, // error#c4b9f9bb code:int text:string = Error; - 0x56730bcc // null#56730bcc = Null; -]) + 0x56730bcc, // null#56730bcc = Null; +]); const AUTH_KEY_TYPES = new Set([ 0x05162463, // resPQ, 0x83c95aec, // p_q_inner_data @@ -27,49 +27,49 @@ const AUTH_KEY_TYPES = new Set([ 0x6643b654, // client_DH_inner_data 0xd712e4be, // req_DH_params 0xf5045f1f, // set_client_DH_params - 0x3072cfa1 // gzip_packed -]) + 0x3072cfa1, // gzip_packed +]); // This is copy-pasted from `gramjs/Helpers.js` to not depend on TypeScript modules function makeCRCTable() { - let c - const crcTable = [] + let c; + const crcTable = []; for (let n = 0; n < 256; n++) { - c = n + c = n; for (let k = 0; k < 8; k++) { - c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)) + c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } - crcTable[n] = c + crcTable[n] = c; } - return crcTable + return crcTable; } -let crcTable = null +let crcTable = null; function crc32(buf) { if (!crcTable) { - crcTable = makeCRCTable() + crcTable = makeCRCTable(); } if (!Buffer.isBuffer(buf)) { - buf = Buffer.from(buf) + buf = Buffer.from(buf); } - let crc = -1 + let crc = -1; for (let index = 0; index < buf.length; index++) { - const byte = buf[index] - crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8) + const byte = buf[index]; + crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8); } - return (crc ^ (-1)) >>> 0 + return (crc ^ (-1)) >>> 0; } const fromLine = (line, isFunction) => { - const match = line.match(/([\w.]+)(?:#([0-9a-fA-F]+))?(?:\s{?\w+:[\w\d<>#.?!]+}?)*\s=\s([\w\d<>#.?]+);$/) + const match = line.match(/([\w.]+)(?:#([0-9a-fA-F]+))?(?:\s{?\w+:[\w\d<>#.?!]+}?)*\s=\s([\w\d<>#.?]+);$/); if (!match) { // Probably "vector#1cb5c415 {t:Type} # [ t ] = Vector t;" - throw new Error(`Cannot parse TLObject ${line}`) + throw new Error(`Cannot parse TLObject ${line}`); } - const argsMatch = findAll(/({)?(\w+):([\w\d<>#.?!]+)}?/, line) + const argsMatch = findAll(/({)?(\w+):([\w\d<>#.?!]+)}?/, line); const currentConfig = { name: match[1], constructorId: parseInt(match[2], 16), @@ -77,24 +77,26 @@ const fromLine = (line, isFunction) => { subclassOfId: crc32(match[3]), result: match[3], isFunction: isFunction, - namespace: null - } + namespace: null, + }; if (!currentConfig.constructorId) { - let hexId = '' - let args + let hexId = ''; + let args; if (Object.values(currentConfig.argsConfig).length) { - args = ` ${Object.keys(currentConfig.argsConfig).map((arg) => arg.toString()).join(' ')}` + args = ` ${Object.keys(currentConfig.argsConfig) + .map((arg) => arg.toString()) + .join(' ')}`; } else { - args = '' + args = ''; } const representation = `${currentConfig.name}${hexId}${args} = ${currentConfig.result}` .replace(/(:|\?)bytes /g, '$1string ') .replace(/|{|}/g, '') - .replace(/ \w+:flags\.\d+\?true/g, '') + .replace(/ \w+:flags\.\d+\?true/g, ''); if (currentConfig.name === 'inputMediaInvoice') { // eslint-disable-next-line no-empty @@ -102,17 +104,17 @@ const fromLine = (line, isFunction) => { } } - currentConfig.constructorId = crc32(Buffer.from(representation, 'utf8')) + currentConfig.constructorId = crc32(Buffer.from(representation, 'utf8')); } for (const [brace, name, argType] of argsMatch) { if (brace === undefined) { - currentConfig.argsConfig[variableSnakeToCamelCase(name)] = buildArgConfig(name, argType) + currentConfig.argsConfig[variableSnakeToCamelCase(name)] = buildArgConfig(name, argType); } } if (currentConfig.name.includes('.')) { - [currentConfig.namespace, currentConfig.name] = currentConfig.name.split(/\.(.+)/) + [currentConfig.namespace, currentConfig.name] = currentConfig.name.split(/\.(.+)/); } - currentConfig.name = snakeToCamelCase(currentConfig.name) + currentConfig.name = snakeToCamelCase(currentConfig.name); /* for (const arg in currentConfig.argsConfig){ if (currentConfig.argsConfig.hasOwnProperty(arg)){ @@ -121,11 +123,11 @@ const fromLine = (line, isFunction) => { } } }*/ - return currentConfig -} + return currentConfig; +}; function buildArgConfig(name, argType) { - name = name === 'self' ? 'is_self' : name + name = name === 'self' ? 'is_self' : name; // Default values const currentConfig = { isVector: false, @@ -134,41 +136,41 @@ function buildArgConfig(name, argType) { flagIndex: -1, flagIndicator: true, type: null, - useVectorId: null - } + useVectorId: null, + }; // Special case: some types can be inferred, which makes it // less annoying to type. Currently the only type that can // be inferred is if the name is 'random_id', to which a // random ID will be assigned if left as None (the default) - let canBeInferred = name === 'random_id' + let canBeInferred = name === 'random_id'; // The type can be an indicator that other arguments will be flags if (argType !== '#') { - currentConfig.flagIndicator = false + currentConfig.flagIndicator = false; // Strip the exclamation mark always to have only the name - currentConfig.type = argType.replace(/^!+/, '') + currentConfig.type = argType.replace(/^!+/, ''); // The type may be a flag (flags.IDX?REAL_TYPE) // Note that 'flags' is NOT the flags name; this // is determined by a previous argument // However, we assume that the argument will always be called 'flags' // @ts-ignore - const flagMatch = currentConfig.type.match(/flags.(\d+)\?([\w<>.]+)/) + const flagMatch = currentConfig.type.match(/flags.(\d+)\?([\w<>.]+)/); if (flagMatch) { - currentConfig.isFlag = true + currentConfig.isFlag = true; currentConfig.flagIndex = Number(flagMatch[1]); // Update the type to match the exact type, not the "flagged" one - [, , currentConfig.type] = flagMatch + [, , currentConfig.type] = flagMatch; } // Then check if the type is a Vector // @ts-ignore - const vectorMatch = currentConfig.type.match(/[Vv]ector<([\w\d.]+)>/) + const vectorMatch = currentConfig.type.match(/[Vv]ector<([\w\d.]+)>/); if (vectorMatch) { - currentConfig.isVector = true + currentConfig.isVector = true; // If the type's first letter is not uppercase, then // it is a constructor and we use (read/write) its ID. @@ -176,7 +178,7 @@ function buildArgConfig(name, argType) { currentConfig.useVectorId = currentConfig.type.charAt(0) === 'V'; // Update the type to match the one inside the vector - [, currentConfig.type] = vectorMatch + [, currentConfig.type] = vectorMatch; } // See use_vector_id. An example of such case is ipPort in @@ -184,10 +186,10 @@ function buildArgConfig(name, argType) { // @ts-ignore if (/^[a-z]$/.test(currentConfig.type.split('.') .pop() - .charAt(0) + .charAt(0), ) ) { - currentConfig.skipConstructorId = true + currentConfig.skipConstructorId = true; } // The name may contain "date" in it, if this is the case and @@ -202,61 +204,65 @@ function buildArgConfig(name, argType) { // this.type = 'date'; // } } - return currentConfig + return currentConfig; } const parseTl = function* (content, layer, methods = [], ignoreIds = CORE_TYPES) { - const methodInfo = (methods || []).reduce((o, m) => ({ ...o, [m.name]: m }), {}) - const objAll = [] - const objByName = {} - const objByType = {} + const methodInfo = (methods || []).reduce((o, m) => ({ + ...o, + [m.name]: m, + }), {}); + const objAll = []; + const objByName = {}; + const objByType = {}; - const file = content + const file = content; - let isFunction = false + let isFunction = false; for (let line of file.split('\n')) { - const commentIndex = line.indexOf('//') + const commentIndex = line.indexOf('//'); if (commentIndex !== -1) { - line = line.slice(0, commentIndex) + line = line.slice(0, commentIndex); } - line = line.trim() + line = line.trim(); if (!line) { - continue + continue; } - const match = line.match(/---(\w+)---/) + const match = line.match(/---(\w+)---/); if (match) { - const [, followingTypes] = match - isFunction = followingTypes === 'functions' - continue + const [, followingTypes] = match; + isFunction = followingTypes === 'functions'; + continue; } try { - const result = fromLine(line, isFunction) + const result = fromLine(line, isFunction); if (ignoreIds.has(result.constructorId)) { - continue + continue; } - objAll.push(result) + objAll.push(result); if (!result.isFunction) { if (!objByType[result.result]) { - objByType[result.result] = [] + objByType[result.result] = []; } - objByName[result.name] = result - objByType[result.result].push(result) + objByName[result.name] = result; + objByType[result.result].push(result); } } catch (e) { - if (!e.toString().includes('vector#1cb5c415')) { - throw e + if (!e.toString() + .includes('vector#1cb5c415')) { + throw e; } } } @@ -268,79 +274,79 @@ const parseTl = function* (content, layer, methods = [], ignoreIds = CORE_TYPES) if (AUTH_KEY_TYPES.has(obj.constructorId)) { for (const arg in obj.argsConfig) { if (obj.argsConfig[arg].type === 'string') { - obj.argsConfig[arg].type = 'bytes' + obj.argsConfig[arg].type = 'bytes'; } } } } for (const obj of objAll) { - yield obj + yield obj; } -} +}; const findAll = (regex, str, matches = []) => { if (!regex.flags.includes(`g`)) { - regex = new RegExp(regex.source, `g`) + regex = new RegExp(regex.source, `g`); } - const res = regex.exec(str) + const res = regex.exec(str); if (res) { - matches.push(res.slice(1)) - findAll(regex, str, matches) + matches.push(res.slice(1)); + findAll(regex, str, matches); } - return matches -} + return matches; +}; function serializeBytes(data) { if (!(data instanceof Buffer)) { if (typeof data == 'string') { - data = Buffer.from(data) + data = Buffer.from(data); } else { - throw Error(`Bytes or str expected, not ${data.constructor.name}`) + throw Error(`Bytes or str expected, not ${data.constructor.name}`); } } - const r = [] - let padding + const r = []; + let padding; if (data.length < 254) { - padding = (data.length + 1) % 4 + padding = (data.length + 1) % 4; if (padding !== 0) { - padding = 4 - padding + padding = 4 - padding; } - r.push(Buffer.from([data.length])) - r.push(data) + r.push(Buffer.from([data.length])); + r.push(data); } else { - padding = data.length % 4 + padding = data.length % 4; if (padding !== 0) { - padding = 4 - padding + 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.from([254, data.length % 256, (data.length >> 8) % 256, (data.length >> 16) % 256])); + r.push(data); } r.push(Buffer.alloc(padding) - .fill(0)) + .fill(0)); - return Buffer.concat(r) + return Buffer.concat(r); } function serializeDate(dt) { if (!dt) { return Buffer.alloc(4) - .fill(0) + .fill(0); } if (dt instanceof Date) { - dt = Math.floor((Date.now() - dt.getTime()) / 1000) + dt = Math.floor((Date.now() - dt.getTime()) / 1000); } if (typeof dt == 'number') { - const t = Buffer.alloc(4) - t.writeInt32LE(dt, 0) - return t + const t = Buffer.alloc(4); + t.writeInt32LE(dt, 0); + return t; } - throw Error(`Cannot interpret "${dt}" as a date`) + throw Error(`Cannot interpret "${dt}" as a date`); } module.exports = { @@ -352,5 +358,5 @@ module.exports = { serializeDate, serializeBytes, snakeToCamelCase, - variableSnakeToCamelCase -} + variableSnakeToCamelCase, +}; diff --git a/src/lib/gramjs/tl/index.js b/src/lib/gramjs/tl/index.js index 3e59ed5c5..1e3cfdfad 100644 --- a/src/lib/gramjs/tl/index.js +++ b/src/lib/gramjs/tl/index.js @@ -1,6 +1,9 @@ -const api = require('./api') -const { serializeBytes, serializeDate } = require('./generationHelpers') -const patched = null +const api = require('./api'); +const { + serializeBytes, + serializeDate, +} = require('./generationHelpers'); +const patched = null; module.exports = { // TODO Refactor internal usages to always use `api`. @@ -8,5 +11,5 @@ module.exports = { requests: api, patched, serializeBytes, - serializeDate -} + serializeDate, +}; diff --git a/src/lib/gramjs/tl/types-generator/generate.js b/src/lib/gramjs/tl/types-generator/generate.js index 0b16b02f9..42ffc493f 100644 --- a/src/lib/gramjs/tl/types-generator/generate.js +++ b/src/lib/gramjs/tl/types-generator/generate.js @@ -1,57 +1,61 @@ -const path = require('path') -const fs = require('fs') +const path = require('path'); +const fs = require('fs'); -const { parseTl } = require('../generationHelpers') -const templateFn = require('./template') +const { parseTl } = require('../generationHelpers'); +const templateFn = require('./template'); -const INPUT_FILE = path.resolve(__dirname, '../static/api.tl') -const SCHEMA_FILE = path.resolve(__dirname, '../static/schema.tl') +const INPUT_FILE = path.resolve(__dirname, '../static/api.tl'); +const SCHEMA_FILE = path.resolve(__dirname, '../static/schema.tl'); -const OUTPUT_FILE = path.resolve(__dirname, '../api.d.ts') +const OUTPUT_FILE = path.resolve(__dirname, '../api.d.ts'); function main() { - const tlContent = fs.readFileSync(INPUT_FILE, 'utf-8') - const apiConfig = extractParams(tlContent) - const schemeContent = fs.readFileSync(SCHEMA_FILE, 'utf-8') - const schemeConfig = extractParams(schemeContent) - const types = [...apiConfig.types, ...schemeConfig.types] - const functions = [...apiConfig.functions, ...schemeConfig.functions] - const constructors = [...apiConfig.constructors, ...schemeConfig.constructors] - const generated = templateFn({ types: types, functions: functions, constructors: constructors }) + const tlContent = fs.readFileSync(INPUT_FILE, 'utf-8'); + const apiConfig = extractParams(tlContent); + const schemeContent = fs.readFileSync(SCHEMA_FILE, 'utf-8'); + const schemeConfig = extractParams(schemeContent); + const types = [...apiConfig.types, ...schemeConfig.types]; + const functions = [...apiConfig.functions, ...schemeConfig.functions]; + const constructors = [...apiConfig.constructors, ...schemeConfig.constructors]; + const generated = templateFn({ + types: types, + functions: functions, + constructors: constructors, + }); - fs.writeFileSync(OUTPUT_FILE, generated) + fs.writeFileSync(OUTPUT_FILE, generated); } function extractParams(fileContent) { - const defInterator = parseTl(fileContent, 109) - const types = {} - const constructors = [] - const functions = [] + const defInterator = parseTl(fileContent, 109); + const types = {}; + const constructors = []; + const functions = []; for (const def of defInterator) { if (def.isFunction) { - functions.push(def) + functions.push(def); } else { if (!types[def.result]) { - let [namespace, name] = def.result.includes('.') ? def.result.split('.') : [undefined, def.result] + let [namespace, name] = def.result.includes('.') ? def.result.split('.') : [undefined, def.result]; types[def.result] = { namespace, name, - constructors: [] - } + constructors: [], + }; } - types[def.result].constructors.push(def.namespace ? `${def.namespace}.${def.name}` : def.name) - constructors.push(def) + types[def.result].constructors.push(def.namespace ? `${def.namespace}.${def.name}` : def.name); + constructors.push(def); } } return { types: Object.values(types), constructors, - functions - } + functions, + }; } -main() +main();