GramJs: Code style fixes by WebStorm

This commit is contained in:
Alexander Zinchuk 2021-05-08 22:41:22 +03:00
parent 99cfeb6e09
commit 559ef6a53b
59 changed files with 2212 additions and 2023 deletions

View File

@ -1,5 +1,5 @@
const crypto = require('./crypto/crypto') const crypto = require('./crypto/crypto');
const BigInt = require('big-integer') const BigInt = require('big-integer');
/** /**
* converts a buffer to big int * converts a buffer to big int
@ -9,17 +9,17 @@ const BigInt = require('big-integer')
* @returns {bigInt.BigInteger} * @returns {bigInt.BigInteger}
*/ */
function readBigIntFromBuffer(buffer, little = true, signed = false) { function readBigIntFromBuffer(buffer, little = true, signed = false) {
let randBuffer = Buffer.from(buffer) let randBuffer = Buffer.from(buffer);
const bytesNumber = randBuffer.length const bytesNumber = randBuffer.length;
if (little) { 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) { if (signed && Math.floor(bigInt.toString('2').length / 8) >= bytesNumber) {
bigInt = bigInt.subtract(BigInt(2) 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} * @returns {Buffer}
*/ */
function toSignedLittleBuffer(big, number = 8) { function toSignedLittleBuffer(big, number = 8) {
const bigNumber = BigInt(big) const bigNumber = BigInt(big);
const byteArray = [] const byteArray = [];
for (let i = 0; i < number; i++) { 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} * @returns {Buffer}
*/ */
function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) { function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) {
bigInt = BigInt(bigInt) bigInt = BigInt(bigInt);
const bitLength = bigInt.bitLength() const bitLength = bigInt.bitLength();
const bytes = Math.ceil(bitLength / 8) const bytes = Math.ceil(bitLength / 8);
if (bytesNumber < bytes) { 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))) { 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))) { if (bigInt.lesser(BigInt(0))) {
below = true below = true;
bigInt = bigInt.abs() bigInt = bigInt.abs();
} }
const hex = bigInt.toString('16') const hex = bigInt.toString('16')
.padStart(bytesNumber * 2, '0') .padStart(bytesNumber * 2, '0');
let l = Buffer.from(hex, 'hex') let l = Buffer.from(hex, 'hex');
if (little) { if (little) {
l = l.reverse() l = l.reverse();
} }
if (signed && below) { if (signed && below) {
if (little) { if (little) {
let reminder = false let reminder = false;
if (l[0] !== 0) { if (l[0] !== 0) {
l[0] -= 1 l[0] -= 1;
} }
for (let i = 0; i < l.length; i++) { for (let i = 0; i < l.length; i++) {
if (l[i] === 0) { if (l[i] === 0) {
reminder = true reminder = true;
continue continue;
} }
if (reminder) { if (reminder) {
l[i] -= 1 l[i] -= 1;
reminder = false reminder = false;
} }
l[i] = 255 - l[i] l[i] = 255 - l[i];
} }
} else { } 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++) { 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} * @returns {BigInteger}
*/ */
function generateRandomLong(signed = true) { 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} * @returns {number}
*/ */
function mod(n, m) { function mod(n, m) {
return ((n % m) + m) % m return ((n % m) + m) % m;
} }
/** /**
@ -122,7 +123,7 @@ function mod(n, m) {
* @returns {BigInt} * @returns {BigInt}
*/ */
function bigIntMod(n, m) { 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} * @returns {Buffer}
*/ */
function generateRandomBytes(count) { function generateRandomBytes(count) {
return Buffer.from(crypto.randomBytes(count)) return Buffer.from(crypto.randomBytes(count));
} }
/** /**
@ -141,6 +142,7 @@ function generateRandomBytes(count) {
* @param client * @param client
* @returns {{iv: Buffer, key: Buffer}} * @returns {{iv: Buffer, key: Buffer}}
*/ */
/*CONTEST /*CONTEST
this is mtproto 1 (mostly used for secret chats) this is mtproto 1 (mostly used for secret chats)
async function calcKey(sharedKey, msgKey, client) { async function calcKey(sharedKey, msgKey, client) {
@ -168,26 +170,26 @@ async function calcKey(sharedKey, msgKey, client) {
* @returns {{key: Buffer, iv: Buffer}} * @returns {{key: Buffer, iv: Buffer}}
*/ */
async function generateKeyDataFromNonce(serverNonce, newNonce) { async function generateKeyDataFromNonce(serverNonce, newNonce) {
serverNonce = toSignedLittleBuffer(serverNonce, 16) serverNonce = toSignedLittleBuffer(serverNonce, 16);
newNonce = toSignedLittleBuffer(newNonce, 32) newNonce = toSignedLittleBuffer(newNonce, 32);
const [hash1, hash2, hash3] = await Promise.all([ const [hash1, hash2, hash3] = await Promise.all([
sha1(Buffer.concat([newNonce, serverNonce])), sha1(Buffer.concat([newNonce, serverNonce])),
sha1(Buffer.concat([serverNonce, newNonce])), sha1(Buffer.concat([serverNonce, newNonce])),
sha1(Buffer.concat([newNonce, newNonce])) sha1(Buffer.concat([newNonce, newNonce])),
]) ]);
const keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)]) const keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)]);
const ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]) const ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]);
return { return {
key: keyBuffer, key: keyBuffer,
iv: ivBuffer iv: ivBuffer,
} };
} }
function convertToLittle(buf) { function convertToLittle(buf) {
const correct = Buffer.alloc(buf.length * 4); const correct = Buffer.alloc(buf.length * 4);
for (let i = 0; i < buf.length; i++) { for (let i = 0; i < buf.length; i++) {
correct.writeUInt32BE(buf[i], i * 4) correct.writeUInt32BE(buf[i], i * 4);
} }
return correct; return correct;
} }
@ -198,9 +200,9 @@ function convertToLittle(buf) {
* @returns {Promise} * @returns {Promise}
*/ */
function sha1(data) { function sha1(data) {
const shaSum = crypto.createHash('sha1') const shaSum = crypto.createHash('sha1');
shaSum.update(data) shaSum.update(data);
return shaSum.digest() return shaSum.digest();
} }
@ -210,9 +212,9 @@ function sha1(data) {
* @returns {Promise} * @returns {Promise}
*/ */
function sha256(data) { function sha256(data) {
const shaSum = crypto.createHash('sha256') const shaSum = crypto.createHash('sha256');
shaSum.update(data) shaSum.update(data);
return shaSum.digest() return shaSum.digest();
} }
/** /**
@ -223,20 +225,20 @@ function sha256(data) {
* @returns {bigInt.BigInteger} * @returns {bigInt.BigInteger}
*/ */
function modExp(a, b, n) { function modExp(a, b, n) {
a = a.remainder(n) a = a.remainder(n);
let result = BigInt.one let result = BigInt.one;
let x = a let x = a;
while (b.greater(BigInt.zero)) { while (b.greater(BigInt.zero)) {
const leastSignificantBit = b.remainder(BigInt(2)) const leastSignificantBit = b.remainder(BigInt(2));
b = b.divide(BigInt(2)) b = b.divide(BigInt(2));
if (leastSignificantBit.eq(BigInt.one)) { if (leastSignificantBit.eq(BigInt.one)) {
result = result.multiply(x) result = result.multiply(x);
result = result.remainder(n) result = result.remainder(n);
} }
x = x.multiply(x) x = x.multiply(x);
x = x.remainder(n) x = x.remainder(n);
} }
return result return result;
} }
@ -247,9 +249,9 @@ function modExp(a, b, n) {
* @returns {Buffer} * @returns {Buffer}
*/ */
function getByteArray(integer, signed = false) { function getByteArray(integer, signed = false) {
const bits = integer.toString(2).length const bits = integer.toString(2).length;
const byteLength = Math.floor((bits + 8 - 1) / 8) const byteLength = Math.floor((bits + 8 - 1) / 8);
return readBufferFromBigInt(BigInt(integer), byteLength, false, signed) return readBufferFromBigInt(BigInt(integer), byteLength, false, signed);
} }
/** /**
@ -259,9 +261,9 @@ function getByteArray(integer, signed = false) {
* @returns {number} * @returns {number}
*/ */
function getRandomInt(min, max) { function getRandomInt(min, max) {
min = Math.ceil(min) min = Math.ceil(min);
max = Math.floor(max) max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min return Math.floor(Math.random() * (max - min + 1)) + min;
} }
/** /**
@ -269,7 +271,7 @@ function getRandomInt(min, max) {
* @param ms time in milliseconds * @param ms time in milliseconds
* @returns {Promise} * @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 * Checks if the obj is an array
@ -293,36 +295,37 @@ function isArrayLike(obj) {
return true return true
} }
*/ */
// Taken from https://stackoverflow.com/questions/18638900/javascript-crc32/18639999#18639999 // Taken from https://stackoverflow.com/questions/18638900/javascript-crc32/18639999#18639999
function makeCRCTable() { function makeCRCTable() {
let c let c;
const crcTable = [] const crcTable = [];
for (let n = 0; n < 256; n++) { for (let n = 0; n < 256; n++) {
c = n c = n;
for (let k = 0; k < 8; k++) { 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) { function crc32(buf) {
if (!crcTable) { if (!crcTable) {
crcTable = makeCRCTable() crcTable = makeCRCTable();
} }
if (!Buffer.isBuffer(buf)) { 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++) { for (let index = 0; index < buf.length; index++) {
const byte = buf[index] const byte = buf[index];
crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8) crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
} }
return (crc ^ (-1)) >>> 0 return (crc ^ (-1)) >>> 0;
} }
module.exports = { module.exports = {
@ -343,6 +346,6 @@ module.exports = {
getByteArray, getByteArray,
//isArrayLike, //isArrayLike,
toSignedLittleBuffer, toSignedLittleBuffer,
convertToLittle convertToLittle,
} };

View File

@ -1,10 +1,16 @@
const BigInt = require('big-integer') const BigInt = require('big-integer');
const Factorizator = require('./crypto/Factorizator') const Factorizator = require('./crypto/Factorizator');
const { constructors } = require('./tl') const { constructors } = require('./tl');
const { readBigIntFromBuffer, readBufferFromBigInt, sha256, bigIntMod, modExp, const {
generateRandomBytes } = require('./Helpers') readBigIntFromBuffer,
const crypto = require('./crypto/crypto') readBufferFromBigInt,
const SIZE_FOR_HASH = 256 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 prime{BigInteger}
* @param g{BigInteger} * @param g{BigInteger}
*/ */
/* /*
We don't support changing passwords yet We don't support changing passwords yet
function checkPrimeAndGoodCheck(prime, g) { 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, 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, 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, 0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B,
]) ]);
if (goodPrime.equals(primeBytes)) { if (goodPrime.equals(primeBytes)) {
if ([ 3, 4, 5, 7 ].includes(g)) { if ([3, 4, 5, 7].includes(g)) {
return // It's good return; // It's good
} }
} }
throw new Error("Changing passwords unsupported") throw new Error('Changing passwords unsupported');
//checkPrimeAndGoodCheck(readBigIntFromBuffer(primeBytes, false), g) //checkPrimeAndGoodCheck(readBigIntFromBuffer(primeBytes, false), g)
} }
@ -97,7 +104,8 @@ function checkPrimeAndGood(primeBytes, g) {
* @returns {boolean} * @returns {boolean}
*/ */
function isGoodLarge(number, p) { 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} * @returns {Buffer}
*/ */
function numBytesForHash(number) { 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} * @returns {Buffer}
*/ */
function bigNumForHash(g) { 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} * @returns {Boolean}
*/ */
function isGoodModExpFirst(modexp, prime) { function isGoodModExpFirst(modexp, prime) {
const diff = prime.subtract(modexp) const diff = prime.subtract(modexp);
const minDiffBitsCount = 2048 - 64 const minDiffBitsCount = 2048 - 64;
const maxModExpSize = 256 const maxModExpSize = 256;
return !(diff.lesser(BigInt(0)) || diff.bitLength() < minDiffBitsCount || return !(diff.lesser(BigInt(0)) || diff.bitLength() < minDiffBitsCount ||
modexp.bitLength() < minDiffBitsCount || modexp.bitLength() < minDiffBitsCount ||
Math.floor((modexp.bitLength() + 7) / 8) > maxModExpSize) Math.floor((modexp.bitLength() + 7) / 8) > maxModExpSize);
} }
function xor(a, b) { 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++) { 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) { 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|*} * @returns {Buffer|*}
*/ */
async function computeHash(algo, password) { async function computeHash(algo, password) {
const hash1 = await sha256(Buffer.concat([ algo.salt1, Buffer.from(password, 'utf-8'), algo.salt1 ])) 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 hash2 = await sha256(Buffer.concat([algo.salt2, hash1, algo.salt2]));
const hash3 = await pbkdf2sha512(hash2, algo.salt1, 100000) const hash3 = await pbkdf2sha512(hash2, algo.salt1, 100000);
return sha256(Buffer.concat([ algo.salt2, hash3, algo.salt2 ])) return sha256(Buffer.concat([algo.salt2, hash3, algo.salt2]));
} }
/** /**
@ -177,15 +185,15 @@ async function computeHash(algo, password) {
*/ */
async function computeDigest(algo, password) { async function computeDigest(algo, password) {
try { try {
checkPrimeAndGood(algo.p, algo.g) checkPrimeAndGood(algo.p, algo.g);
} catch (e) { } catch (e) {
throw new Error('bad p/g in password') throw new Error('bad p/g in password');
} }
const value = modExp(BigInt(algo.g), const value = modExp(BigInt(algo.g),
readBigIntFromBuffer(await computeHash(algo, password), false), readBigIntFromBuffer(await computeHash(algo, password), false),
readBigIntFromBuffer(algo.p, false)) readBigIntFromBuffer(algo.p, false));
return bigNumForHash(value) return bigNumForHash(value);
} }
/** /**
@ -194,70 +202,70 @@ async function computeDigest(algo, password) {
* @param password {string} * @param password {string}
*/ */
async function computeCheck(request, password) { async function computeCheck(request, password) {
const algo = request.currentAlgo const algo = request.currentAlgo;
if (!(algo instanceof constructors.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)) { 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 pwHash = await computeHash(algo, password);
const p = readBigIntFromBuffer(algo.p, false) const p = readBigIntFromBuffer(algo.p, false);
const g = algo.g const g = algo.g;
const B = readBigIntFromBuffer(request.srp_B, false) const B = readBigIntFromBuffer(request.srp_B, false);
try { try {
checkPrimeAndGood(algo.p, g) checkPrimeAndGood(algo.p, g);
} catch (e) { } catch (e) {
throw new Error('bad /g in password') throw new Error('bad /g in password');
} }
if (!isGoodLarge(B, p)) { if (!isGoodLarge(B, p)) {
throw new Error('bad b in check') throw new Error('bad b in check');
} }
const x = readBigIntFromBuffer(pwHash, false) const x = readBigIntFromBuffer(pwHash, false);
const pForHash = numBytesForHash(algo.p) const pForHash = numBytesForHash(algo.p);
const gForHash = bigNumForHash(g) const gForHash = bigNumForHash(g);
const bForHash = numBytesForHash(request.srp_B) const bForHash = numBytesForHash(request.srp_B);
const gX = modExp(BigInt(g), x, p) const gX = modExp(BigInt(g), x, p);
const k = readBigIntFromBuffer(await sha256(Buffer.concat([ pForHash, gForHash ])), false) const k = readBigIntFromBuffer(await sha256(Buffer.concat([pForHash, gForHash])), false);
const kgX = bigIntMod(k.multiply(gX),p) const kgX = bigIntMod(k.multiply(gX), p);
const generateAndCheckRandom =async () => { const generateAndCheckRandom = async () => {
const randomSize = 256 const randomSize = 256;
// eslint-disable-next-line no-constant-condition // eslint-disable-next-line no-constant-condition
while (true) { while (true) {
const random = generateRandomBytes(randomSize) const random = generateRandomBytes(randomSize);
const a = readBigIntFromBuffer(random, false) const a = readBigIntFromBuffer(random, false);
const A = modExp(BigInt(g), a, p) const A = modExp(BigInt(g), a, p);
if (isGoodModExpFirst(A, p)) { if (isGoodModExpFirst(A, p)) {
const aForHash = bigNumForHash(A) const aForHash = bigNumForHash(A);
const u = readBigIntFromBuffer(await sha256(Buffer.concat([ aForHash, bForHash ])), false) const u = readBigIntFromBuffer(await sha256(Buffer.concat([aForHash, bForHash])), false);
if (u.greater(BigInt(0))) { if (u.greater(BigInt(0))) {
return [ a, aForHash, u ] return [a, aForHash, u];
} }
} }
} }
} };
const [ a, aForHash, u ] =await generateAndCheckRandom() const [a, aForHash, u] = await generateAndCheckRandom();
const gB = bigIntMod(B.subtract(kgX),p) const gB = bigIntMod(B.subtract(kgX), p);
if (!isGoodModExpFirst(gB, p)) { if (!isGoodModExpFirst(gB, p)) {
throw new Error('bad gB') throw new Error('bad gB');
} }
const ux = u.multiply(x) const ux = u.multiply(x);
const aUx = a.add(ux) const aUx = a.add(ux);
const S = modExp(gB, aUx, p) const S = modExp(gB, aUx, p);
const [K, pSha ,gSha, salt1Sha, salt2Sha] = await Promise.all([ const [K, pSha, gSha, salt1Sha, salt2Sha] = await Promise.all([
sha256(bigNumForHash(S)), sha256(bigNumForHash(S)),
sha256(pForHash), sha256(pForHash),
sha256(gForHash), sha256(gForHash),
sha256(algo.salt1), sha256(algo.salt1),
sha256(algo.salt2) sha256(algo.salt2),
]) ]);
const M1 = await sha256(Buffer.concat([ const M1 = await sha256(Buffer.concat([
xor(pSha,gSha), xor(pSha, gSha),
salt1Sha, salt1Sha,
salt2Sha, salt2Sha,
aForHash, aForHash,
bForHash, bForHash,
K, K,
])) ]));
return new constructors.InputCheckPasswordSRP({ return new constructors.InputCheckPasswordSRP({
@ -265,11 +273,11 @@ async function computeCheck(request, password) {
A: Buffer.from(aForHash), A: Buffer.from(aForHash),
M1: M1, M1: M1,
}) });
} }
module.exports = { module.exports = {
computeCheck, computeCheck,
computeDigest, computeDigest,
} };

View File

@ -1,18 +1,18 @@
const { constructors } = require('./tl') const { constructors } = require('./tl');
const USERNAME_RE = new RegExp('@|(?:https?:\\/\\/)?(?:www\\.)?' + 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_HEADER = Buffer.from('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00', 'hex');
const JPEG_FOOTER = Buffer.from('ffd9', 'hex') const JPEG_FOOTER = Buffer.from('ffd9', 'hex');
const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite=') const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite=');
const VALID_USERNAME_RE = new RegExp('^([a-z]((?!__)[\\w\\d]){3,30}[a-z\\d]|gif|vid|' + 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) { 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). // e.g. custom.Dialog (can't cyclic import).
if (allowSelf && 'inputEntity' in entity) { if (allowSelf && 'inputEntity' in entity) {
return entity.inputEntity return entity.inputEntity;
} else if ('entity' in entity) { } else if ('entity' in entity) {
return getInputPeer(entity.entity) return getInputPeer(entity.entity);
} else { } else {
_raiseCastFail(entity, 'InputPeer') _raiseCastFail(entity, 'InputPeer');
} }
} }
if (entity.SUBCLASS_OF_ID === 0xc91c90b6) { // crc32(b'InputPeer') if (entity.SUBCLASS_OF_ID === 0xc91c90b6) { // crc32(b'InputPeer')
return entity return entity;
} }
if (entity instanceof constructors.User) { if (entity instanceof constructors.User) {
if (entity.isSelf && allowSelf) { if (entity.isSelf && allowSelf) {
return new constructors.InputPeerSelf() return new constructors.InputPeerSelf();
} else if (entity.accessHash !== undefined || !checkHash) { } else if (entity.accessHash !== undefined || !checkHash) {
return new constructors.InputPeerUser({ return new constructors.InputPeerUser({
userId: entity.id, userId: entity.id,
accessHash: entity.accessHash, accessHash: entity.accessHash,
}) });
} else { } 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 || if (entity instanceof constructors.Chat || entity instanceof constructors.ChatEmpty ||
entity instanceof constructors.ChatForbidden) { 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 instanceof constructors.Channel) {
if (entity.accessHash !== undefined || !checkHash) { if (entity.accessHash !== undefined || !checkHash) {
return new constructors.InputPeerChannel({ return new constructors.InputPeerChannel({
channelId: entity.id, channelId: entity.id,
accessHash: entity.accessHash accessHash: entity.accessHash,
}) });
} else { } 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) { 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. // also not optional, we assume that this truly is the case.
return new constructors.InputPeerChannel({ return new constructors.InputPeerChannel({
channelId: entity.id, channelId: entity.id,
accessHash: entity.accessHash accessHash: entity.accessHash,
}) });
} }
if (entity instanceof constructors.InputUser) { if (entity instanceof constructors.InputUser) {
return new constructors.InputPeerUser({ return new constructors.InputPeerUser({
userId: entity.userId, userId: entity.userId,
accessHash: entity.accessHash accessHash: entity.accessHash,
}) });
} }
if (entity instanceof constructors.InputChannel) { if (entity instanceof constructors.InputChannel) {
return new constructors.InputPeerChannel({ return new constructors.InputPeerChannel({
channelId: entity.channelId, channelId: entity.channelId,
accessHash: entity.accessHash accessHash: entity.accessHash,
}) });
} }
if (entity instanceof constructors.UserEmpty) { if (entity instanceof constructors.UserEmpty) {
return new constructors.InputPeerEmpty() return new constructors.InputPeerEmpty();
} }
if (entity instanceof constructors.UserFull) { if (entity instanceof constructors.UserFull) {
return getInputPeer(entity.user) return getInputPeer(entity.user);
} }
if (entity instanceof constructors.ChatFull) { if (entity instanceof constructors.ChatFull) {
return new constructors.InputPeerChat({ chatId: entity.id }) return new constructors.InputPeerChat({ chatId: entity.id });
} }
if (entity instanceof constructors.PeerChat) { 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') _raiseCastFail(dialog, 'InputDialogPeer')
} }
*/ */
/*CONTEST /*CONTEST
function getInputMessage(message) { function getInputMessage(message) {
@ -259,12 +260,12 @@ function getInputMessage(message) {
function strippedPhotoToJpg(stripped) { function strippedPhotoToJpg(stripped) {
// Note: Changes here should update _stripped_real_length // Note: Changes here should update _stripped_real_length
if (stripped.length < 3 || stripped[0] !== 1) { if (stripped.length < 3 || stripped[0] !== 1) {
return stripped return stripped;
} }
const header = Buffer.from(JPEG_HEADER) const header = Buffer.from(JPEG_HEADER);
header[164] = stripped[1] header[164] = stripped[1];
header[166] = stripped[2] header[166] = stripped[2];
return Buffer.concat([header, stripped.slice(3), JPEG_FOOTER]) return Buffer.concat([header, stripped.slice(3), JPEG_FOOTER]);
} }
/*CONTEST /*CONTEST
@ -329,16 +330,16 @@ function getInputLocation(location) {
*/ */
function getAppropriatedPartSize(fileSize) { function getAppropriatedPartSize(fileSize) {
if (fileSize <= 104857600) { // 100MB if (fileSize <= 104857600) { // 100MB
return 128 return 128;
} }
if (fileSize <= 786432000) { // 750MB if (fileSize <= 786432000) { // 750MB
return 256 return 256;
} }
if (fileSize <= 1572864000) { // 1500MB if (fileSize <= 1572864000) { // 1500MB
return 512 return 512;
} }
throw new Error('File size too large') throw new Error('File size too large');
} }
/*CONTEST /*CONTEST
@ -480,6 +481,7 @@ function resolveId(markedId) {
* @returns {{inputEntity: *, entity: *}} * @returns {{inputEntity: *, entity: *}}
* @private * @private
*/ */
/*CONTEST /*CONTEST
function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer) { function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer) {
@ -501,15 +503,15 @@ function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer)
function getMessageId(message) { function getMessageId(message) {
if (message === null || message === undefined) { if (message === null || message === undefined) {
return null return null;
} }
if (typeof message == 'number') { if (typeof message == 'number') {
return message return message;
} }
if (message.SUBCLASS_OF_ID === 0x790009e3) { // crc32(b'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 * @param phone
*/ */
function parsePhone(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} * @param username {string}
*/ */
/*CONTEST /*CONTEST
function parseUsername(username) { function parseUsername(username) {
@ -577,18 +581,18 @@ function rtrim(s, mask) {
function getDisplayName(entity) { function getDisplayName(entity) {
if (entity instanceof constructors.User) { if (entity instanceof constructors.User) {
if (entity.lastName && entity.firstName) { if (entity.lastName && entity.firstName) {
return `${entity.firstName} ${entity.lastName}` return `${entity.firstName} ${entity.lastName}`;
} else if (entity.firstName) { } else if (entity.firstName) {
return entity.firstName return entity.firstName;
} else if (entity.lastName) { } else if (entity.lastName) {
return entity.lastName return entity.lastName;
} else { } else {
return '' return '';
} }
} else if (entity instanceof constructors.Chat || entity instanceof constructors.Channel) { } 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 * @param item
* @returns {boolean} * @returns {boolean}
*/ */
/*CONTEST /*CONTEST
Duplicate ? Duplicate ?
function isListLike(item) { function isListLike(item) {
@ -618,34 +623,34 @@ function getDC(dcId, cdn = false) {
return { return {
id: 1, id: 1,
ipAddress: 'pluto.web.telegram.org', ipAddress: 'pluto.web.telegram.org',
port: 443 port: 443,
} };
case 2: case 2:
return { return {
id: 2, id: 2,
ipAddress: 'venus.web.telegram.org', ipAddress: 'venus.web.telegram.org',
port: 443 port: 443,
} };
case 3: case 3:
return { return {
id: 3, id: 3,
ipAddress: 'aurora.web.telegram.org', ipAddress: 'aurora.web.telegram.org',
port: 443 port: 443,
} };
case 4: case 4:
return { return {
id: 4, id: 4,
ipAddress: 'vesta.web.telegram.org', ipAddress: 'vesta.web.telegram.org',
port: 443 port: 443,
} };
case 5: case 5:
return { return {
id: 5, id: 5,
ipAddress: 'flora.web.telegram.org', ipAddress: 'flora.web.telegram.org',
port: 443 port: 443,
} };
default: 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 // TODO chose based on current connection method
/* /*
@ -683,5 +688,5 @@ module.exports = {
getAppropriatedPartSize, getAppropriatedPartSize,
//getInputLocation, //getInputLocation,
strippedPhotoToJpg, strippedPhotoToJpg,
getDC getDC,
} };

View File

@ -1 +1 @@
module.exports = '0.0.2' module.exports = '0.0.2';

View File

@ -3,7 +3,7 @@ import { Api } from '..';
import { BotAuthParams, UserAuthParams } from './auth'; import { BotAuthParams, UserAuthParams } from './auth';
import { uploadFile, UploadFileParams } from './uploadFile'; import { uploadFile, UploadFileParams } from './uploadFile';
import { downloadFile, DownloadFileParams } from './downloadFile'; import { downloadFile, DownloadFileParams } from './downloadFile';
import { updateTwoFaSettings, TwoFaParams } from './2fa'; import { TwoFaParams, updateTwoFaSettings } from './2fa';
declare class TelegramClient { declare class TelegramClient {
constructor(...args: any) constructor(...args: any)

View File

@ -1,28 +1,34 @@
const Logger = require('../extensions/Logger') const Logger = require('../extensions/Logger');
const { sleep } = require('../Helpers') const { sleep } = require('../Helpers');
const errors = require('../errors') const errors = require('../errors');
const MemorySession = require('../sessions/Memory') const MemorySession = require('../sessions/Memory');
const Helpers = require('../Helpers') const Helpers = require('../Helpers');
const { BinaryWriter } = require('../extensions') const { BinaryWriter } = require('../extensions');
const utils = require('../Utils') const utils = require('../Utils');
const Session = require('../sessions/Abstract') const Session = require('../sessions/Abstract');
const os = require('os') const os = require('os');
const { LAYER } = require('../tl/AllTLObjects') const { LAYER } = require('../tl/AllTLObjects');
const { constructors, requests } = require('../tl') const {
const MTProtoSender = require('../network/MTProtoSender') constructors,
const { UpdateConnectionState } = require("../network") requests,
const { ConnectionTCPObfuscated } = require('../network/connection/TCPObfuscated') } = require('../tl');
const { authFlow, checkAuthorization } = require('./auth') const MTProtoSender = require('../network/MTProtoSender');
const { downloadFile } = require('./downloadFile') const { UpdateConnectionState } = require('../network');
const { uploadFile } = require('./uploadFile') const { ConnectionTCPObfuscated } = require('../network/connection/TCPObfuscated');
const { updateTwoFaSettings } = require('./2fa') const {
authFlow,
checkAuthorization,
} = require('./auth');
const { downloadFile } = require('./downloadFile');
const { uploadFile } = require('./uploadFile');
const { updateTwoFaSettings } = require('./2fa');
const DEFAULT_DC_ID = 2 const DEFAULT_DC_ID = 2;
const DEFAULT_IPV4_IP = 'venus.web.telegram.org' const DEFAULT_IPV4_IP = 'venus.web.telegram.org';
const DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]' const DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]';
// All types // 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 { class TelegramClient {
@ -45,7 +51,7 @@ class TelegramClient {
baseLogger: 'gramjs', baseLogger: 'gramjs',
useWSS: false, useWSS: false,
additionalDcsDisabled: false, additionalDcsDisabled: false,
} };
/** /**
* *
@ -56,52 +62,52 @@ class TelegramClient {
*/ */
constructor(session, apiId, apiHash, opts = TelegramClient.DEFAULT_OPTIONS) { constructor(session, apiId, apiHash, opts = TelegramClient.DEFAULT_OPTIONS) {
if (apiId === undefined || apiHash === undefined) { 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 } const args = { ...TelegramClient.DEFAULT_OPTIONS, ...opts };
this.apiId = apiId this.apiId = apiId;
this.apiHash = apiHash this.apiHash = apiHash;
this._useIPV6 = args.useIPV6 this._useIPV6 = args.useIPV6;
// this._entityCache = new Set() // this._entityCache = new Set()
if (typeof args.baseLogger == 'string') { if (typeof args.baseLogger == 'string') {
this._log = new Logger() this._log = new Logger();
} else { } else {
this._log = args.baseLogger this._log = args.baseLogger;
} }
// Determine what session we will use // Determine what session we will use
if (typeof session === 'string' || !session) { if (typeof session === 'string' || !session) {
try { try {
throw new Error('not implemented') throw new Error('not implemented');
} catch (e) { } catch (e) {
session = new MemorySession() session = new MemorySession();
} }
} else if (!(session instanceof Session)) { } 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.floodSleepLimit = args.floodSleepLimit;
this._eventBuilders = [] this._eventBuilders = [];
this._phoneCodeHash = {} this._phoneCodeHash = {};
this.session = session this.session = session;
// this._entityCache = EntityCache(); // this._entityCache = EntityCache();
this.apiId = parseInt(apiId) this.apiId = parseInt(apiId);
this.apiHash = apiHash this.apiHash = apiHash;
this._requestRetries = args.requestRetries this._requestRetries = args.requestRetries;
this._connectionRetries = args.connectionRetries this._connectionRetries = args.connectionRetries;
this._retryDelay = args.retryDelay || 0 this._retryDelay = args.retryDelay || 0;
if (args.proxy) { if (args.proxy) {
this._log.warn('proxies are not supported') this._log.warn('proxies are not supported');
} }
this._proxy = args.proxy this._proxy = args.proxy;
this._timeout = args.timeout this._timeout = args.timeout;
this._autoReconnect = args.autoReconnect this._autoReconnect = args.autoReconnect;
this._connection = args.connection this._connection = args.connection;
// TODO add proxy support // TODO add proxy support
this._floodWaitedRequests = {} this._floodWaitedRequests = {};
this._initWith = (x) => { this._initWith = (x) => {
return new requests.InvokeWithLayer({ return new requests.InvokeWithLayer({
@ -119,14 +125,14 @@ class TelegramClient {
query: x, query: x,
proxy: null, // no proxies yet. proxy: null, // no proxies yet.
}), }),
}) });
} };
this._args = args this._args = args;
// These will be set later // These will be set later
this._config = null this._config = null;
this.phoneCodeHashes = [] this.phoneCodeHashes = [];
this._borrowedSenderPromises = {} this._borrowedSenderPromises = {};
this._additionalDcsDisabled = args.additionalDcsDisabled; this._additionalDcsDisabled = args.additionalDcsDisabled;
} }
@ -140,7 +146,7 @@ class TelegramClient {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async connect() { async connect() {
await this._initSession() await this._initSession();
this._sender = new MTProtoSender(this.session.getAuthKey(), { this._sender = new MTProtoSender(this.session.getAuthKey(), {
logger: this._log, logger: this._log,
@ -152,45 +158,45 @@ class TelegramClient {
authKeyCallback: this._authKeyCallback.bind(this), authKeyCallback: this._authKeyCallback.bind(this),
updateCallback: this._handleUpdate.bind(this), updateCallback: this._handleUpdate.bind(this),
isMainSender: true, isMainSender: true,
}) });
const connection = new this._connection( 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( await this._sender.send(this._initWith(
new requests.help.GetConfig({}), new requests.help.GetConfig({}),
)) ));
this._updateLoop() this._updateLoop();
} }
async _initSession() { async _initSession() {
await this.session.load() await this.session.load();
if (!this.session.serverAddress || (this.session.serverAddress.includes(':') !== this._useIPV6)) { 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() { async _updateLoop() {
while (this.isConnected()) { while (this.isConnected()) {
await Helpers.sleep(3 * 1000) await Helpers.sleep(3 * 1000);
try { try {
await attempts(() => { await attempts(() => {
return timeout(this._sender.send(new requests.Ping({ return timeout(this._sender.send(new requests.Ping({
pingId: Helpers.getRandomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) pingId: Helpers.getRandomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER),
})), 1500) })), 1500);
}, 3, 100) }, 3, 100);
} catch (err) { } catch (err) {
await this.disconnect() await this.disconnect();
this.connect() this.connect();
return return;
} }
// We need to send some content-related request at least hourly // 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 // TODO Call getDifference instead since it's more relevant
if (new Date().getTime() - this._lastRequest > 30 * 60 * 1000) { if (new Date().getTime() - this._lastRequest > 30 * 60 * 1000) {
try { try {
await this.invoke(new requests.updates.GetState()) await this.invoke(new requests.updates.GetState());
} catch (e) { } catch (e) {
} }
@ -214,20 +220,21 @@ class TelegramClient {
*/ */
async disconnect() { async disconnect() {
if (this._sender) { if (this._sender) {
await this._sender.disconnect() await this._sender.disconnect();
} }
await Promise.all( await Promise.all(
Object.values(this._borrowedSenderPromises).map((promise) => { Object.values(this._borrowedSenderPromises)
return promise.then((sender) => { .map((promise) => {
if (sender) { return promise.then((sender) => {
return sender.disconnect() if (sender) {
} return sender.disconnect();
}) }
}) });
) }),
);
this._borrowedSenderPromises = {} this._borrowedSenderPromises = {};
} }
/** /**
@ -235,55 +242,55 @@ class TelegramClient {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async destroy() { async destroy() {
await this.disconnect() await this.disconnect();
this.session.delete() this.session.delete();
this._eventBuilders = [] this._eventBuilders = [];
} }
async _switchDC(newDc) { async _switchDC(newDc) {
this._log.info(`Reconnecting to new data center ${newDc}`) this._log.info(`Reconnecting to new data center ${newDc}`);
const DC = utils.getDC(newDc) const DC = utils.getDC(newDc);
this.session.setDC(newDc, DC.ipAddress, DC.port) this.session.setDC(newDc, DC.ipAddress, DC.port);
// authKey's are associated with a server, which has now changed // authKey's are associated with a server, which has now changed
// so it's not valid anymore. Set to None to force recreating it. // so it's not valid anymore. Set to None to force recreating it.
await this._sender.authKey.setKey(null) await this._sender.authKey.setKey(null);
this.session.setAuthKey(null) this.session.setAuthKey(null);
await this.disconnect() await this.disconnect();
return this.connect() return this.connect();
} }
async _authKeyCallback(authKey, dcId) { async _authKeyCallback(authKey, dcId) {
this.session.setAuthKey(authKey, dcId) this.session.setAuthKey(authKey, dcId);
} }
// endregion // endregion
// export region // export region
_cleanupBorrowedSender(dcId) { _cleanupBorrowedSender(dcId) {
delete this._borrowedSenderPromises[dcId] delete this._borrowedSenderPromises[dcId];
} }
async _borrowExportedSender(dcId) { async _borrowExportedSender(dcId) {
if (this._additionalDcsDisabled) { if (this._additionalDcsDisabled) {
return undefined return undefined;
} }
let senderPromise = this._borrowedSenderPromises[dcId] let senderPromise = this._borrowedSenderPromises[dcId];
if (!senderPromise) { if (!senderPromise) {
senderPromise = this._createExportedSender(dcId) senderPromise = this._createExportedSender(dcId);
this._borrowedSenderPromises[dcId] = senderPromise this._borrowedSenderPromises[dcId] = senderPromise;
senderPromise.then((sender) => { senderPromise.then((sender) => {
if (!sender) { if (!sender) {
delete this._borrowedSenderPromises[dcId] delete this._borrowedSenderPromises[dcId];
} }
}); });
} }
return senderPromise return senderPromise;
} }
async _createExportedSender(dcId) { async _createExportedSender(dcId) {
const dc = utils.getDC(dcId) const dc = utils.getDC(dcId);
const sender = new MTProtoSender(this.session.getAuthKey(dcId), const sender = new MTProtoSender(this.session.getAuthKey(dcId),
{ {
logger: this._log, logger: this._log,
@ -295,7 +302,7 @@ class TelegramClient {
authKeyCallback: this._authKeyCallback.bind(this), authKeyCallback: this._authKeyCallback.bind(this),
isMainSender: dcId === this.session.dcId, isMainSender: dcId === this.session.dcId,
onConnectionBreak: this._cleanupBorrowedSender.bind(this), onConnectionBreak: this._cleanupBorrowedSender.bind(this),
}) });
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
try { try {
await sender.connect(new this._connection( await sender.connect(new this._connection(
@ -303,24 +310,24 @@ class TelegramClient {
dc.port, dc.port,
dcId, dcId,
this._log, this._log,
)) ));
if (this.session.dcId !== dcId) { if (this.session.dcId !== dcId) {
this._log.info(`Exporting authorization for data center ${dc.ipAddress}`) this._log.info(`Exporting authorization for data center ${dc.ipAddress}`);
const auth = await this.invoke(new requests.auth.ExportAuthorization({ dcId: dcId })) const auth = await this.invoke(new requests.auth.ExportAuthorization({ dcId: dcId }));
const req = this._initWith(new requests.auth.ImportAuthorization({ const req = this._initWith(new requests.auth.ImportAuthorization({
id: auth.id, id: auth.id,
bytes: auth.bytes, bytes: auth.bytes,
}, },
)) ));
await sender.send(req) await sender.send(req);
} }
sender.dcId = dcId sender.dcId = dcId;
return sender return sender;
} catch (e) { } catch (e) {
await sender.disconnect() await sender.disconnect();
} }
} }
return null return null;
} }
// end region // end region
@ -340,85 +347,85 @@ class TelegramClient {
* @returns {Promise<Buffer>} * @returns {Promise<Buffer>}
*/ */
async downloadFile(inputLocation, args = {}) { async downloadFile(inputLocation, args = {}) {
return downloadFile(this, inputLocation, args) return downloadFile(this, inputLocation, args);
} }
async downloadMedia(messageOrMedia, args) { async downloadMedia(messageOrMedia, args) {
let date let date;
let media let media;
if (messageOrMedia instanceof constructors.Message) { if (messageOrMedia instanceof constructors.Message) {
date = messageOrMedia.date date = messageOrMedia.date;
media = messageOrMedia.media media = messageOrMedia.media;
} else { } else {
date = new Date().getTime() date = new Date().getTime();
media = messageOrMedia media = messageOrMedia;
} }
if (typeof media == 'string') { if (typeof media == 'string') {
throw new Error('not implemented') throw new Error('not implemented');
} }
if (media instanceof constructors.MessageMediaWebPage) { if (media instanceof constructors.MessageMediaWebPage) {
if (media.webpage instanceof constructors.WebPage) { 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) { 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) { } 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) { } 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) { } 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) { async downloadProfilePhoto(entity, isBig = false) {
// ('User', 'Chat', 'UserFull', 'ChatFull') // ('User', 'Chat', 'UserFull', 'ChatFull')
const ENTITIES = [0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697] const ENTITIES = [0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697];
// ('InputPeer', 'InputUser', 'InputChannel') // ('InputPeer', 'InputUser', 'InputChannel')
// const INPUTS = [0xc91c90b6, 0xe669bf46, 0x40f202fd] // const INPUTS = [0xc91c90b6, 0xe669bf46, 0x40f202fd]
// Todo account for input methods // Todo account for input methods
const sizeType = isBig ? 'x' : 'm' const sizeType = isBig ? 'x' : 'm';
let photo let photo;
if (!(ENTITIES.includes(entity.SUBCLASS_OF_ID))) { if (!(ENTITIES.includes(entity.SUBCLASS_OF_ID))) {
photo = entity photo = entity;
} else { } else {
if (!entity.photo) { if (!entity.photo) {
// Special case: may be a ChatFull with photo:Photo // Special case: may be a ChatFull with photo:Photo
if (!entity.chatPhoto) { if (!entity.chatPhoto) {
return null return null;
} }
return this._downloadPhoto( return this._downloadPhoto(
entity.chatPhoto, { sizeType }, entity.chatPhoto, { sizeType },
) );
} }
photo = entity.photo photo = entity.photo;
} }
let dcId let dcId;
let loc let loc;
if (photo instanceof constructors.UserProfilePhoto || photo instanceof constructors.ChatPhoto) { if (photo instanceof constructors.UserProfilePhoto || photo instanceof constructors.ChatPhoto) {
dcId = photo.dcId dcId = photo.dcId;
const size = isBig ? photo.photoBig : photo.photoSmall const size = isBig ? photo.photoBig : photo.photoSmall;
loc = new constructors.InputPeerPhotoFileLocation({ loc = new constructors.InputPeerPhotoFileLocation({
peer: utils.getInputPeer(entity), peer: utils.getInputPeer(entity),
localId: size.localId, localId: size.localId,
volumeId: size.volumeId, volumeId: size.volumeId,
big: isBig, big: isBig,
}) });
} else { } else {
// It doesn't make any sense to check if `photo` can be used // It doesn't make any sense to check if `photo` can be used
// as input location, because then this method would be able // as input location, because then this method would be able
// to "download the profile photo of a message", i.e. its // to "download the profile photo of a message", i.e. its
// media which should be done with `download_media` instead. // media which should be done with `download_media` instead.
return null return null;
} }
try { try {
return this.downloadFile(loc, { return this.downloadFile(loc, {
dcId: dcId, dcId: dcId,
}) });
} catch (e) { } catch (e) {
// TODO this should never raise // TODO this should never raise
throw e; throw e;
@ -440,65 +447,65 @@ class TelegramClient {
async downloadStickerSetThumb(stickerSet) { async downloadStickerSetThumb(stickerSet) {
if (!stickerSet.thumb || !stickerSet.thumb.location) { if (!stickerSet.thumb || !stickerSet.thumb.location) {
return undefined return undefined;
} }
const { location } = stickerSet.thumb const { location } = stickerSet.thumb;
return this.downloadFile( return this.downloadFile(
new constructors.InputStickerSetThumb({ new constructors.InputStickerSetThumb({
stickerset: new constructors.InputStickerSetID({ stickerset: new constructors.InputStickerSetID({
id: stickerSet.id, id: stickerSet.id,
accessHash: stickerSet.accessHash accessHash: stickerSet.accessHash,
}), }),
localId: location.localId, localId: location.localId,
volumeId: location.volumeId, volumeId: location.volumeId,
}), }),
{ dcId: stickerSet.thumbDcId } { dcId: stickerSet.thumbDcId },
) );
} }
_pickFileSize(sizes, sizeType) { _pickFileSize(sizes, sizeType) {
if (!sizeType || !sizes || !sizes.length) { if (!sizeType || !sizes || !sizes.length) {
return null return null;
} }
const indexOfSize = sizeTypes.indexOf(sizeType) const indexOfSize = sizeTypes.indexOf(sizeType);
let size let size;
for (let i = indexOfSize; i < sizeTypes.length; i++) { 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) { if (size) {
return size return size;
} }
} }
return null return null;
} }
_downloadCachedPhotoSize(size) { _downloadCachedPhotoSize(size) {
// No need to download anything, simply write the bytes // No need to download anything, simply write the bytes
let data let data;
if (size instanceof constructors.PhotoStrippedSize) { if (size instanceof constructors.PhotoStrippedSize) {
data = utils.strippedPhotoToJpg(size.bytes) data = utils.strippedPhotoToJpg(size.bytes);
} else { } else {
data = size.bytes data = size.bytes;
} }
return data return data;
} }
async _downloadPhoto(photo, args) { async _downloadPhoto(photo, args) {
if (photo instanceof constructors.MessageMediaPhoto) { if (photo instanceof constructors.MessageMediaPhoto) {
photo = photo.photo photo = photo.photo;
} }
if (!(photo instanceof constructors.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)) { if (!size || (size instanceof constructors.PhotoSizeEmpty)) {
return return;
} }
if (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize) { if (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize) {
return this._downloadCachedPhotoSize(size) return this._downloadCachedPhotoSize(size);
} }
return this.downloadFile( return this.downloadFile(
new constructors.InputPhotoFileLocation({ new constructors.InputPhotoFileLocation({
@ -512,26 +519,26 @@ class TelegramClient {
fileSize: size.size, fileSize: size.size,
progressCallback: args.progressCallback, progressCallback: args.progressCallback,
}, },
) );
} }
async _downloadDocument(doc, args) { async _downloadDocument(doc, args) {
if (doc instanceof constructors.MessageMediaDocument) { if (doc instanceof constructors.MessageMediaDocument) {
doc = doc.document doc = doc.document;
} }
if (!(doc instanceof constructors.Document)) { if (!(doc instanceof constructors.Document)) {
return return;
} }
let size = null let size = null;
if (args.sizeType) { 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/')) { if (!size && doc.mimeType.startsWith('video/')) {
return return;
} }
if (size && (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize)) { 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, dcId: doc.dcId,
workers: args.workers, workers: args.workers,
}, },
) );
} }
_downloadContact(media, args) { _downloadContact(media, args) {
throw new Error('not implemented') throw new Error('not implemented');
} }
_downloadWebDocument(media, args) { _downloadWebDocument(media, args) {
throw new Error('not implemented') throw new Error('not implemented');
} }
// region Invoking Telegram request // region Invoking Telegram request
@ -569,76 +576,76 @@ class TelegramClient {
*/ */
async invoke(request) { async invoke(request) {
if (request.classType !== '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 // This causes issues for now because not enough utils
// await request.resolve(this, utils) // await request.resolve(this, utils)
this._lastRequest = new Date().getTime() this._lastRequest = new Date().getTime();
let attempt = 0 let attempt = 0;
for (attempt = 0; attempt < this._requestRetries; attempt++) { for (attempt = 0; attempt < this._requestRetries; attempt++) {
try { try {
const promise = this._sender.send(request) const promise = this._sender.send(request);
const result = await promise const result = await promise;
//this.session.processEntities(result) //this.session.processEntities(result)
// this._entityCache.add(result) // this._entityCache.add(result)
return result return result;
} catch (e) { } catch (e) {
if (e instanceof errors.ServerError || e.message === 'RPC_CALL_FAIL' || if (e instanceof errors.ServerError || e.message === 'RPC_CALL_FAIL' ||
e.message === 'RPC_MCGET_FAIL') { e.message === 'RPC_MCGET_FAIL') {
this._log.warn(`Telegram is having internal issues ${e.constructor.name}`) this._log.warn(`Telegram is having internal issues ${e.constructor.name}`);
await sleep(2000) await sleep(2000);
} else if (e instanceof errors.FloodWaitError || e instanceof errors.FloodTestPhoneWaitError) { } else if (e instanceof errors.FloodWaitError || e instanceof errors.FloodTestPhoneWaitError) {
if (e.seconds <= this.floodSleepLimit) { if (e.seconds <= this.floodSleepLimit) {
this._log.info(`Sleeping for ${e.seconds}s on flood wait`) this._log.info(`Sleeping for ${e.seconds}s on flood wait`);
await sleep(e.seconds * 1000) await sleep(e.seconds * 1000);
} else { } else {
throw e throw e;
} }
} else if (e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError || } else if (e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError ||
e instanceof errors.UserMigrateError) { e instanceof errors.UserMigrateError) {
this._log.info(`Phone migrated to ${e.newDc}`) this._log.info(`Phone migrated to ${e.newDc}`);
const shouldRaise = e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError const shouldRaise = e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError;
if (shouldRaise && await checkAuthorization(this)) { if (shouldRaise && await checkAuthorization(this)) {
throw e throw e;
} }
await this._switchDC(e.newDc) await this._switchDC(e.newDc);
} else { } 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() { async getMe() {
try { try {
return (await this.invoke(new requests.users return (await this.invoke(new requests.users
.GetUsers({ id: [new constructors.InputUserSelf()] })))[0] .GetUsers({ id: [new constructors.InputUserSelf()] })))[0];
} catch (e) { } catch (e) {
} }
} }
async start(authParams) { async start(authParams) {
if (!this.isConnected()) { if (!this.isConnected()) {
await this.connect() await this.connect();
} }
if (await checkAuthorization(this)) { if (await checkAuthorization(this)) {
return return;
} }
const apiCredentials = { const apiCredentials = {
apiId: this.apiId, apiId: this.apiId,
apiHash: this.apiHash apiHash: this.apiHash,
} };
await authFlow(this, apiCredentials, authParams) await authFlow(this, apiCredentials, authParams);
} }
uploadFile(fileParams) { uploadFile(fileParams) {
return uploadFile(this, fileParams) return uploadFile(this, fileParams);
} }
updateTwoFaSettings(params) { updateTwoFaSettings(params) {
@ -647,7 +654,7 @@ class TelegramClient {
// event region // event region
addEventHandler(callback, event) { addEventHandler(callback, event) {
this._eventBuilders.push([event, callback]) this._eventBuilders.push([event, callback]);
} }
_handleUpdate(update) { _handleUpdate(update) {
@ -656,29 +663,29 @@ class TelegramClient {
if (update instanceof constructors.Updates || update instanceof constructors.UpdatesCombined) { if (update instanceof constructors.Updates || update instanceof constructors.UpdatesCombined) {
// TODO deal with entities // TODO deal with entities
const entities = [] const entities = [];
for (const x of [...update.users, ...update.chats]) { for (const x of [...update.users, ...update.chats]) {
entities.push(x) entities.push(x);
} }
for (const u of update.updates) { for (const u of update.updates) {
this._processUpdate(u, update.updates, entities) this._processUpdate(u, update.updates, entities);
} }
} else if (update instanceof constructors.UpdateShort) { } else if (update instanceof constructors.UpdateShort) {
this._processUpdate(update.update, null) this._processUpdate(update.update, null);
} else { } else {
this._processUpdate(update, null) this._processUpdate(update, null);
} }
// TODO add caching // TODO add caching
// this._stateCache.update(update) // this._stateCache.update(update)
} }
_processUpdate(update, others, entities) { _processUpdate(update, others, entities) {
update._entities = entities || [] update._entities = entities || [];
const args = { const args = {
update: update, update: update,
others: others, others: others,
} };
this._dispatchUpdate(args) this._dispatchUpdate(args);
} }
@ -830,6 +837,7 @@ class TelegramClient {
* @param peer * @param peer
* @returns {Promise<>} * @returns {Promise<>}
*/ */
/*CONTEST /*CONTEST
async getInputEntity(peer) { async getInputEntity(peer) {
// Short-circuit if the input parameter directly maps to an InputPeer // Short-circuit if the input parameter directly maps to an InputPeer
@ -918,9 +926,9 @@ class TelegramClient {
ptsDate: null, ptsDate: null,
}) { }) {
for (const [builder, callback] of this._eventBuilders) { for (const [builder, callback] of this._eventBuilders) {
const event = builder.build(args.update) const event = builder.build(args.update);
if (event) { if (event) {
await callback(event) await callback(event);
} }
} }
} }
@ -928,33 +936,34 @@ class TelegramClient {
isConnected() { isConnected() {
if (this._sender) { if (this._sender) {
if (this._sender.isConnected()) { if (this._sender.isConnected()) {
return true return true;
} }
} }
return false return false;
} }
} }
async function timeout(promise, ms) { async function timeout(promise, ms) {
return Promise.race([ return Promise.race([
promise, 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) { async function attempts(cb, times, pause) {
for (let i = 0; i < times; i++) { for (let i = 0; i < times; i++) {
try { try {
// We need to `return await` here so it can be caught locally // We need to `return await` here so it can be caught locally
return await cb() return await cb();
} catch (err) { } catch (err) {
if (i === times - 1) { if (i === times - 1) {
throw err throw err;
} }
await Helpers.sleep(pause) await Helpers.sleep(pause);
} }
} }
} }
module.exports = TelegramClient module.exports = TelegramClient;

View File

@ -169,7 +169,7 @@ async function signInUserWithQrCode(
const inputPromise = (async () => { const inputPromise = (async () => {
while (1) { while (1) {
if (isScanningComplete){ if (isScanningComplete) {
break; break;
} }

View File

@ -4,13 +4,13 @@ import { getAppropriatedPartSize } from '../Utils';
import { sleep } from '../Helpers'; import { sleep } from '../Helpers';
export interface progressCallback { export interface progressCallback {
isCanceled?: boolean;
acceptsBuffer?: boolean;
( (
progress: number, // Float between 0 and 1. progress: number, // Float between 0 and 1.
...args: any[] ...args: any[]
): void; ): void;
isCanceled?: boolean;
acceptsBuffer?: boolean;
} }
export interface DownloadFileParams { export interface DownloadFileParams {

View File

@ -5,10 +5,10 @@ import { generateRandomBytes, readBigIntFromBuffer, sleep } from '../Helpers';
import { getAppropriatedPartSize } from '../Utils'; import { getAppropriatedPartSize } from '../Utils';
interface OnProgress { interface OnProgress {
isCanceled?: boolean;
// Float between 0 and 1. // Float between 0 and 1.
(progress: number): void; (progress: number): void;
isCanceled?: boolean;
} }
export interface UploadFileParams { export interface UploadFileParams {

View File

@ -1,49 +1,54 @@
const { sha1, toSignedLittleBuffer,readBufferFromBigInt, readBigIntFromBuffer } = require('../Helpers') const {
const BinaryReader = require('../extensions/BinaryReader') sha1,
const { sleep } = require('../Helpers') toSignedLittleBuffer,
readBufferFromBigInt,
readBigIntFromBuffer,
} = require('../Helpers');
const BinaryReader = require('../extensions/BinaryReader');
const { sleep } = require('../Helpers');
class AuthKey { class AuthKey {
constructor(value, hash) { constructor(value, hash) {
if (!hash || !value) { if (!hash || !value) {
return return;
} }
this._key = value this._key = value;
this._hash = hash this._hash = hash;
const reader = new BinaryReader(hash) const reader = new BinaryReader(hash);
this.auxHash = reader.readLong(false) this.auxHash = reader.readLong(false);
reader.read(4) reader.read(4);
this.keyId = reader.readLong(false) this.keyId = reader.readLong(false);
} }
async setKey(value) { async setKey(value) {
if (!value) { if (!value) {
this._key = this.auxHash = this.keyId = this._hash = null this._key = this.auxHash = this.keyId = this._hash = null;
return return;
} }
if (value instanceof AuthKey) { if (value instanceof AuthKey) {
this._key = value._key this._key = value._key;
this.auxHash = value.auxHash this.auxHash = value.auxHash;
this.keyId = value.keyId this.keyId = value.keyId;
this._hash = value._hash this._hash = value._hash;
return return;
} }
this._key = value this._key = value;
this._hash = await sha1(this._key) this._hash = await sha1(this._key);
const reader = new BinaryReader(this._hash) const reader = new BinaryReader(this._hash);
this.auxHash = reader.readLong(false) this.auxHash = reader.readLong(false);
reader.read(4) reader.read(4);
this.keyId = reader.readLong(false) this.keyId = reader.readLong(false);
} }
async waitForKey() { async waitForKey() {
while (!this.keyId) { while (!this.keyId) {
await sleep(20) await sleep(20);
} }
} }
getKey() { getKey() {
return this._key return this._key;
} }
// TODO : This doesn't really fit here, it's only used in authentication // TODO : This doesn't really fit here, it's only used in authentication
@ -55,20 +60,21 @@ class AuthKey {
* @returns {bigint} * @returns {bigint}
*/ */
async calcNewNonceHash(newNonce, number) { async calcNewNonceHash(newNonce, number) {
newNonce = toSignedLittleBuffer(newNonce, 32) newNonce = toSignedLittleBuffer(newNonce, 32);
const n = Buffer.alloc(1) const n = Buffer.alloc(1);
n.writeUInt8(number, 0) n.writeUInt8(number, 0);
const data = Buffer.concat([newNonce, 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 // Calculates the message key from the given data
const shaData = (await sha1(data)).slice(4, 20) const shaData = (await sha1(data)).slice(4, 20);
return readBigIntFromBuffer(shaData, true, true) return readBigIntFromBuffer(shaData, true, true);
} }
equals(other) { 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;

View File

@ -1,17 +1,17 @@
const crypto = require('./crypto') const crypto = require('./crypto');
class CTR { class CTR {
constructor(key, iv) { constructor(key, iv) {
if (!Buffer.isBuffer(key) || !Buffer.isBuffer(iv) || iv.length !== 16) { 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) { encrypt(data) {
return Buffer.from(this.cipher.update(data)) return Buffer.from(this.cipher.update(data));
} }
} }
module.exports = CTR module.exports = CTR;

View File

@ -1,5 +1,5 @@
const BigInt = require('big-integer') const BigInt = require('big-integer');
const { modExp } = require('../Helpers') const { modExp } = require('../Helpers');
class Factorizator { class Factorizator {
/** /**
@ -10,11 +10,11 @@ class Factorizator {
*/ */
static gcd(a, b) { static gcd(a, b) {
while (b.neq(BigInt.zero)) { while (b.neq(BigInt.zero)) {
let temp = b let temp = b;
b = a.remainder(b) b = a.remainder(b);
a = temp a = temp;
} }
return a return a;
} }
/** /**
@ -23,57 +23,75 @@ class Factorizator {
* @returns {{p: *, q: *}} * @returns {{p: *, q: *}}
*/ */
static factorize(pq) { static factorize(pq) {
if (pq.remainder(2).equals(BigInt.zero)) { if (pq.remainder(2)
return { p: BigInt(2), q: pq.divide(BigInt(2)) } .equals(BigInt.zero)) {
return {
p: BigInt(2),
q: pq.divide(BigInt(2)),
};
} }
let y = 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 c = BigInt.randBetween(BigInt(1), pq.minus(1));
const m = BigInt.randBetween(BigInt(1),pq.minus(1)) const m = BigInt.randBetween(BigInt(1), pq.minus(1));
let g = BigInt.one let g = BigInt.one;
let r = BigInt.one let r = BigInt.one;
let q = BigInt.one let q = BigInt.one;
let x = BigInt.zero let x = BigInt.zero;
let ys = BigInt.zero let ys = BigInt.zero;
let k let k;
while (g.eq(BigInt.one)) { while (g.eq(BigInt.one)) {
x = y x = y;
for (let i = 0; BigInt(i).lesser(r); i++) { for (let i = 0; BigInt(i)
y = (modExp(y, BigInt(2), pq)).add(c).remainder(pq) .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)) { while (k.lesser(r) && g.eq(BigInt.one)) {
ys = y ys = y;
let condition = BigInt.min(m, r.minus(k)) let condition = BigInt.min(m, r.minus(k));
for (let i = 0; BigInt(i).lesser(condition); i++) { for (let i = 0; BigInt(i)
y = (modExp(y, BigInt(2), pq)).add(c).remainder(pq) .lesser(condition); i++) {
q = q.multiply(x.minus(y).abs()).remainder(pq) y = (modExp(y, BigInt(2), pq)).add(c)
.remainder(pq);
q = q.multiply(x.minus(y)
.abs())
.remainder(pq);
} }
g = Factorizator.gcd(q, pq) g = Factorizator.gcd(q, pq);
k = k.add(m) k = k.add(m);
} }
r = r.multiply(2) r = r.multiply(2);
} }
if (g.eq(pq)) { if (g.eq(pq)) {
while (true) { while (true) {
ys = (modExp(ys, BigInt(2), pq)).add(c).remainder(pq) ys = (modExp(ys, BigInt(2), pq)).add(c)
g = Factorizator.gcd(x.minus(ys).abs(), pq) .remainder(pq);
g = Factorizator.gcd(x.minus(ys)
.abs(), pq);
if (g.greater(1)) { if (g.greater(1)) {
break break;
} }
} }
} }
const p = g const p = g;
q = pq.divide(g) q = pq.divide(g);
return p < q ? { p: p, q: q } : { p: q, q: p } return p < q ? {
p: p,
q: q,
} : {
p: q,
q: p,
};
} }
} }
module.exports = Factorizator module.exports = Factorizator;

View File

@ -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 { class IGENEW {
constructor(key, iv) { 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} * @returns {Buffer}
*/ */
encryptIge(plainText) { encryptIge(plainText) {
const padding = plainText.length % 16 const padding = plainText.length % 16;
if (padding) { 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)); return Helpers.convertToLittle(this.ige.encrypt(plainText));
@ -34,4 +34,4 @@ class IGENEW {
} }
module.exports = IGENEW module.exports = IGENEW;

View File

@ -1,29 +1,39 @@
const BigInt = require('big-integer') const BigInt = require('big-integer');
const { readBigIntFromBuffer, readBufferFromBigInt, getByteArray, sha1, generateRandomBytes, modExp } = require('../Helpers') const {
readBigIntFromBuffer,
readBufferFromBigInt,
getByteArray,
sha1,
generateRandomBytes,
modExp,
} = require('../Helpers');
const PUBLIC_KEYS = [{ const PUBLIC_KEYS = [{
'fingerprint': [40, 85, 94, 156, 117, 240, 61, 22, 65, 244, 169, 2, 33, 107, 232, 108, 2, 43, 180, 195], 'fingerprint': [40, 85, 94, 156, 117, 240, 61, 22, 65, 244, 169, 2, 33, 107, 232, 108, 2, 43, 180, 195],
'n': BigInt('24403446649145068056824081744112065346446136066297307473868293895086332508101251964919587745984311372853053253457835208829824428441874946556659953519213382748319518214765985662663680818277989736779506318868003755216402538945900388706898101286548187286716959100102939636333452457308619454821845196109544157601096359148241435922125602449263164512290854366930013825808102403072317738266383237191313714482187326643144603633877219028262697593882410403273959074350849923041765639673335775605842311578109726403165298875058941765362622936097839775380070572921007586266115476975819175319995527916042178582540628652481530373407'), 'n': BigInt('24403446649145068056824081744112065346446136066297307473868293895086332508101251964919587745984311372853053253457835208829824428441874946556659953519213382748319518214765985662663680818277989736779506318868003755216402538945900388706898101286548187286716959100102939636333452457308619454821845196109544157601096359148241435922125602449263164512290854366930013825808102403072317738266383237191313714482187326643144603633877219028262697593882410403273959074350849923041765639673335775605842311578109726403165298875058941765362622936097839775380070572921007586266115476975819175319995527916042178582540628652481530373407'),
'e': 65537 'e': 65537,
}, { }, {
'fingerprint': [140, 171, 9, 34, 146, 246, 166, 50, 10, 170, 229, 247, 155, 114, 28, 177, 29, 106, 153, 154], 'fingerprint': [140, 171, 9, 34, 146, 246, 166, 50, 10, 170, 229, 247, 155, 114, 28, 177, 29, 106, 153, 154],
'n': BigInt('25081407810410225030931722734886059247598515157516470397242545867550116598436968553551465554653745201634977779380884774534457386795922003815072071558370597290368737862981871277312823942822144802509055492512145589734772907225259038113414940384446493111736999668652848440655603157665903721517224934142301456312994547591626081517162758808439979745328030376796953660042629868902013177751703385501412640560275067171555763725421377065095231095517201241069856888933358280729674273422117201596511978645878544308102076746465468955910659145532699238576978901011112475698963666091510778777356966351191806495199073754705289253783'), 'n': BigInt('25081407810410225030931722734886059247598515157516470397242545867550116598436968553551465554653745201634977779380884774534457386795922003815072071558370597290368737862981871277312823942822144802509055492512145589734772907225259038113414940384446493111736999668652848440655603157665903721517224934142301456312994547591626081517162758808439979745328030376796953660042629868902013177751703385501412640560275067171555763725421377065095231095517201241069856888933358280729674273422117201596511978645878544308102076746465468955910659145532699238576978901011112475698963666091510778777356966351191806495199073754705289253783'),
'e': 65537 'e': 65537,
}, { }, {
'fingerprint': [243, 218, 109, 239, 16, 202, 176, 78, 167, 8, 255, 209, 120, 234, 205, 112, 111, 42, 91, 176], 'fingerprint': [243, 218, 109, 239, 16, 202, 176, 78, 167, 8, 255, 209, 120, 234, 205, 112, 111, 42, 91, 176],
'n': BigInt('22347337644621997830323797217583448833849627595286505527328214795712874535417149457567295215523199212899872122674023936713124024124676488204889357563104452250187725437815819680799441376434162907889288526863223004380906766451781702435861040049293189979755757428366240570457372226323943522935844086838355728767565415115131238950994049041950699006558441163206523696546297006014416576123345545601004508537089192869558480948139679182328810531942418921113328804749485349441503927570568778905918696883174575510385552845625481490900659718413892216221539684717773483326240872061786759868040623935592404144262688161923519030977'), 'n': BigInt('22347337644621997830323797217583448833849627595286505527328214795712874535417149457567295215523199212899872122674023936713124024124676488204889357563104452250187725437815819680799441376434162907889288526863223004380906766451781702435861040049293189979755757428366240570457372226323943522935844086838355728767565415115131238950994049041950699006558441163206523696546297006014416576123345545601004508537089192869558480948139679182328810531942418921113328804749485349441503927570568778905918696883174575510385552845625481490900659718413892216221539684717773483326240872061786759868040623935592404144262688161923519030977'),
'e': 65537 'e': 65537,
}, { }, {
'fingerprint': [128, 80, 214, 72, 77, 244, 98, 7, 201, 250, 37, 244, 227, 51, 96, 199, 182, 37, 224, 113], 'fingerprint': [128, 80, 214, 72, 77, 244, 98, 7, 201, 250, 37, 244, 227, 51, 96, 199, 182, 37, 224, 113],
'n': BigInt('24573455207957565047870011785254215390918912369814947541785386299516827003508659346069416840622922416779652050319196701077275060353178142796963682024347858398319926119639265555410256455471016400261630917813337515247954638555325280392998950756512879748873422896798579889820248358636937659872379948616822902110696986481638776226860777480684653756042166610633513404129518040549077551227082262066602286208338952016035637334787564972991208252928951876463555456715923743181359826124083963758009484867346318483872552977652588089928761806897223231500970500186019991032176060579816348322451864584743414550721639495547636008351'), 'n': BigInt('24573455207957565047870011785254215390918912369814947541785386299516827003508659346069416840622922416779652050319196701077275060353178142796963682024347858398319926119639265555410256455471016400261630917813337515247954638555325280392998950756512879748873422896798579889820248358636937659872379948616822902110696986481638776226860777480684653756042166610633513404129518040549077551227082262066602286208338952016035637334787564972991208252928951876463555456715923743181359826124083963758009484867346318483872552977652588089928761806897223231500970500186019991032176060579816348322451864584743414550721639495547636008351'),
'e': 65537 'e': 65537,
}] }];
const _serverKeys = {} const _serverKeys = {};
PUBLIC_KEYS.forEach(({ fingerprint, ...keyInfo }) => { PUBLIC_KEYS.forEach(({
_serverKeys[readBigIntFromBuffer(fingerprint.slice(-8), true, true)] = keyInfo fingerprint,
}) ...keyInfo
}) => {
_serverKeys[readBigIntFromBuffer(fingerprint.slice(-8), true, true)] = keyInfo;
});
/** /**
* Encrypts the given data known the fingerprint to be used * 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. * @returns {Buffer|*|undefined} the cipher text, or None if no key matching this fingerprint is found.
*/ */
async function encrypt(fingerprint, data) { async function encrypt(fingerprint, data) {
const key = _serverKeys[fingerprint] const key = _serverKeys[fingerprint];
if (!key) { if (!key) {
return undefined return undefined;
} }
// len(sha1.digest) is always 20, so we're left with 255 - 20 - x padding // 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 rsa.encrypt adds 11 bits for padding which we don't want
// rsa module uses rsa.transform.bytes2int(to_encrypt), easier way: // rsa module uses rsa.transform.bytes2int(to_encrypt), easier way:
const payload = readBigIntFromBuffer(toEncrypt, false) const payload = readBigIntFromBuffer(toEncrypt, false);
const encrypted = modExp(payload, BigInt(key.e), key.n) const encrypted = modExp(payload, BigInt(key.e), key.n);
// rsa module uses transform.int2bytes(encrypted, keylength), easier: // rsa module uses transform.int2bytes(encrypted, keylength), easier:
return readBufferFromBigInt(encrypted, 256, false) return readBufferFromBigInt(encrypted, 256, false);
} }
module.exports = { module.exports = {
encrypt, encrypt,
} };

View File

@ -1,24 +1,27 @@
const AES = require('@cryptography/aes').default; const AES = require('@cryptography/aes').default;
const { i2ab, ab2i } = require('./converters'); const {
i2ab,
ab2i,
} = require('./converters');
const { getWords } = require('./words'); const { getWords } = require('./words');
class Counter { class Counter {
constructor(initialValue) { constructor(initialValue) {
this.setBytes(initialValue) this.setBytes(initialValue);
} }
setBytes(bytes) { setBytes(bytes) {
bytes = Buffer.from(bytes) bytes = Buffer.from(bytes);
this._counter = bytes this._counter = bytes;
} }
increment() { increment() {
for (let i = 15; i >= 0; i--) { for (let i = 15; i >= 0; i--) {
if (this._counter[i] === 255) { if (this._counter[i] === 255) {
this._counter[i] = 0 this._counter[i] = 0;
} else { } else {
this._counter[i]++ this._counter[i]++;
break break;
} }
} }
} }
@ -28,34 +31,34 @@ class CTR {
constructor(key, counter) { constructor(key, counter) {
if (!(counter instanceof Counter)) { if (!(counter instanceof Counter)) {
counter = new Counter(counter) counter = new Counter(counter);
} }
this._counter = counter this._counter = counter;
this._remainingCounter = null this._remainingCounter = null;
this._remainingCounterIndex = 16 this._remainingCounterIndex = 16;
this._aes = new AES(getWords(key)) this._aes = new AES(getWords(key));
} }
update(plainText) { update(plainText) {
return this.encrypt(plainText) return this.encrypt(plainText);
} }
encrypt(plainText) { encrypt(plainText) {
const encrypted = Buffer.from(plainText) const encrypted = Buffer.from(plainText);
for (let i = 0; i < encrypted.length; i++) { for (let i = 0; i < encrypted.length; i++) {
if (this._remainingCounterIndex === 16) { if (this._remainingCounterIndex === 16) {
this._remainingCounter = Buffer.from(i2ab(this._aes.encrypt(ab2i(this._counter._counter)))) this._remainingCounter = Buffer.from(i2ab(this._aes.encrypt(ab2i(this._counter._counter))));
this._remainingCounterIndex = 0 this._remainingCounterIndex = 0;
this._counter.increment() 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')) { if (algorithm.includes('ECB')) {
throw new Error('Not supported'); throw new Error('Not supported');
} else { } else {
return new CTR(key, iv) return new CTR(key, iv);
} }
} }
@ -72,47 +75,49 @@ function createCipheriv(algorithm, key, iv) {
if (algorithm.includes('ECB')) { if (algorithm.includes('ECB')) {
throw new Error('Not supported'); throw new Error('Not supported');
} else { } else {
return new CTR(key, iv) return new CTR(key, iv);
} }
} }
function randomBytes(count) { function randomBytes(count) {
const bytes = new Uint8Array(count) const bytes = new Uint8Array(count);
crypto.getRandomValues(bytes) crypto.getRandomValues(bytes);
return bytes return bytes;
} }
class Hash { class Hash {
constructor(algorithm) { constructor(algorithm) {
this.algorithm = algorithm this.algorithm = algorithm;
} }
update(data) { update(data) {
//We shouldn't be needing new Uint8Array but it doesn't //We shouldn't be needing new Uint8Array but it doesn't
//work without it //work without it
this.data = new Uint8Array(data) this.data = new Uint8Array(data);
} }
async digest() { async digest() {
if (this.algorithm === 'sha1') { 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') { } 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) { async function pbkdf2(password, salt, iterations) {
const passwordKey = await crypto.subtle.importKey('raw', password, const passwordKey = await crypto.subtle.importKey('raw', password,
{name: 'PBKDF2'}, false, ['deriveBits']) { name: 'PBKDF2' }, false, ['deriveBits']);
return Buffer.from(await crypto.subtle.deriveBits({ return Buffer.from(await crypto.subtle.deriveBits({
name: 'PBKDF2', name: 'PBKDF2',
hash: 'SHA-512', salt, iterations hash: 'SHA-512',
salt,
iterations,
}, passwordKey, 512)); }, passwordKey, 512));
} }
function createHash(algorithm) { function createHash(algorithm) {
return new Hash(algorithm) return new Hash(algorithm);
} }
module.exports = { module.exports = {
@ -120,5 +125,5 @@ module.exports = {
createDecipheriv, createDecipheriv,
randomBytes, randomBytes,
createHash, createHash,
pbkdf2 pbkdf2,
} };

View File

@ -8,7 +8,7 @@
*/ */
class ReadCancelledError extends Error { class ReadCancelledError extends Error {
constructor() { constructor() {
super('The read operation was cancelled.') super('The read operation was cancelled.');
} }
} }
@ -20,12 +20,12 @@ class TypeNotFoundError extends Error {
constructor(invalidConstructorId, remaining) { constructor(invalidConstructorId, remaining) {
super(`Could not find a matching Constructor ID for the TLObject that was supposed to be 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 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') { if (typeof alert !== 'undefined') {
alert(`Missing MTProto Entity: Please, make sure to add TL definition for ID ${invalidConstructorId}`) alert(`Missing MTProto Entity: Please, make sure to add TL definition for ID ${invalidConstructorId}`);
} }
this.invalidConstructorId = invalidConstructorId this.invalidConstructorId = invalidConstructorId;
this.remaining = remaining this.remaining = remaining;
} }
} }
@ -35,9 +35,9 @@ class TypeNotFoundError extends Error {
*/ */
class InvalidChecksumError extends Error { class InvalidChecksumError extends Error {
constructor(checksum, validChecksum) { constructor(checksum, validChecksum) {
super(`Invalid checksum (${checksum} when ${validChecksum} was expected). This packet should be skipped.`) super(`Invalid checksum (${checksum} when ${validChecksum} was expected). This packet should be skipped.`);
this.checksum = checksum this.checksum = checksum;
this.validChecksum = validChecksum this.validChecksum = validChecksum;
} }
} }
@ -47,15 +47,15 @@ class InvalidChecksumError extends Error {
*/ */
class InvalidBufferError extends Error { class InvalidBufferError extends Error {
constructor(payload) { constructor(payload) {
let code = null let code = null;
if (payload.length === 4) { if (payload.length === 4) {
code = -payload.readInt32LE(0) code = -payload.readInt32LE(0);
super(`Invalid response buffer (HTTP code ${code})`) super(`Invalid response buffer (HTTP code ${code})`);
} else { } else {
super(`Invalid response buffer (too short ${payload})`) super(`Invalid response buffer (too short ${payload})`);
} }
this.code = code this.code = code;
this.payload = payload this.payload = payload;
} }
} }
@ -65,9 +65,9 @@ class InvalidBufferError extends Error {
class SecurityError extends Error { class SecurityError extends Error {
constructor(...args) { constructor(...args) {
if (!args.length) { 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 { class CdnFileTamperedError extends SecurityError {
constructor() { 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).', 'the correct salt, and the message is to be re-sent with it).',
64: 'Invalid container.', 64: 'Invalid container.',
} };
constructor(request,code) { constructor(request, code) {
let errorMessage = BadMessageError.ErrorMessages[code] || let errorMessage = BadMessageError.ErrorMessages[code] ||
`Unknown error code (this should not happen): ${code}.` `Unknown error code (this should not happen): ${code}.`;
errorMessage+= ` Caused by ${request.className}` errorMessage += ` Caused by ${request.className}`;
super(errorMessage) super(errorMessage);
this.message = errorMessage this.message = errorMessage;
this.code = code this.code = code;
} }
} }
@ -145,4 +145,4 @@ module.exports = {
SecurityError, SecurityError,
CdnFileTamperedError, CdnFileTamperedError,
BadMessageError, BadMessageError,
} };

View File

@ -7,18 +7,18 @@ class RPCError extends Error {
'RPCError {0}: {1}{2}' 'RPCError {0}: {1}{2}'
.replace('{0}', code) .replace('{0}', code)
.replace('{1}', message) .replace('{1}', message)
.replace('{2}', RPCError._fmtRequest(request)) .replace('{2}', RPCError._fmtRequest(request)),
) );
this.code = code this.code = code;
this.message = message this.message = message;
} }
static _fmtRequest(request) { static _fmtRequest(request) {
// TODO fix this // TODO fix this
if (request) { if (request) {
return ` (caused by ${request.className})` return ` (caused by ${request.className})`;
} else { } else {
return '' return '';
} }
} }
} }
@ -28,9 +28,9 @@ class RPCError extends Error {
*/ */
class InvalidDCError extends RPCError { class InvalidDCError extends RPCError {
constructor(request, message, code) { constructor(request, message, code) {
super(message, request, code) super(message, request, code);
this.code = code || 303 this.code = code || 303;
this.message = message || 'ERROR_SEE_OTHER' 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. * notified that the data must be corrected before the query is repeated.
*/ */
class BadRequestError extends RPCError { class BadRequestError extends RPCError {
code = 400 code = 400;
message = 'BAD_REQUEST' message = 'BAD_REQUEST';
} }
/** /**
@ -49,8 +49,8 @@ class BadRequestError extends RPCError {
* to authorized users. * to authorized users.
*/ */
class UnauthorizedError extends RPCError { class UnauthorizedError extends RPCError {
code = 401 code = 401;
message = 'UNAUTHORIZED' message = 'UNAUTHORIZED';
} }
/** /**
@ -58,16 +58,16 @@ class UnauthorizedError extends RPCError {
* someone who has blacklisted the current user. * someone who has blacklisted the current user.
*/ */
class ForbiddenError extends RPCError { class ForbiddenError extends RPCError {
code = 403 code = 403;
message = 'FORBIDDEN' message = 'FORBIDDEN';
} }
/** /**
* An attempt to invoke a non-existent object, such as a method. * An attempt to invoke a non-existent object, such as a method.
*/ */
class NotFoundError extends RPCError { class NotFoundError extends RPCError {
code = 404 code = 404;
message = 'NOT_FOUND' message = 'NOT_FOUND';
} }
/** /**
@ -75,8 +75,8 @@ class NotFoundError extends RPCError {
* AUTH_KEY_DUPLICATED which can cause the connection to fail. * AUTH_KEY_DUPLICATED which can cause the connection to fail.
*/ */
class AuthKeyError extends RPCError { class AuthKeyError extends RPCError {
code = 406 code = 406;
message = 'AUTH_KEY' message = 'AUTH_KEY';
} }
/** /**
@ -86,8 +86,8 @@ class AuthKeyError extends RPCError {
* phone number. * phone number.
*/ */
class FloodError extends RPCError { class FloodError extends RPCError {
code = 420 code = 420;
message = 'FLOOD' message = 'FLOOD';
} }
/** /**
@ -96,8 +96,8 @@ class FloodError extends RPCError {
* storage * storage
*/ */
class ServerError extends RPCError { class ServerError extends RPCError {
code = 500 // Also witnessed as -500 code = 500; // Also witnessed as -500
message = 'INTERNAL' message = 'INTERNAL';
} }
/** /**
@ -105,8 +105,8 @@ class ServerError extends RPCError {
* call ``answerCallbackQuery`` will result in this "special" RPCError. * call ``answerCallbackQuery`` will result in this "special" RPCError.
*/ */
class TimedOutError extends RPCError { class TimedOutError extends RPCError {
code = 503 // Only witnessed as -503 code = 503; // Only witnessed as -503
message = 'Timeout' message = 'Timeout';
} }
module.exports = { module.exports = {
@ -120,4 +120,4 @@ module.exports = {
FloodError, FloodError,
ServerError, ServerError,
TimedOutError, TimedOutError,
} };

View File

@ -1,75 +1,81 @@
const { RPCError, InvalidDCError, FloodError, BadRequestError } = require('./RPCBaseErrors') const {
RPCError,
InvalidDCError,
FloodError,
BadRequestError,
} = require('./RPCBaseErrors');
class UserMigrateError extends InvalidDCError { class UserMigrateError extends InvalidDCError {
constructor(args) { constructor(args) {
const newDc = Number(args.capture || 0) 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)) 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.message = `The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request);
this.newDc = newDc this.newDc = newDc;
} }
} }
class PhoneMigrateError extends InvalidDCError { class PhoneMigrateError extends InvalidDCError {
constructor(args) { constructor(args) {
const newDc = Number(args.capture || 0) 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)) 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.message = `The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request);
this.newDc = newDc this.newDc = newDc;
} }
} }
class SlowModeWaitError extends FloodError { class SlowModeWaitError extends FloodError {
constructor(args) { constructor(args) {
const seconds = Number(args.capture || 0) 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)) 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.message = `A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request);
this.seconds = seconds this.seconds = seconds;
} }
} }
class FloodWaitError extends FloodError { class FloodWaitError extends FloodError {
constructor(args) { constructor(args) {
const seconds = Number(args.capture || 0) const seconds = Number(args.capture || 0);
super(`A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request)) 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.message = `A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request);
this.seconds = seconds this.seconds = seconds;
} }
} }
class FloodTestPhoneWaitError extends FloodError { class FloodTestPhoneWaitError extends FloodError {
constructor(args) { constructor(args) {
const seconds = Number(args.capture || 0) const seconds = Number(args.capture || 0);
super(`A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request)) 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.message = `A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request);
this.seconds = seconds this.seconds = seconds;
} }
} }
class FileMigrateError extends InvalidDCError { class FileMigrateError extends InvalidDCError {
constructor(args) { constructor(args) {
const newDc = Number(args.capture || 0) const newDc = Number(args.capture || 0);
super(`The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request)) 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.message = `The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request);
this.newDc = newDc this.newDc = newDc;
} }
} }
class NetworkMigrateError extends InvalidDCError { class NetworkMigrateError extends InvalidDCError {
constructor(args) { constructor(args) {
const newDc = Number(args.capture || 0) const newDc = Number(args.capture || 0);
super(`The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)) 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.message = `The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request);
this.newDc = newDc this.newDc = newDc;
} }
} }
class EmailUnconfirmedError extends BadRequestError { class EmailUnconfirmedError extends BadRequestError {
constructor(args) { constructor(args) {
const codeLength = Number(args.capture || 0) const codeLength = Number(args.capture || 0);
super(`Email unconfirmed, the length of the code must be ${codeLength}` + RPCError._fmtRequest(args.request)) 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.message = `Email unconfirmed, the length of the code must be ${codeLength}` + RPCError._fmtRequest(args.request);
this.codeLength = codeLength this.codeLength = codeLength;
} }
} }
@ -83,7 +89,7 @@ const rpcErrorRe = [
[/NETWORK_MIGRATE_(\d+)/, NetworkMigrateError], [/NETWORK_MIGRATE_(\d+)/, NetworkMigrateError],
[/EMAIL_UNCONFIRMED_(\d+)/, EmailUnconfirmedError], [/EMAIL_UNCONFIRMED_(\d+)/, EmailUnconfirmedError],
] ];
module.exports = { module.exports = {
rpcErrorRe, rpcErrorRe,
FileMigrateError, FileMigrateError,
@ -93,5 +99,5 @@ module.exports = {
SlowModeWaitError, SlowModeWaitError,
UserMigrateError, UserMigrateError,
NetworkMigrateError, NetworkMigrateError,
EmailUnconfirmedError EmailUnconfirmedError,
} };

View File

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

View File

@ -1,21 +1,21 @@
const { EventBuilder } = require('./common') const { EventBuilder } = require('./common');
class Raw extends EventBuilder { class Raw extends EventBuilder {
constructor(args = { constructor(args = {
types: null, types: null,
func: null, func: null,
}) { }) {
super() super();
if (!args.types) { if (!args.types) {
this.types = true this.types = true;
} else { } else {
this.types = args.types this.types = args.types;
} }
} }
build(update, others = null) { build(update, others = null) {
return update return update;
} }
} }
module.exports = Raw module.exports = Raw;

View File

@ -1,12 +1,14 @@
class EventBuilder { class EventBuilder {
constructor(args = { constructor(args = {
chats: null, blacklistChats: null, func: null, chats: null,
}, blacklistChats: null,
func: null,
},
) { ) {
this.chats = args.chats this.chats = args.chats;
this.blacklistChats = Boolean(args.blacklistChats) this.blacklistChats = Boolean(args.blacklistChats);
this.resolved = false this.resolved = false;
this.func = args.func this.func = args.func;
} }
build(update, others = null) { build(update, others = null) {
@ -19,4 +21,7 @@ class EventCommon {
} }
module.exports = { EventBuilder, EventCommon } module.exports = {
EventBuilder,
EventCommon,
};

View File

@ -1,8 +1,12 @@
const NewMessage = require('./NewMessage') const NewMessage = require('./NewMessage');
const Raw = require('./Raw') const Raw = require('./Raw');
class StopPropagation extends Error { class StopPropagation extends Error {
} }
module.exports = { NewMessage, StopPropagation, Raw } module.exports = {
NewMessage,
StopPropagation,
Raw,
};

View File

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

View File

@ -1,7 +1,7 @@
const { TypeNotFoundError } = require('../errors/Common') const { TypeNotFoundError } = require('../errors/Common');
const { coreObjects } = require('../tl/core') const { coreObjects } = require('../tl/core');
const { tlobjects } = require('../tl/AllTLObjects') const { tlobjects } = require('../tl/AllTLObjects');
const { readBigIntFromBuffer } = require('../Helpers') const { readBigIntFromBuffer } = require('../Helpers');
class BinaryReader { class BinaryReader {
/** /**
@ -9,9 +9,9 @@ class BinaryReader {
* @param data {Buffer} * @param data {Buffer}
*/ */
constructor(data) { constructor(data) {
this.stream = data this.stream = data;
this._last = null this._last = null;
this.offset = 0 this.offset = 0;
} }
// region Reading // region Reading
@ -22,7 +22,7 @@ class BinaryReader {
* Reads a single byte value. * Reads a single byte value.
*/ */
readByte() { readByte() {
return this.read(1)[0] return this.read(1)[0];
} }
/** /**
@ -30,14 +30,14 @@ class BinaryReader {
* @param signed {Boolean} * @param signed {Boolean}
*/ */
readInt(signed = true) { readInt(signed = true) {
let res let res;
if (signed) { if (signed) {
res = this.stream.readInt32LE(this.offset) res = this.stream.readInt32LE(this.offset);
} else { } else {
res = this.stream.readUInt32LE(this.offset) res = this.stream.readUInt32LE(this.offset);
} }
this.offset += 4 this.offset += 4;
return res return res;
} }
/** /**
@ -46,7 +46,7 @@ class BinaryReader {
* @returns {BigInteger} * @returns {BigInteger}
*/ */
readLong(signed = true) { readLong(signed = true) {
return this.readLargeInt(64, signed) return this.readLargeInt(64, signed);
} }
/** /**
@ -54,7 +54,8 @@ class BinaryReader {
* @returns {number} * @returns {number}
*/ */
readFloat() { readFloat() {
return this.read(4).readFloatLE(0) return this.read(4)
.readFloatLE(0);
} }
/** /**
@ -63,7 +64,8 @@ class BinaryReader {
*/ */
readDouble() { readDouble() {
// was this a bug ? it should have been <d // was this a bug ? it should have been <d
return this.read(8).readDoubleLE(0) return this.read(8)
.readDoubleLE(0);
} }
/** /**
@ -72,8 +74,8 @@ class BinaryReader {
* @param signed {Boolean} * @param signed {Boolean}
*/ */
readLargeInt(bits, signed = true) { readLargeInt(bits, signed = true) {
const buffer = this.read(Math.floor(bits / 8)) const buffer = this.read(Math.floor(bits / 8));
return readBigIntFromBuffer(buffer, true, signed) return readBigIntFromBuffer(buffer, true, signed);
} }
/** /**
@ -82,17 +84,17 @@ class BinaryReader {
*/ */
read(length = -1) { read(length = -1) {
if (length === -1) { if (length === -1) {
length = this.stream.length - this.offset length = this.stream.length - this.offset;
} }
const result = this.stream.slice(this.offset, this.offset + length) const result = this.stream.slice(this.offset, this.offset + length);
this.offset += length this.offset += length;
if (result.length !== length) { if (result.length !== length) {
throw Error( throw Error(
`No more data left to read (need ${length}, got ${result.length}: ${result}); last read ${this._last}`, `No more data left to read (need ${length}, got ${result.length}: ${result}); last read ${this._last}`,
) );
} }
this._last = result this._last = result;
return result return result;
} }
/** /**
@ -100,7 +102,7 @@ class BinaryReader {
* @returns {Buffer} * @returns {Buffer}
*/ */
getBuffer() { getBuffer() {
return this.stream return this.stream;
} }
// endregion // endregion
@ -112,24 +114,24 @@ class BinaryReader {
* @returns {Buffer} * @returns {Buffer}
*/ */
tgReadBytes() { tgReadBytes() {
const firstByte = this.readByte() const firstByte = this.readByte();
let padding let padding;
let length let length;
if (firstByte === 254) { if (firstByte === 254) {
length = this.readByte() | (this.readByte() << 8) | (this.readByte() << 16) length = this.readByte() | (this.readByte() << 8) | (this.readByte() << 16);
padding = length % 4 padding = length % 4;
} else { } else {
length = firstByte length = firstByte;
padding = (length + 1) % 4 padding = (length + 1) % 4;
} }
const data = this.read(length) const data = this.read(length);
if (padding > 0) { if (padding > 0) {
padding = 4 - padding padding = 4 - padding;
this.read(padding) this.read(padding);
} }
return data return data;
} }
/** /**
@ -137,7 +139,8 @@ class BinaryReader {
* @returns {string} * @returns {string}
*/ */
tgReadString() { tgReadString() {
return this.tgReadBytes().toString('utf-8') return this.tgReadBytes()
.toString('utf-8');
} }
/** /**
@ -145,15 +148,15 @@ class BinaryReader {
* @returns {boolean} * @returns {boolean}
*/ */
tgReadBool() { tgReadBool() {
const value = this.readInt(false) const value = this.readInt(false);
if (value === 0x997275b5) { if (value === 0x997275b5) {
// boolTrue // boolTrue
return true return true;
} else if (value === 0xbc799737) { } else if (value === 0xbc799737) {
// boolFalse // boolFalse
return false return false;
} else { } 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} * @returns {Date}
*/ */
tgReadDate() { tgReadDate() {
const value = this.readInt() const value = this.readInt();
return new Date(value * 1000) return new Date(value * 1000);
} }
/** /**
* Reads a Telegram object. * Reads a Telegram object.
*/ */
tgReadObject() { tgReadObject() {
const constructorId = this.readInt(false) const constructorId = this.readInt(false);
let clazz = tlobjects[constructorId] let clazz = tlobjects[constructorId];
if (clazz === undefined) { if (clazz === undefined) {
/** /**
* The class was None, but there's still a * The class was None, but there's still a
* chance of it being a manually parsed value like bool! * chance of it being a manually parsed value like bool!
*/ */
const value = constructorId const value = constructorId;
if (value === 0x997275b5) { if (value === 0x997275b5) {
// boolTrue // boolTrue
return true return true;
} else if (value === 0xbc799737) { } else if (value === 0xbc799737) {
// boolFalse // boolFalse
return false return false;
} else if (value === 0x1cb5c415) { } else if (value === 0x1cb5c415) {
// Vector // Vector
const temp = [] const temp = [];
const length = this.readInt() const length = this.readInt();
for (let i = 0; i < length; i++) { 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 (clazz === undefined) {
// If there was still no luck, give up // If there was still no luck, give up
this.seek(-4) // Go back this.seek(-4); // Go back
const pos = this.tellPosition() const pos = this.tellPosition();
const error = new TypeNotFoundError(constructorId, this.read()) const error = new TypeNotFoundError(constructorId, this.read());
this.setPosition(pos) this.setPosition(pos);
throw error throw error;
} }
} }
return clazz.fromReader(this) return clazz.fromReader(this);
} }
/** /**
@ -215,14 +218,14 @@ class BinaryReader {
*/ */
tgReadVector() { tgReadVector() {
if (this.readInt(false) !== 0x1cb5c415) { 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 count = this.readInt();
const temp = [] const temp = [];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
temp.push(this.tgReadObject()) temp.push(this.tgReadObject());
} }
return temp return temp;
} }
// endregion // endregion
@ -231,7 +234,7 @@ class BinaryReader {
* Closes the reader. * Closes the reader.
*/ */
close() { close() {
this.stream = null this.stream = null;
} }
// region Position related // region Position related
@ -241,7 +244,7 @@ class BinaryReader {
* @returns {number} * @returns {number}
*/ */
tellPosition() { tellPosition() {
return this.offset return this.offset;
} }
/** /**
@ -249,7 +252,7 @@ class BinaryReader {
* @param position * @param position
*/ */
setPosition(position) { setPosition(position) {
this.offset = position this.offset = position;
} }
/** /**
@ -258,10 +261,10 @@ class BinaryReader {
* @param offset * @param offset
*/ */
seek(offset) { seek(offset) {
this.offset += offset this.offset += offset;
} }
// endregion // endregion
} }
module.exports = BinaryReader module.exports = BinaryReader;

View File

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

View File

@ -1,17 +1,17 @@
let _level = null let _level = null;
class Logger { class Logger {
static levels = ['error', 'warn', 'info', 'debug'] static levels = ['error', 'warn', 'info', 'debug'];
constructor(level) { constructor(level) {
if (!_level) { if (!_level) {
_level = level || 'debug' _level = level || 'debug';
} }
this.isBrowser = typeof process === 'undefined' || this.isBrowser = typeof process === 'undefined' ||
process.type === 'renderer' || process.type === 'renderer' ||
process.browser === true || process.browser === true ||
process.__nwjs process.__nwjs;
if (!this.isBrowser) { if (!this.isBrowser) {
this.colors = { this.colors = {
start: '\x1b[2m', start: '\x1b[2m',
@ -20,7 +20,7 @@ class Logger {
debug: '\x1b[36m', debug: '\x1b[36m',
error: '\x1b[31m', error: '\x1b[31m',
end: '\x1b[0m', end: '\x1b[0m',
} };
} else { } else {
this.colors = { this.colors = {
start: '%c', start: '%c',
@ -29,9 +29,13 @@ class Logger {
debug: 'color : #00ffff', debug: 'color : #00ffff',
error: 'color : #ff0000', error: 'color : #ff0000',
end: '', end: '',
} };
} }
this.messageFormat = '[%t] [%l] - [%m]' this.messageFormat = '[%t] [%l] - [%m]';
}
static setLevel(level) {
_level = level;
} }
/** /**
@ -40,45 +44,41 @@ class Logger {
* @returns {boolean} * @returns {boolean}
*/ */
canSend(level) { canSend(level) {
return (Logger.levels.indexOf(_level) >= Logger.levels.indexOf(level)) return (Logger.levels.indexOf(_level) >= Logger.levels.indexOf(level));
} }
/** /**
* @param message {string} * @param message {string}
*/ */
warn(message) { warn(message) {
this._log('warn', message, this.colors.warn) this._log('warn', message, this.colors.warn);
} }
/** /**
* @param message {string} * @param message {string}
*/ */
info(message) { info(message) {
this._log('info', message, this.colors.info) this._log('info', message, this.colors.info);
} }
/** /**
* @param message {string} * @param message {string}
*/ */
debug(message) { debug(message) {
this._log('debug', message, this.colors.debug) this._log('debug', message, this.colors.debug);
} }
/** /**
* @param message {string} * @param message {string}
*/ */
error(message) { error(message) {
this._log('error', message, this.colors.error) this._log('error', message, this.colors.error);
} }
format(message, level) { format(message, level) {
return this.messageFormat.replace('%t', new Date().toISOString()) return this.messageFormat.replace('%t', new Date().toISOString())
.replace('%l', level.toUpperCase()) .replace('%l', level.toUpperCase())
.replace('%m', message) .replace('%m', message);
}
static setLevel(level) {
_level = level;
} }
/** /**
@ -87,14 +87,14 @@ class Logger {
* @param color {string} * @param color {string}
*/ */
_log(level, message, color) { _log(level, message, color) {
if (!_level){ if (!_level) {
return return;
} }
if (this.canSend(level)) { if (this.canSend(level)) {
if (!this.isBrowser) { if (!this.isBrowser) {
console.log(color + this.format(message, level) + this.colors.end) console.log(color + this.format(message, level) + this.colors.end);
} else { } else {
console.log(this.colors.start + this.format(message, level), color) console.log(this.colors.start + this.format(message, level), color);
} }
} else { } else {
@ -102,4 +102,4 @@ class Logger {
} }
} }
module.exports = Logger module.exports = Logger;

View File

@ -1,112 +1,116 @@
const MessageContainer = require('../tl/core/MessageContainer') const MessageContainer = require('../tl/core/MessageContainer');
const TLMessage = require('../tl/core/TLMessage') const TLMessage = require('../tl/core/TLMessage');
const BinaryWriter = require('../extensions/BinaryWriter') const BinaryWriter = require('../extensions/BinaryWriter');
class MessagePacker { class MessagePacker {
constructor(state, logger) { constructor(state, logger) {
this._state = state this._state = state;
this._queue = [] this._queue = [];
this._pendingStates = [] this._pendingStates = [];
this._ready = new Promise(((resolve) => { this._ready = new Promise(((resolve) => {
this.setReady = resolve this.setReady = resolve;
})) }));
this._log = logger this._log = logger;
} }
values() { values() {
return this._queue return this._queue;
} }
append(state) { append(state) {
this._queue.push(state) this._queue.push(state);
this.setReady(true) this.setReady(true);
if (state) { if (state) {
this._pendingStates.push(state) this._pendingStates.push(state);
state.promise state.promise
// Using finally causes triggering `unhandledrejection` event // Using finally causes triggering `unhandledrejection` event
.catch(() => {}) .catch(() => {
.finally(() => {
this._pendingStates = this._pendingStates.filter((s) => s !== state)
}) })
.finally(() => {
this._pendingStates = this._pendingStates.filter((s) => s !== state);
});
} }
} }
extend(states) { extend(states) {
for (const state of states) { for (const state of states) {
this._queue.push(state) this._queue.push(state);
} }
this.setReady(true) this.setReady(true);
} }
async get() { async get() {
if (!this._queue.length) { if (!this._queue.length) {
this._ready = new Promise(((resolve) => { this._ready = new Promise(((resolve) => {
this.setReady = resolve this.setReady = resolve;
})) }));
await this._ready await this._ready;
} }
if (!this._queue[this._queue.length - 1]) { if (!this._queue[this._queue.length - 1]) {
this._queue = [] this._queue = [];
return return;
} }
let data let data;
let buffer = new BinaryWriter(Buffer.alloc(0)) let buffer = new BinaryWriter(Buffer.alloc(0));
const batch = [] const batch = [];
let size = 0 let size = 0;
while (this._queue.length && batch.length <= MessageContainer.MAXIMUM_LENGTH) { while (this._queue.length && batch.length <= MessageContainer.MAXIMUM_LENGTH) {
const state = this._queue.shift() const state = this._queue.shift();
size += state.data.length + TLMessage.SIZE_OVERHEAD size += state.data.length + TLMessage.SIZE_OVERHEAD;
if (size <= MessageContainer.MAXIMUM_SIZE) { if (size <= MessageContainer.MAXIMUM_SIZE) {
let afterId let afterId;
if (state.after) { if (state.after) {
afterId = state.after.msgId afterId = state.after.msgId;
} }
state.msgId = await this._state.writeDataAsMessage( state.msgId = await this._state.writeDataAsMessage(
buffer, state.data, state.request.classType === 'request', buffer, state.data, state.request.classType === 'request',
afterId, afterId,
) );
this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.className || state.request.constructor.name}`) this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.className || state.request.constructor.name}`);
batch.push(state) batch.push(state);
continue continue;
} }
if (batch.length) { if (batch.length) {
this._queue.unshift(state) this._queue.unshift(state);
break break;
} }
this._log.warn(`Message payload for ${state.request.className || state.request.constructor.name} is too long ${state.data.length} and cannot be sent`) this._log.warn(`Message payload for ${state.request.className || state.request.constructor.name} is too long ${state.data.length} and cannot be sent`);
state.reject('Request Payload is too big') state.reject('Request Payload is too big');
size = 0 size = 0;
continue
} }
if (!batch.length) { if (!batch.length) {
return null return null;
} }
if (batch.length > 1) { if (batch.length > 1) {
const b = Buffer.alloc(8) const b = Buffer.alloc(8);
b.writeUInt32LE(MessageContainer.CONSTRUCTOR_ID, 0) b.writeUInt32LE(MessageContainer.CONSTRUCTOR_ID, 0);
b.writeInt32LE(batch.length, 4) b.writeInt32LE(batch.length, 4);
data = Buffer.concat([b, buffer.getValue()]) data = Buffer.concat([b, buffer.getValue()]);
buffer = new BinaryWriter(Buffer.alloc(0)) buffer = new BinaryWriter(Buffer.alloc(0));
const containerId = await this._state.writeDataAsMessage( const containerId = await this._state.writeDataAsMessage(
buffer, data, false, buffer, data, false,
) );
for (const s of batch) { for (const s of batch) {
s.containerId = containerId s.containerId = containerId;
} }
} }
data = buffer.getValue() data = buffer.getValue();
return { batch, data } return {
batch,
data,
};
} }
rejectAll() { rejectAll() {
this._pendingStates.forEach((requestState) => { this._pendingStates.forEach((requestState) => {
requestState.reject(new Error('Disconnect')) requestState.reject(new Error('Disconnect'));
}) });
} }
} }
module.exports = MessagePacker module.exports = MessagePacker;

View File

@ -1,9 +1,9 @@
const Mutex = require('async-mutex').Mutex const Mutex = require('async-mutex').Mutex;
const mutex = new 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 { class PromisedWebSockets {
constructor() { constructor() {
@ -14,117 +14,117 @@ class PromisedWebSockets {
process.__nwjs process.__nwjs
*/ */
this.client = null this.client = null;
this.closed = true this.closed = true;
} }
async readExactly(number) { async readExactly(number) {
let readData = Buffer.alloc(0) let readData = Buffer.alloc(0);
while (true) { while (true) {
const thisTime = await this.read(number) const thisTime = await this.read(number);
readData = Buffer.concat([readData, thisTime]) readData = Buffer.concat([readData, thisTime]);
number = number - thisTime.length number = number - thisTime.length;
if (!number) { if (!number) {
return readData return readData;
} }
} }
} }
async read(number) { async read(number) {
if (this.closed) { if (this.closed) {
throw closeError throw closeError;
} }
await this.canRead await this.canRead;
if (this.closed) { if (this.closed) {
throw closeError throw closeError;
} }
const toReturn = this.stream.slice(0, number) const toReturn = this.stream.slice(0, number);
this.stream = this.stream.slice(number) this.stream = this.stream.slice(number);
if (this.stream.length === 0) { if (this.stream.length === 0) {
this.canRead = new Promise((resolve) => { this.canRead = new Promise((resolve) => {
this.resolveRead = resolve this.resolveRead = resolve;
}) });
} }
return toReturn return toReturn;
} }
async readAll() { async readAll() {
if (this.closed || !await this.canRead) { if (this.closed || !await this.canRead) {
throw closeError throw closeError;
} }
const toReturn = this.stream const toReturn = this.stream;
this.stream = Buffer.alloc(0) this.stream = Buffer.alloc(0);
this.canRead = new Promise((resolve) => { this.canRead = new Promise((resolve) => {
this.resolveRead = resolve this.resolveRead = resolve;
}) });
return toReturn return toReturn;
} }
getWebSocketLink(ip, port) { getWebSocketLink(ip, port) {
if (port === 443) { if (port === 443) {
return `wss://${ip}:${port}/apiws` return `wss://${ip}:${port}/apiws`;
} else { } else {
return `ws://${ip}:${port}/apiws` return `ws://${ip}:${port}/apiws`;
} }
} }
async connect(port, ip) { async connect(port, ip) {
this.stream = Buffer.alloc(0) this.stream = Buffer.alloc(0);
this.canRead = new Promise((resolve) => { this.canRead = new Promise((resolve) => {
this.resolveRead = resolve this.resolveRead = resolve;
}) });
this.closed = false this.closed = false;
this.website = this.getWebSocketLink(ip, port) this.website = this.getWebSocketLink(ip, port);
this.client = new WebSocketClient(this.website, 'binary') this.client = new WebSocketClient(this.website, 'binary');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.client.onopen = () => { this.client.onopen = () => {
this.receive() this.receive();
resolve(this) resolve(this);
} };
this.client.onerror = (error) => { this.client.onerror = (error) => {
reject(error) reject(error);
} };
this.client.onclose = () => { this.client.onclose = () => {
this.resolveRead(false) this.resolveRead(false);
this.closed = true this.closed = true;
} };
//CONTEST //CONTEST
// Seems to not be working, at least in a web worker // Seems to not be working, at least in a web worker
self.addEventListener('offline', async () => { self.addEventListener('offline', async () => {
await this.close() await this.close();
this.resolveRead(false) this.resolveRead(false);
}) });
}) });
} }
write(data) { write(data) {
if (this.closed) { if (this.closed) {
throw closeError throw closeError;
} }
this.client.send(data) this.client.send(data);
} }
async close() { async close() {
await this.client.close() await this.client.close();
this.closed = true this.closed = true;
} }
async receive() { async receive() {
this.client.onmessage = async (message) => { this.client.onmessage = async (message) => {
const release = await mutex.acquire() const release = await mutex.acquire();
try { try {
const data = message.data instanceof ArrayBuffer const data = message.data instanceof ArrayBuffer
? Buffer.from(message.data) ? Buffer.from(message.data)
: Buffer.from(await new Response(message.data).arrayBuffer()) : Buffer.from(await new Response(message.data).arrayBuffer());
this.stream = Buffer.concat([this.stream, data]) this.stream = Buffer.concat([this.stream, data]);
this.resolveRead(true) this.resolveRead(true);
} finally { } finally {
release() release();
} }
} };
} }
} }
module.exports = PromisedWebSockets module.exports = PromisedWebSockets;

View File

@ -1,9 +1,9 @@
const Logger = require('./Logger') const Logger = require('./Logger');
const BinaryWriter = require('./BinaryWriter') const BinaryWriter = require('./BinaryWriter');
const BinaryReader = require('./BinaryReader') const BinaryReader = require('./BinaryReader');
const PromisedWebSockets = require('./PromisedWebSockets') const PromisedWebSockets = require('./PromisedWebSockets');
const MessagePacker = require('./MessagePacker') const MessagePacker = require('./MessagePacker');
const AsyncQueue = require('./AsyncQueue') const AsyncQueue = require('./AsyncQueue');
module.exports = { module.exports = {
BinaryWriter, BinaryWriter,
BinaryReader, BinaryReader,
@ -11,4 +11,4 @@ module.exports = {
AsyncQueue, AsyncQueue,
Logger, Logger,
PromisedWebSockets, PromisedWebSockets,
} };

View File

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

View File

@ -1,12 +1,15 @@
const BigInt = require('big-integer') const BigInt = require('big-integer');
const IGE = require('../crypto/IGE') const IGE = require('../crypto/IGE');
const AuthKey = require('../crypto/AuthKey') const AuthKey = require('../crypto/AuthKey');
const Factorizator = require('../crypto/Factorizator') const Factorizator = require('../crypto/Factorizator');
const RSA = require('../crypto/RSA') const RSA = require('../crypto/RSA');
const Helpers = require('../Helpers') const Helpers = require('../Helpers');
const { constructors, requests } = require('../tl') const {
const BinaryReader = require('../extensions/BinaryReader') constructors,
const { SecurityError } = require("../errors/Common") requests,
} = require('../tl');
const BinaryReader = require('../extensions/BinaryReader');
const { SecurityError } = require('../errors/Common');
/** /**
* Executes the authentication process with the Telegram servers. * Executes the authentication process with the Telegram servers.
@ -16,31 +19,34 @@ const { SecurityError } = require("../errors/Common")
*/ */
async function doAuthentication(sender, log) { async function doAuthentication(sender, log) {
// Step 1 sending: PQ Request, endianness doesn't matter since it's random // 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 })) const resPQ = await sender.send(new requests.ReqPqMulti({ nonce: nonce }));
log.debug('Starting authKey generation step 1') log.debug('Starting authKey generation step 1');
if (!(resPQ instanceof constructors.ResPQ)) { 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)) { 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) const pq = Helpers.readBigIntFromBuffer(resPQ.pq, false, true);
log.debug('Finished authKey generation step 1') log.debug('Finished authKey generation step 1');
log.debug('Starting authKey generation step 2') log.debug('Starting authKey generation step 2');
// Step 2 sending: DH Exchange // Step 2 sending: DH Exchange
let { p, q } = Factorizator.factorize(pq) let {
p,
q,
} = Factorizator.factorize(pq);
// TODO Bring back after `Factorizator` fix. // TODO Bring back after `Factorizator` fix.
p = Helpers.getByteArray(p) p = Helpers.getByteArray(p);
q = Helpers.getByteArray(q) q = Helpers.getByteArray(q);
bytes = Helpers.generateRandomBytes(32) bytes = Helpers.generateRandomBytes(32);
const newNonce = Helpers.readBigIntFromBuffer(bytes, true, true) const newNonce = Helpers.readBigIntFromBuffer(bytes, true, true);
const pqInnerData = new constructors.PQInnerData({ const pqInnerData = new constructors.PQInnerData({
pq: Helpers.getByteArray(pq), // unsigned pq: Helpers.getByteArray(pq), // unsigned
@ -49,20 +55,20 @@ async function doAuthentication(sender, log) {
nonce: resPQ.nonce, nonce: resPQ.nonce,
serverNonce: resPQ.serverNonce, serverNonce: resPQ.serverNonce,
newNonce: newNonce, newNonce: newNonce,
}) });
// sha_digest + data + random_bytes // sha_digest + data + random_bytes
let cipherText = null let cipherText = null;
let targetFingerprint = null let targetFingerprint = null;
for (const fingerprint of resPQ.serverPublicKeyFingerprints) { 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) { if (cipherText !== null && cipherText !== undefined) {
targetFingerprint = fingerprint targetFingerprint = fingerprint;
break break;
} }
} }
if (cipherText === null || cipherText === undefined) { 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( const serverDhParams = await sender.send(
@ -74,58 +80,62 @@ async function doAuthentication(sender, log) {
publicKeyFingerprint: targetFingerprint, publicKeyFingerprint: targetFingerprint,
encryptedData: cipherText, encryptedData: cipherText,
}), }),
) );
if (!(serverDhParams instanceof constructors.ServerDHParamsOk || serverDhParams instanceof constructors.ServerDHParamsFail)) { 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)) { 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)) { 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) { if (serverDhParams instanceof constructors.ServerDHParamsFail) {
const sh = await Helpers.sha1(Helpers.toSignedLittleBuffer(newNonce, 32).slice(4, 20)) const sh = await Helpers.sha1(Helpers.toSignedLittleBuffer(newNonce, 32)
const nnh = Helpers.readBigIntFromBuffer(sh, true, true) .slice(4, 20));
const nnh = Helpers.readBigIntFromBuffer(sh, true, true);
if (serverDhParams.newNonceHash.neq(nnh)) { 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)) { 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('Finished authKey generation step 2');
log.debug('Starting authKey generation step 3') log.debug('Starting authKey generation step 3');
// Step 3 sending: Complete DH Exchange // 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) { if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
// See PR#453 // 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 ige = new IGE(key, iv);
const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer) const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer);
const reader = new BinaryReader(plainTextAnswer) const reader = new BinaryReader(plainTextAnswer);
reader.read(20) // hash sum reader.read(20); // hash sum
const serverDhInner = reader.tgReadObject() const serverDhInner = reader.tgReadObject();
if (!(serverDhInner instanceof constructors.ServerDHInnerData)) { 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)) { 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)) { 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 dhPrime = Helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false);
const ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false) const ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false);
const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000) const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000);
const b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false) const b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false);
const gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime) const gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime);
const gab = Helpers.modExp(ga, b, dhPrime) const gab = Helpers.modExp(ga, b, dhPrime);
// Prepare client DH Inner Data // Prepare client DH Inner Data
const clientDhInner = new constructors.ClientDHInnerData({ const clientDhInner = new constructors.ClientDHInnerData({
@ -133,49 +143,52 @@ async function doAuthentication(sender, log) {
serverNonce: resPQ.serverNonce, serverNonce: resPQ.serverNonce,
retryId: 0, // TODO Actual retry ID retryId: 0, // TODO Actual retry ID
gB: Helpers.getByteArray(gb, false), 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 // Encryption
const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed) const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed);
const dhGen = await sender.send( const dhGen = await sender.send(
new requests.SetClientDHParams({ new requests.SetClientDHParams({
nonce: resPQ.nonce, nonce: resPQ.nonce,
serverNonce: resPQ.serverNonce, serverNonce: resPQ.serverNonce,
encryptedData: clientDhEncrypted, 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])) { 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)) { 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)) { 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() const authKey = new AuthKey();
await authKey.setKey(Helpers.getByteArray(gab)) 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 newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber);
const dhHash = dhGen[`newNonceHash${nonceNumber}`] const dhHash = dhGen[`newNonceHash${nonceNumber}`];
if (dhHash.neq(newNonceHash)) { 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)) { 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;

View File

@ -2,12 +2,12 @@
* This module contains the class used to communicate with Telegram's servers * This module contains the class used to communicate with Telegram's servers
* in plain text, when no authorization key has been created yet. * in plain text, when no authorization key has been created yet.
*/ */
const Helpers = require('../Helpers') const Helpers = require('../Helpers');
const MTProtoState = require('./MTProtoState') const MTProtoState = require('./MTProtoState');
const BinaryReader = require('../extensions/BinaryReader') const BinaryReader = require('../extensions/BinaryReader');
const { InvalidBufferError } = require('../errors/Common') const { InvalidBufferError } = require('../errors/Common');
const BigInt = require('big-integer') const BigInt = require('big-integer');
const { toSignedLittleBuffer } = require("../Helpers") const { toSignedLittleBuffer } = require('../Helpers');
/** /**
* MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages) * MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)
@ -20,8 +20,8 @@ class MTProtoPlainSender {
* @param loggers * @param loggers
*/ */
constructor(connection, loggers) { constructor(connection, loggers) {
this._state = new MTProtoState(connection, loggers) this._state = new MTProtoState(connection, loggers);
this._connection = connection this._connection = connection;
} }
/** /**
@ -30,27 +30,27 @@ class MTProtoPlainSender {
*/ */
async send(request) { async send(request) {
let body = request.getBytes() let body = request.getBytes();
let msgId = this._state._getNewMsgId() let msgId = this._state._getNewMsgId();
const m = toSignedLittleBuffer(msgId, 8) const m = toSignedLittleBuffer(msgId, 8);
const b = Buffer.alloc(4) const b = Buffer.alloc(4);
b.writeInt32LE(body.length, 0) 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) await this._connection.send(res);
body = await this._connection.recv() body = await this._connection.recv();
if (body.length < 8) { if (body.length < 8) {
throw new InvalidBufferError(body) throw new InvalidBufferError(body);
} }
const reader = new BinaryReader(body) const reader = new BinaryReader(body);
const authKeyId = reader.readLong() const authKeyId = reader.readLong();
if (authKeyId.neq(BigInt(0))) { 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))) { 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 /** ^ We should make sure that the read ``msg_id`` is greater
* than our own ``msg_id``. However, under some circumstances * than our own ``msg_id``. However, under some circumstances
@ -58,18 +58,18 @@ class MTProtoPlainSender {
* be the case, which would cause endless assertion errors. * be the case, which would cause endless assertion errors.
*/ */
const length = reader.readInt() const length = reader.readInt();
if (length <= 0) { 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 * We could read length bytes and use those in a new reader to read
* the next TLObject without including the padding, but since the * the next TLObject without including the padding, but since the
* reader isn't used for anything else after this, it's unnecessary. * reader isn't used for anything else after this, it's unnecessary.
*/ */
return reader.tgReadObject() return reader.tgReadObject();
} }
} }
module.exports = MTProtoPlainSender module.exports = MTProtoPlainSender;

View File

@ -1,17 +1,22 @@
const MtProtoPlainSender = require('./MTProtoPlainSender') const MtProtoPlainSender = require('./MTProtoPlainSender');
const MTProtoState = require('./MTProtoState') const MTProtoState = require('./MTProtoState');
const Helpers = require('../Helpers') const Helpers = require('../Helpers');
const AuthKey = require('../crypto/AuthKey') const AuthKey = require('../crypto/AuthKey');
const doAuthentication = require('./Authenticator') const doAuthentication = require('./Authenticator');
const RPCResult = require('../tl/core/RPCResult') const RPCResult = require('../tl/core/RPCResult');
const MessageContainer = require('../tl/core/MessageContainer') const MessageContainer = require('../tl/core/MessageContainer');
const GZIPPacked = require('../tl/core/GZIPPacked') const GZIPPacked = require('../tl/core/GZIPPacked');
const RequestState = require('./RequestState') const RequestState = require('./RequestState');
const { MsgsAck, upload, MsgsStateInfo, Pong } = require('../tl').constructors const {
const MessagePacker = require('../extensions/MessagePacker') MsgsAck,
const BinaryReader = require('../extensions/BinaryReader') upload,
const { UpdateConnectionState } = require("./index"); MsgsStateInfo,
const { BadMessageError } = require("../errors/Common") Pong,
} = require('../tl').constructors;
const MessagePacker = require('../extensions/MessagePacker');
const BinaryReader = require('../extensions/BinaryReader');
const { UpdateConnectionState } = require('./index');
const { BadMessageError } = require('../errors/Common');
const { const {
BadServerSalt, BadServerSalt,
BadMsgNotification, BadMsgNotification,
@ -22,12 +27,12 @@ const {
MsgsStateReq, MsgsStateReq,
MsgResendReq, MsgResendReq,
MsgsAllInfo, MsgsAllInfo,
} = require('../tl').constructors } = require('../tl').constructors;
const { SecurityError } = require('../errors/Common') const { SecurityError } = require('../errors/Common');
const { InvalidBufferError } = require('../errors/Common') const { InvalidBufferError } = require('../errors/Common');
const { LogOut } = require('../tl').requests.auth const { LogOut } = require('../tl').requests.auth;
const { RPCMessageToError } = require('../errors') const { RPCMessageToError } = require('../errors');
const { TypeNotFoundError } = require('../errors/Common') const { TypeNotFoundError } = require('../errors/Common');
/** /**
@ -55,24 +60,24 @@ class MTProtoSender {
autoReconnectCallback: null, autoReconnectCallback: null,
isMainSender: null, isMainSender: null,
onConnectionBreak: null, onConnectionBreak: null,
} };
/** /**
* @param authKey * @param authKey
* @param opts * @param opts
*/ */
constructor(authKey, opts) { constructor(authKey, opts) {
const args = { ...MTProtoSender.DEFAULT_OPTIONS, ...opts } const args = { ...MTProtoSender.DEFAULT_OPTIONS, ...opts };
this._connection = null this._connection = null;
this._log = args.logger this._log = args.logger;
this._dcId = args.dcId this._dcId = args.dcId;
this._retries = args.retries this._retries = args.retries;
this._delay = args.delay this._delay = args.delay;
this._autoReconnect = args.autoReconnect this._autoReconnect = args.autoReconnect;
this._connectTimeout = args.connectTimeout this._connectTimeout = args.connectTimeout;
this._authKeyCallback = args.authKeyCallback this._authKeyCallback = args.authKeyCallback;
this._updateCallback = args.updateCallback this._updateCallback = args.updateCallback;
this._autoReconnectCallback = args.autoReconnectCallback this._autoReconnectCallback = args.autoReconnectCallback;
this._isMainSender = args.isMainSender; this._isMainSender = args.isMainSender;
this._onConnectionBreak = args.onConnectionBreak; this._onConnectionBreak = args.onConnectionBreak;
@ -84,44 +89,44 @@ class MTProtoSender {
* be cleared but on explicit user disconnection all the * be cleared but on explicit user disconnection all the
* pending futures should be cancelled. * pending futures should be cancelled.
*/ */
this._user_connected = false this._user_connected = false;
this._reconnecting = false this._reconnecting = false;
this._disconnected = true this._disconnected = true;
/** /**
* We need to join the loops upon disconnection * We need to join the loops upon disconnection
*/ */
this._send_loop_handle = null this._send_loop_handle = null;
this._recv_loop_handle = null this._recv_loop_handle = null;
/** /**
* Preserving the references of the AuthKey and state is important * Preserving the references of the AuthKey and state is important
*/ */
this.authKey = authKey || new AuthKey() this.authKey = authKey || new AuthKey();
this._state = new MTProtoState(this.authKey, this._log) this._state = new MTProtoState(this.authKey, this._log);
/** /**
* Outgoing messages are put in a queue and sent in a batch. * Outgoing messages are put in a queue and sent in a batch.
* Note that here we're also storing their ``_RequestState``. * 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. * 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. * 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. * Similar to pending_messages but only for the last acknowledges.
* These can't go in pending_messages because no acknowledge for them * 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. * 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 * Jump table from response ID to method that handles it
@ -142,7 +147,7 @@ class MTProtoSender {
[MsgsStateReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this), [MsgsStateReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this),
[MsgResendReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this), [MsgResendReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this),
[MsgsAllInfo.CONSTRUCTOR_ID]: this._handleMsgAll.bind(this), [MsgsAllInfo.CONSTRUCTOR_ID]: this._handleMsgAll.bind(this),
} };
} }
// Public API // Public API
@ -155,30 +160,30 @@ class MTProtoSender {
*/ */
async connect(connection, force) { async connect(connection, force) {
if (this._user_connected && !force) { if (this._user_connected && !force) {
this._log.info('User is already connected!') this._log.info('User is already connected!');
return false return false;
} }
this._connection = connection this._connection = connection;
for (let attempt = 0; attempt < this._retries; attempt++) { for (let attempt = 0; attempt < this._retries; attempt++) {
try { try {
await this._connect() await this._connect();
if (this._updateCallback) { if (this._updateCallback) {
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.connected)) this._updateCallback(new UpdateConnectionState(UpdateConnectionState.connected));
} }
break break;
} catch (err) { } catch (err) {
if (this._updateCallback && attempt===0){ if (this._updateCallback && attempt === 0) {
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected)) this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected));
} }
this._log.error('WebSocket connection failed attempt: ' + (attempt + 1)) this._log.error('WebSocket connection failed attempt: ' + (attempt + 1));
await Helpers.sleep(this._delay) await Helpers.sleep(this._delay);
} }
} }
} }
isConnected() { 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. * all pending requests, and closes the send and receive loops.
*/ */
async disconnect() { async disconnect() {
await this._disconnect() await this._disconnect();
} }
/** /**
@ -216,12 +221,12 @@ class MTProtoSender {
*/ */
send(request) { send(request) {
if (!this._user_connected) { if (!this._user_connected) {
throw new Error('Cannot send requests while disconnected') throw new Error('Cannot send requests while disconnected');
} }
//CONTEST //CONTEST
const state = new RequestState(request) const state = new RequestState(request);
this._send_queue.append(state) this._send_queue.append(state);
return state.promise return state.promise;
/* /*
if (!Helpers.isArrayLike(request)) { if (!Helpers.isArrayLike(request)) {
const state = new RequestState(request) const state = new RequestState(request)
@ -240,18 +245,18 @@ class MTProtoSender {
* @private * @private
*/ */
async _connect() { async _connect() {
this._log.info('Connecting to {0}...'.replace('{0}', this._connection)) this._log.info('Connecting to {0}...'.replace('{0}', this._connection));
await this._connection.connect() await this._connection.connect();
this._log.debug('Connection success!') this._log.debug('Connection success!');
//process.exit(0) //process.exit(0)
if (!this.authKey.getKey()) { if (!this.authKey.getKey()) {
const plain = new MtProtoPlainSender(this._connection, this._log) const plain = new MtProtoPlainSender(this._connection, this._log);
this._log.debug('New auth_key attempt ...') this._log.debug('New auth_key attempt ...');
const res = await doAuthentication(plain, this._log) const res = await doAuthentication(plain, this._log);
this._log.debug('Generated new auth_key successfully') this._log.debug('Generated new auth_key successfully');
await this.authKey.setKey(res.authKey) 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 * This is *EXTREMELY* important since we don't control
@ -260,41 +265,41 @@ class MTProtoSender {
* switch to different data centers. * switch to different data centers.
*/ */
if (this._authKeyCallback) { if (this._authKeyCallback) {
await this._authKeyCallback(this.authKey, this._dcId) await this._authKeyCallback(this.authKey, this._dcId);
} }
} else { } else {
this._log.debug('Already have an auth key ...') this._log.debug('Already have an auth key ...');
} }
this._user_connected = true this._user_connected = true;
this._reconnecting = false this._reconnecting = false;
this._log.debug('Starting send loop') this._log.debug('Starting send loop');
this._send_loop_handle = this._sendLoop() this._send_loop_handle = this._sendLoop();
this._log.debug('Starting receive loop') this._log.debug('Starting receive loop');
this._recv_loop_handle = this._recvLoop() this._recv_loop_handle = this._recvLoop();
// _disconnected only completes after manual disconnection // _disconnected only completes after manual disconnection
// or errors after which the sender cannot continue such // or errors after which the sender cannot continue such
// as failing to reconnect or any unexpected error. // 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) { async _disconnect(error = null) {
this._send_queue.rejectAll() this._send_queue.rejectAll();
if (this._connection === null) { if (this._connection === null) {
this._log.info('Not disconnecting (already have no connection)') this._log.info('Not disconnecting (already have no connection)');
return return;
} }
if (this._updateCallback){ if (this._updateCallback) {
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected)) this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected));
} }
this._log.info('Disconnecting from %s...'.replace('%s', this._connection.toString())) this._log.info('Disconnecting from %s...'.replace('%s', this._connection.toString()));
this._user_connected = false this._user_connected = false;
this._log.debug('Closing current connection...') this._log.debug('Closing current connection...');
await this._connection.disconnect() await this._connection.disconnect();
} }
/** /**
@ -305,92 +310,92 @@ class MTProtoSender {
* @private * @private
*/ */
async _sendLoop() { 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) { while (this._user_connected && !this._reconnecting) {
if (this._pending_ack.size) { if (this._pending_ack.size) {
const ack = new RequestState(new MsgsAck({ msgIds: Array(...this._pending_ack) })) const ack = new RequestState(new MsgsAck({ msgIds: Array(...this._pending_ack) }));
this._send_queue.append(ack) this._send_queue.append(ack);
this._last_acks.push(ack) this._last_acks.push(ack);
this._pending_ack.clear() 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? // TODO Wait for the connection send queue to be empty?
// This means that while it's not empty we can wait for // This means that while it's not empty we can wait for
// more messages to be added to the send queue. // 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) { if (this._reconnecting) {
return return;
} }
if (!res) { if (!res) {
continue continue;
} }
let data = res.data let data = res.data;
const batch = res.batch const batch = res.batch;
this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`) 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 { try {
await this._connection.send(data) await this._connection.send(data);
} catch (e) { } catch (e) {
this._log.error(e) this._log.error(e);
this._log.info('Connection closed while sending data') this._log.info('Connection closed while sending data');
return return;
} }
for (const state of batch) { for (const state of batch) {
if (!Array.isArray(state)) { if (!Array.isArray(state)) {
if (state.request.classType === 'request') { if (state.request.classType === 'request') {
this._pending_state[state.msgId] = state this._pending_state[state.msgId] = state;
} }
} else { } else {
for (const s of state) { for (const s of state) {
if (s.request.classType === 'request') { 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() { async _recvLoop() {
let body let body;
let message let message;
while (this._user_connected && !this._reconnecting) { 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...') this._log.debug('Receiving items from the network...');
try { try {
body = await this._connection.recv() body = await this._connection.recv();
} catch (e) { } catch (e) {
// this._log.info('Connection closed while receiving data'); // this._log.info('Connection closed while receiving data');
this._log.warn('Connection closed while receiving data') this._log.warn('Connection closed while receiving data');
this.reconnect() this.reconnect();
return return;
} }
try { try {
message = await this._state.decryptMessageData(body) message = await this._state.decryptMessageData(body);
} catch (e) { } catch (e) {
if (e instanceof TypeNotFoundError) { if (e instanceof TypeNotFoundError) {
// Received object which we don't know how to deserialize // Received object which we don't know how to deserialize
this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`) this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`);
continue continue;
} else if (e instanceof SecurityError) { } else if (e instanceof SecurityError) {
// A step while decoding had the incorrect data. This message // A step while decoding had the incorrect data. This message
// should not be considered safe and it should be ignored. // should not be considered safe and it should be ignored.
this._log.warn(`Security error while unpacking a received message: ${e}`) this._log.warn(`Security error while unpacking a received message: ${e}`);
continue continue;
} else if (e instanceof InvalidBufferError) { } else if (e instanceof InvalidBufferError) {
this._log.info('Broken authorization key; resetting') this._log.info('Broken authorization key; resetting');
if (this._updateCallback && this._isMainSender){ if (this._updateCallback && this._isMainSender) {
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.broken)) this._updateCallback(new UpdateConnectionState(UpdateConnectionState.broken));
} else if (this._onConnectionBreak && !this._isMainSender){ } else if (this._onConnectionBreak && !this._isMainSender) {
// Deletes the current sender from the object // 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 // 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 // We can disconnect at sign in
/* await this.disconnect() /* await this.disconnect()
*/ */
return return;
} else { } else {
this._log.error('Unhandled error while receiving data') this._log.error('Unhandled error while receiving data');
this._log.error(e) this._log.error(e);
this.reconnect() this.reconnect();
return return;
} }
} }
try { try {
await this._processMessage(message) await this._processMessage(message);
} catch (e) { } catch (e) {
this._log.error('Unhandled error while receiving data') this._log.error('Unhandled error while receiving data');
this._log.error(e) this._log.error(e);
} }
} }
} }
@ -430,15 +435,15 @@ class MTProtoSender {
* @private * @private
*/ */
async _processMessage(message) { async _processMessage(message) {
this._pending_ack.add(message.msgId) this._pending_ack.add(message.msgId);
// eslint-disable-next-line require-atomic-updates // eslint-disable-next-line require-atomic-updates
message.obj = await message.obj message.obj = await message.obj;
let handler = this._handlers[message.obj.CONSTRUCTOR_ID] let handler = this._handlers[message.obj.CONSTRUCTOR_ID];
if (!handler) { 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 * @private
*/ */
_popStates(msgId) { _popStates(msgId) {
let state = this._pending_state[msgId] let state = this._pending_state[msgId];
if (state) { if (state) {
delete this._pending_state[msgId] delete this._pending_state[msgId];
return [state] return [state];
} }
const toPop = [] const toPop = [];
for (state of Object.values(this._pending_state)) { for (state of Object.values(this._pending_state)) {
if (state.containerId && state.containerId.equals(msgId)) { if (state.containerId && state.containerId.equals(msgId)) {
toPop.push(state.msgId) toPop.push(state.msgId);
} }
} }
if (toPop.length) { if (toPop.length) {
const temp = [] const temp = [];
for (const x of toPop) { for (const x of toPop) {
temp.push(this._pending_state[x]) temp.push(this._pending_state[x]);
delete this._pending_state[x] delete this._pending_state[x];
} }
return temp return temp;
} }
for (const ack of this._last_acks) { for (const ack of this._last_acks) {
if (ack.msgId === msgId) { if (ack.msgId === msgId) {
return [ack] return [ack];
} }
} }
return [] return [];
} }
/** /**
@ -490,12 +495,12 @@ class MTProtoSender {
* @private * @private
*/ */
_handleRPCResult(message) { _handleRPCResult(message) {
const RPCResult = message.obj const RPCResult = message.obj;
const state = this._pending_state[RPCResult.reqMsgId] const state = this._pending_state[RPCResult.reqMsgId];
if (state) { 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) { if (!state) {
// TODO We should not get responses to things we never sent // 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 // See #658, #759 and #958. They seem to happen in a container
// which contain the real response right after. // which contain the real response right after.
try { try {
const reader = new BinaryReader(RPCResult.body) const reader = new BinaryReader(RPCResult.body);
if (!(reader.tgReadObject() instanceof upload.File)) { if (!(reader.tgReadObject() instanceof upload.File)) {
throw new TypeNotFoundError('Not an upload.File') throw new TypeNotFoundError('Not an upload.File');
} }
} catch (e) { } catch (e) {
this._log.error(e) this._log.error(e);
if (e instanceof TypeNotFoundError) { if (e instanceof TypeNotFoundError) {
this._log.info(`Received response without parent request: ${RPCResult.body}`) this._log.info(`Received response without parent request: ${RPCResult.body}`);
return return;
} else { } else {
throw e throw e;
} }
} }
} }
if (RPCResult.error) { if (RPCResult.error) {
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
const error = RPCMessageToError(RPCResult.error, state.request) const error = RPCMessageToError(RPCResult.error, state.request);
this._send_queue.append(new RequestState(new MsgsAck({ msgIds: [state.msgId] }))) this._send_queue.append(new RequestState(new MsgsAck({ msgIds: [state.msgId] })));
state.reject(error) state.reject(error);
} else { } else {
const reader = new BinaryReader(RPCResult.body) const reader = new BinaryReader(RPCResult.body);
const read = state.request.readResult(reader) const read = state.request.readResult(reader);
state.resolve(read) state.resolve(read);
} }
} }
@ -537,9 +542,9 @@ class MTProtoSender {
* @private * @private
*/ */
async _handleContainer(message) { async _handleContainer(message) {
this._log.debug('Handling container') this._log.debug('Handling container');
for (const innerMessage of message.obj.messages) { for (const innerMessage of message.obj.messages) {
await this._processMessage(innerMessage) await this._processMessage(innerMessage);
} }
} }
@ -551,21 +556,21 @@ class MTProtoSender {
* @private * @private
*/ */
async _handleGzipPacked(message) { async _handleGzipPacked(message) {
this._log.debug('Handling gzipped data') this._log.debug('Handling gzipped data');
const reader = new BinaryReader(message.obj.data) const reader = new BinaryReader(message.obj.data);
message.obj = reader.tgReadObject() message.obj = reader.tgReadObject();
await this._processMessage(message) await this._processMessage(message);
} }
async _handleUpdate(message) { async _handleUpdate(message) {
if (message.obj.SUBCLASS_OF_ID !== 0x8af52aac) { if (message.obj.SUBCLASS_OF_ID !== 0x8af52aac) {
// crc32(b'Updates') // crc32(b'Updates')
this._log.warn(`Note: ${message.obj.className} is not an update, not dispatching it`) this._log.warn(`Note: ${message.obj.className} is not an update, not dispatching it`);
return return;
} }
this._log.debug('Handling update ' + message.obj.className) this._log.debug('Handling update ' + message.obj.className);
if (this._updateCallback) { if (this._updateCallback) {
this._updateCallback(message.obj) this._updateCallback(message.obj);
} }
} }
@ -578,14 +583,14 @@ class MTProtoSender {
* @private * @private
*/ */
async _handlePong(message) { async _handlePong(message) {
const pong = message.obj const pong = message.obj;
this._log.debug(`Handling pong for message ${pong.msgId}`) this._log.debug(`Handling pong for message ${pong.msgId}`);
const state = this._pending_state[pong.msgId] const state = this._pending_state[pong.msgId];
delete this._pending_state[pong.msgId] delete this._pending_state[pong.msgId];
// Todo Check result // Todo Check result
if (state) { if (state) {
state.resolve(pong) state.resolve(pong);
} }
} }
@ -599,12 +604,12 @@ class MTProtoSender {
* @private * @private
*/ */
async _handleBadServerSalt(message) { async _handleBadServerSalt(message) {
const badSalt = message.obj const badSalt = message.obj;
this._log.debug(`Handling bad salt for message ${badSalt.badMsgId}`) this._log.debug(`Handling bad salt for message ${badSalt.badMsgId}`);
this._state.salt = badSalt.newServerSalt this._state.salt = badSalt.newServerSalt;
const states = this._popStates(badSalt.badMsgId) const states = this._popStates(badSalt.badMsgId);
this._send_queue.extend(states) this._send_queue.extend(states);
this._log.debug(`${states.length} message(s) will be resent`) this._log.debug(`${states.length} message(s) will be resent`);
} }
/** /**
@ -617,32 +622,32 @@ class MTProtoSender {
* @private * @private
*/ */
async _handleBadNotification(message) { async _handleBadNotification(message) {
const badMsg = message.obj const badMsg = message.obj;
const states = this._popStates(badMsg.badMsgId) const states = this._popStates(badMsg.badMsgId);
this._log.debug(`Handling bad msg ${JSON.stringify(badMsg)}`) this._log.debug(`Handling bad msg ${JSON.stringify(badMsg)}`);
if ([16, 17].includes(badMsg.errorCode)) { if ([16, 17].includes(badMsg.errorCode)) {
// Sent msg_id too low or too high (respectively). // Sent msg_id too low or too high (respectively).
// Use the current msg_id to determine the right time offset. // Use the current msg_id to determine the right time offset.
const to = this._state.updateTimeOffset(message.msgId) const to = this._state.updateTimeOffset(message.msgId);
this._log.info(`System clock is wrong, set time offset to ${to}s`) this._log.info(`System clock is wrong, set time offset to ${to}s`);
} else if (badMsg.errorCode === 32) { } else if (badMsg.errorCode === 32) {
// msg_seqno too low, so just pump it up by some "large" amount // 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 // 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) { } else if (badMsg.errorCode === 33) {
// msg_seqno too high never seems to happen but just in case // msg_seqno too high never seems to happen but just in case
this._state._sequence -= 16 this._state._sequence -= 16;
} else { } else {
for (const state of states) { 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 // Messages are to be re-sent once we've corrected the issue
this._send_queue.extend(states) this._send_queue.extend(states);
this._log.debug(`${states.length} messages will be resent due to bad msg`) this._log.debug(`${states.length} messages will be resent due to bad msg`);
} }
/** /**
@ -655,9 +660,9 @@ class MTProtoSender {
*/ */
async _handleDetailedInfo(message) { async _handleDetailedInfo(message) {
// TODO https://goo.gl/VvpCC6 // TODO https://goo.gl/VvpCC6
const msgId = message.obj.answerMsgId const msgId = message.obj.answerMsgId;
this._log.debug(`Handling detailed info for message ${msgId}`) this._log.debug(`Handling detailed info for message ${msgId}`);
this._pending_ack.add(msgId) this._pending_ack.add(msgId);
} }
/** /**
@ -670,9 +675,9 @@ class MTProtoSender {
*/ */
async _handleNewDetailedInfo(message) { async _handleNewDetailedInfo(message) {
// TODO https://goo.gl/VvpCC6 // TODO https://goo.gl/VvpCC6
const msgId = message.obj.answerMsgId const msgId = message.obj.answerMsgId;
this._log.debug(`Handling new detailed info for message ${msgId}`) this._log.debug(`Handling new detailed info for message ${msgId}`);
this._pending_ack.add(msgId) this._pending_ack.add(msgId);
} }
/** /**
@ -685,8 +690,8 @@ class MTProtoSender {
*/ */
async _handleNewSessionCreated(message) { async _handleNewSessionCreated(message) {
// TODO https://goo.gl/LMyN7A // TODO https://goo.gl/LMyN7A
this._log.debug('Handling new session created') this._log.debug('Handling new session created');
this._state.salt = message.obj.serverSalt this._state.salt = message.obj.serverSalt;
} }
/** /**
@ -708,13 +713,13 @@ class MTProtoSender {
* @private * @private
*/ */
async _handleAck(message) { async _handleAck(message) {
const ack = message.obj const ack = message.obj;
this._log.debug(`Handling acknowledge for ${ack.msgIds}`) this._log.debug(`Handling acknowledge for ${ack.msgIds}`);
for (const msgId of 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) { if (state && state.request instanceof LogOut) {
delete this._pending_state[msgId] delete this._pending_state[msgId];
state.resolve(true) state.resolve(true);
} }
} }
} }
@ -731,12 +736,12 @@ class MTProtoSender {
async _handleFutureSalts(message) { async _handleFutureSalts(message) {
// TODO save these salts and automatically adjust to the // TODO save these salts and automatically adjust to the
// correct one whenever the salt in use expires. // correct one whenever the salt in use expires.
this._log.debug(`Handling future salts for message ${message.msgId}`) this._log.debug(`Handling future salts for message ${message.msgId}`);
const state = this._pending_state[message.msgId] const state = this._pending_state[message.msgId];
if (state) { if (state) {
delete this._pending_state[message] delete this._pending_state[message];
state.resolve(message.obj) state.resolve(message.obj);
} }
} }
@ -749,8 +754,9 @@ class MTProtoSender {
*/ */
async _handleStateForgotten(message) { async _handleStateForgotten(message) {
this._send_queue.append( 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() { async reconnect() {
if (this._user_connected && !this._reconnecting) { if (this._user_connected && !this._reconnecting) {
this._reconnecting = true this._reconnecting = true;
// TODO Should we set this? // TODO Should we set this?
// this._user_connected = false // this._user_connected = false
this._log.info("Started reconnecting") this._log.info('Started reconnecting');
this._reconnect() this._reconnect();
} }
} }
async _reconnect() { async _reconnect() {
this._log.debug('Closing current connection...') this._log.debug('Closing current connection...');
try { try {
await this.disconnect() await this.disconnect();
} catch (err) { } catch (err) {
this._log.warn(err) this._log.warn(err);
} }
this._send_queue.append(null) this._send_queue.append(null);
this._state.reset() 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 // uncomment this if you want to resend
//this._send_queue.extend(Object.values(this._pending_state)) //this._send_queue.extend(Object.values(this._pending_state))
this._pending_state = {} this._pending_state = {};
if (this._autoReconnectCallback) { if (this._autoReconnectCallback) {
await this._autoReconnectCallback() await this._autoReconnectCallback();
} }
} }
} }
module.exports = MTProtoSender module.exports = MTProtoSender;

View File

@ -1,13 +1,19 @@
const Helpers = require('../Helpers') const Helpers = require('../Helpers');
const IGE = require('../crypto/IGE') const IGE = require('../crypto/IGE');
const BinaryReader = require('../extensions/BinaryReader') const BinaryReader = require('../extensions/BinaryReader');
const GZIPPacked = require('../tl/core/GZIPPacked') const GZIPPacked = require('../tl/core/GZIPPacked');
const { TLMessage } = require('../tl/core') const { TLMessage } = require('../tl/core');
const { SecurityError, InvalidBufferError } = require('../errors/Common') const {
const { InvokeAfterMsg } = require('../tl').requests SecurityError,
const BigInt = require('big-integer') InvalidBufferError,
const { toSignedLittleBuffer,readBufferFromBigInt } = require("../Helpers") } = require('../errors/Common');
const { readBigIntFromBuffer } = require("../Helpers") const { InvokeAfterMsg } = require('../tl').requests;
const BigInt = require('big-integer');
const {
toSignedLittleBuffer,
readBufferFromBigInt,
} = require('../Helpers');
const { readBigIntFromBuffer } = require('../Helpers');
class MTProtoState { class MTProtoState {
/** /**
@ -36,13 +42,13 @@ class MTProtoState {
* @param loggers * @param loggers
*/ */
constructor(authKey, loggers) { constructor(authKey, loggers) {
this.authKey = authKey this.authKey = authKey;
this._log = loggers this._log = loggers;
this.timeOffset = 0 this.timeOffset = 0;
this.salt = 0 this.salt = 0;
this.id = this._sequence = this._lastMsgId = null this.id = this._sequence = this._lastMsgId = null;
this.reset() this.reset();
} }
/** /**
@ -50,9 +56,9 @@ class MTProtoState {
*/ */
reset() { reset() {
// Session IDs can be random on every connection // Session IDs can be random on every connection
this.id = Helpers.generateRandomLong(true) this.id = Helpers.generateRandomLong(true);
this._sequence = 0 this._sequence = 0;
this._lastMsgId = BigInt(0) this._lastMsgId = BigInt(0);
} }
/** /**
@ -61,7 +67,7 @@ class MTProtoState {
* @param message * @param message
*/ */
updateMessageId(message) { updateMessageId(message) {
message.msgId = this._getNewMsgId() message.msgId = this._getNewMsgId();
} }
/** /**
@ -72,14 +78,17 @@ class MTProtoState {
* @returns {{iv: Buffer, key: Buffer}} * @returns {{iv: Buffer, key: Buffer}}
*/ */
async _calcKey(authKey, msgKey, client) { async _calcKey(authKey, msgKey, client) {
const x = client === true ? 0 : 8 const x = client === true ? 0 : 8;
const [sha256a , sha256b] = await Promise.all([ const [sha256a, sha256b] = await Promise.all([
Helpers.sha256(Buffer.concat([msgKey, authKey.slice(x, x + 36)])), Helpers.sha256(Buffer.concat([msgKey, authKey.slice(x, x + 36)])),
Helpers.sha256(Buffer.concat([authKey.slice(x + 40, x + 76), msgKey])) 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 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)]) const iv = Buffer.concat([sha256b.slice(0, 8), sha256a.slice(8, 24), sha256b.slice(24, 32)]);
return { key, iv } return {
key,
iv,
};
} }
/** /**
@ -91,22 +100,22 @@ class MTProtoState {
* @param afterId * @param afterId
*/ */
async writeDataAsMessage(buffer, data, contentRelated, afterId) { async writeDataAsMessage(buffer, data, contentRelated, afterId) {
const msgId = this._getNewMsgId() const msgId = this._getNewMsgId();
const seqNo = this._getSeqNo(contentRelated) const seqNo = this._getSeqNo(contentRelated);
let body let body;
if (!afterId) { if (!afterId) {
body = await GZIPPacked.gzipIfSmaller(contentRelated, data) body = await GZIPPacked.gzipIfSmaller(contentRelated, data);
} else { } 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) const s = Buffer.alloc(4);
s.writeInt32LE(seqNo, 0) s.writeInt32LE(seqNo, 0);
const b = Buffer.alloc(4) const b = Buffer.alloc(4);
b.writeInt32LE(body.length, 0) b.writeInt32LE(body.length, 0);
const m = toSignedLittleBuffer(msgId, 8) const m = toSignedLittleBuffer(msgId, 8);
buffer.write(Buffer.concat([m, s, b])) buffer.write(Buffer.concat([m, s, b]));
buffer.write(body) buffer.write(body);
return msgId return msgId;
} }
/** /**
@ -115,21 +124,25 @@ class MTProtoState {
* @param data * @param data
*/ */
async encryptMessageData(data) { async encryptMessageData(data) {
await this.authKey.waitForKey() await this.authKey.waitForKey();
const s = toSignedLittleBuffer(this.salt,8) const s = toSignedLittleBuffer(this.salt, 8);
const i = toSignedLittleBuffer(this.id,8) const i = toSignedLittleBuffer(this.id, 8);
data = Buffer.concat([Buffer.concat([s,i]), data]) data = Buffer.concat([Buffer.concat([s, i]), data]);
const padding = Helpers.generateRandomBytes(Helpers.mod(-(data.length + 12), 16) + 12) const padding = Helpers.generateRandomBytes(Helpers.mod(-(data.length + 12), 16) + 12);
// Being substr(what, offset, length); x = 0 for client // Being substr(what, offset, length); x = 0 for client
// "msg_key_large = SHA256(substr(auth_key, 88+x, 32) + pt + padding)" // "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)" // "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) const keyId = Helpers.readBufferFromBigInt(this.authKey.keyId, 8);
return Buffer.concat([keyId, msgKey, new IGE(key,iv).encryptIge(Buffer.concat([data, padding]))]) return Buffer.concat([keyId, msgKey, new IGE(key, iv).encryptIge(Buffer.concat([data, padding]))]);
} }
/** /**
@ -138,45 +151,49 @@ class MTProtoState {
*/ */
async decryptMessageData(body) { async decryptMessageData(body) {
if (body.length < 8) { if (body.length < 8) {
throw new InvalidBufferError(body) throw new InvalidBufferError(body);
} }
// TODO Check salt,sessionId, and sequenceNumber // 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)) { 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 msgKey = body.slice(8, 24);
const { iv, key } = await this._calcKey(this.authKey.getKey(), msgKey, false) const {
body = new IGE(key,iv).decryptIge(body.slice(24)) 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 // https://core.telegram.org/mtproto/security_guidelines
// Sections "checking sha256 hash" and "message length" // 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))) { 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) const reader = new BinaryReader(body);
reader.readLong() // removeSalt reader.readLong(); // removeSalt
const serverId = reader.readLong() const serverId = reader.readLong();
if (serverId !== this.id) { if (serverId !== this.id) {
// throw new SecurityError('Server replied with a wrong session ID'); // throw new SecurityError('Server replied with a wrong session ID');
} }
const remoteMsgId = reader.readLong() const remoteMsgId = reader.readLong();
const remoteSequence = reader.readInt() const remoteSequence = reader.readInt();
reader.readInt() // msgLen for the inner object, padding ignored reader.readInt(); // msgLen for the inner object, padding ignored
// We could read msg_len bytes and use those in a new reader to read // 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 // the next TLObject without including the padding, but since the
// reader isn't used for anything else after this, it's unnecessary. // 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 * @private
*/ */
_getNewMsgId() { _getNewMsgId() {
const now = new Date().getTime() / 1000 + this.timeOffset const now = new Date().getTime() / 1000 + this.timeOffset;
const nanoseconds = Math.floor((now - Math.floor(now)) * 1e9) const nanoseconds = Math.floor((now - Math.floor(now)) * 1e9);
let newMsgId = (BigInt(Math.floor(now)).shiftLeft(BigInt(32))).or(BigInt(nanoseconds).shiftLeft(BigInt(2))) let newMsgId = (BigInt(Math.floor(now))
.shiftLeft(BigInt(32))).or(BigInt(nanoseconds)
.shiftLeft(BigInt(2)));
if (this._lastMsgId.greaterOrEquals(newMsgId)) { if (this._lastMsgId.greaterOrEquals(newMsgId)) {
newMsgId = this._lastMsgId.add(BigInt(4)) newMsgId = this._lastMsgId.add(BigInt(4));
} }
this._lastMsgId = newMsgId this._lastMsgId = newMsgId;
return newMsgId return newMsgId;
} }
/** /**
@ -201,20 +220,20 @@ class MTProtoState {
* @param correctMsgId {BigInteger} * @param correctMsgId {BigInteger}
*/ */
updateTimeOffset(correctMsgId) { updateTimeOffset(correctMsgId) {
const bad = this._getNewMsgId() const bad = this._getNewMsgId();
const old = this.timeOffset const old = this.timeOffset;
const now = Math.floor(new Date().getTime() / 1000) const now = Math.floor(new Date().getTime() / 1000);
const correct = correctMsgId.shiftRight(BigInt(32)) const correct = correctMsgId.shiftRight(BigInt(32));
this.timeOffset = correct - now this.timeOffset = correct - now;
if (this.timeOffset !== old) { if (this.timeOffset !== old) {
this._lastMsgId = BigInt(0) this._lastMsgId = BigInt(0);
this._log.debug( this._log.debug(
`Updated time offset (old offset ${old}, bad ${bad}, good ${correctMsgId}, new ${this.timeOffset})`, `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) { _getSeqNo(contentRelated) {
if (contentRelated) { if (contentRelated) {
const result = this._sequence * 2 + 1 const result = this._sequence * 2 + 1;
this._sequence += 1 this._sequence += 1;
return result return result;
} else { } else {
return this._sequence * 2 return this._sequence * 2;
} }
} }
} }
module.exports = MTProtoState module.exports = MTProtoState;

View File

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

View File

@ -1,5 +1,5 @@
const PromisedWebSockets = require('../../extensions/PromisedWebSockets') const PromisedWebSockets = require('../../extensions/PromisedWebSockets');
const AsyncQueue = require('../../extensions/AsyncQueue') const AsyncQueue = require('../../extensions/AsyncQueue');
/** /**
* The `Connection` class is a wrapper around ``asyncio.open_connection``. * 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). * the client is disconnected (includes remote disconnections).
*/ */
class Connection { class Connection {
PacketCodecClass = null PacketCodecClass = null;
constructor(ip, port, dcId, loggers) { constructor(ip, port, dcId, loggers) {
this._ip = ip this._ip = ip;
this._port = port this._port = port;
this._dcId = dcId this._dcId = dcId;
this._log = loggers this._log = loggers;
this._connected = false this._connected = false;
this._sendTask = null this._sendTask = null;
this._recvTask = null this._recvTask = null;
this._codec = null this._codec = null;
this._obfuscation = null // TcpObfuscated and MTProxy this._obfuscation = null; // TcpObfuscated and MTProxy
this._sendArray = new AsyncQueue() this._sendArray = new AsyncQueue();
this._recvArray = new AsyncQueue() this._recvArray = new AsyncQueue();
//this.socket = new PromiseSocket(new Socket()) //this.socket = new PromiseSocket(new Socket())
this.socket = new PromisedWebSockets() this.socket = new PromisedWebSockets();
} }
async _connect() { async _connect() {
this._log.debug('Connecting') this._log.debug('Connecting');
this._codec = new this.PacketCodecClass(this) this._codec = new this.PacketCodecClass(this);
await this.socket.connect(this._port, this._ip, this) await this.socket.connect(this._port, this._ip, this);
this._log.debug('Finished connecting') this._log.debug('Finished connecting');
// await this.socket.connect({host: this._ip, port: this._port}); // await this.socket.connect({host: this._ip, port: this._port});
await this._initConn() await this._initConn();
} }
async connect() { async connect() {
await this._connect() await this._connect();
this._connected = true this._connected = true;
if (!this._sendTask) { if (!this._sendTask) {
this._sendTask = this._sendLoop() this._sendTask = this._sendLoop();
} }
this._recvTask = this._recvLoop() this._recvTask = this._recvLoop();
} }
async disconnect() { async disconnect() {
this._connected = false this._connected = false;
await this._recvArray.push(null) await this._recvArray.push(null);
await this.socket.close() await this.socket.close();
} }
async send(data) { async send(data) {
if (!this._connected) { 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() { async recv() {
while (this._connected) { while (this._connected) {
const result = await this._recvArray.pop() const result = await this._recvArray.pop();
// null = sentinel value = keep trying // null = sentinel value = keep trying
if (result) { if (result) {
return result return result;
} }
} }
throw new Error('Not connected') throw new Error('Not connected');
} }
async _sendLoop() { async _sendLoop() {
// TODO handle errors // TODO handle errors
try { try {
while (this._connected) { while (this._connected) {
const data = await this._sendArray.pop() const data = await this._sendArray.pop();
if (!data) { if (!data) {
this._sendTask = null this._sendTask = null;
return return;
} }
await this._send(data) await this._send(data);
} }
} catch (e) { } catch (e) {
this._log.info('The server closed the connection while sending') this._log.info('The server closed the connection while sending');
} }
} }
async _recvLoop() { async _recvLoop() {
let data let data;
while (this._connected) { while (this._connected) {
try { try {
data = await this._recv() data = await this._recv();
if (!data) { if (!data) {
throw new Error("no data received") throw new Error('no data received');
} }
} catch (e) { } catch (e) {
this._log.info('connection closed') this._log.info('connection closed');
//await this._recvArray.push() //await this._recvArray.push()
this.disconnect() this.disconnect();
return return;
} }
await this._recvArray.push(data) await this._recvArray.push(data);
} }
} }
async _initConn() { async _initConn() {
if (this._codec.tag) { if (this._codec.tag) {
await this.socket.write(this._codec.tag) await this.socket.write(this._codec.tag);
} }
} }
async _send(data) { async _send(data) {
const encodedPacket = this._codec.encodePacket(data) const encodedPacket = this._codec.encodePacket(data);
this.socket.write(encodedPacket) this.socket.write(encodedPacket);
} }
async _recv() { async _recv() {
return await this._codec.readPacket(this.socket) return await this._codec.readPacket(this.socket);
} }
toString() { 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 { class ObfuscatedConnection extends Connection {
ObfuscatedIO = null ObfuscatedIO = null;
async _initConn() { async _initConn() {
this._obfuscation = new this.ObfuscatedIO(this) this._obfuscation = new this.ObfuscatedIO(this);
this.socket.write(this._obfuscation.header) this.socket.write(this._obfuscation.header);
} }
_send(data) { _send(data) {
this._obfuscation.write(this._codec.encodePacket(data)) this._obfuscation.write(this._codec.encodePacket(data));
} }
async _recv() { async _recv() {
return await this._codec.readPacket(this._obfuscation) return await this._codec.readPacket(this._obfuscation);
} }
} }
class PacketCodec { class PacketCodec {
constructor(connection) { constructor(connection) {
this._conn = connection this._conn = connection;
} }
encodePacket(data) { encodePacket(data) {
throw new Error('Not Implemented') throw new Error('Not Implemented');
// Override // Override
} }
async readPacket(reader) { async readPacket(reader) {
// override // override
throw new Error('Not Implemented') throw new Error('Not Implemented');
} }
} }
@ -169,4 +169,4 @@ module.exports = {
Connection, Connection,
PacketCodec, PacketCodec,
ObfuscatedConnection, ObfuscatedConnection,
} };

View File

@ -1,38 +1,41 @@
const { readBufferFromBigInt } = require('../../Helpers') const { readBufferFromBigInt } = require('../../Helpers');
const { Connection, PacketCodec } = require('./Connection') const {
const BigInt = require('big-integer') Connection,
PacketCodec,
} = require('./Connection');
const BigInt = require('big-integer');
class AbridgedPacketCodec extends PacketCodec { class AbridgedPacketCodec extends PacketCodec {
static tag = Buffer.from('ef', 'hex') static tag = Buffer.from('ef', 'hex');
static obfuscateTag = Buffer.from('efefefef', 'hex') static obfuscateTag = Buffer.from('efefefef', 'hex');
constructor(props) { constructor(props) {
super(props) super(props);
this.tag = AbridgedPacketCodec.tag this.tag = AbridgedPacketCodec.tag;
this.obfuscateTag = AbridgedPacketCodec.obfuscateTag this.obfuscateTag = AbridgedPacketCodec.obfuscateTag;
} }
encodePacket(data) { encodePacket(data) {
let length = data.length >> 2 let length = data.length >> 2;
if (length < 127) { if (length < 127) {
const b = Buffer.alloc(1) const b = Buffer.alloc(1);
b.writeUInt8(length, 0) b.writeUInt8(length, 0);
length = b length = b;
} else { } 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) { async readPacket(reader) {
const readData = await reader.read(1) const readData = await reader.read(1);
let length = readData[0] let length = readData[0];
if (length >= 127) { if (length >= 127) {
length = Buffer.concat([await reader.read(3), Buffer.alloc(1)]) 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). * 508 bytes (127 << 2, which is very common).
*/ */
class ConnectionTCPAbridged extends Connection { class ConnectionTCPAbridged extends Connection {
PacketCodecClass = AbridgedPacketCodec PacketCodecClass = AbridgedPacketCodec;
} }
module.exports = { module.exports = {
ConnectionTCPAbridged, ConnectionTCPAbridged,
AbridgedPacketCodec, AbridgedPacketCodec,
} };

View File

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

View File

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

View File

@ -1,14 +1,14 @@
const MTProtoPlainSender = require('./MTProtoPlainSender') const MTProtoPlainSender = require('./MTProtoPlainSender');
const doAuthentication = require('./Authenticator') const doAuthentication = require('./Authenticator');
const MTProtoSender = require('./MTProtoSender') const MTProtoSender = require('./MTProtoSender');
class UpdateConnectionState { class UpdateConnectionState {
static disconnected = -1 static disconnected = -1;
static connected = 1 static connected = 1;
static broken = 0 static broken = 0;
constructor(state) { constructor(state) {
this.state = state this.state = state;
} }
} }
@ -17,7 +17,7 @@ const {
ConnectionTCPFull, ConnectionTCPFull,
ConnectionTCPAbridged, ConnectionTCPAbridged,
ConnectionTCPObfuscated, ConnectionTCPObfuscated,
} = require('./connection') } = require('./connection');
module.exports = { module.exports = {
Connection, Connection,
ConnectionTCPFull, ConnectionTCPFull,
@ -27,4 +27,4 @@ module.exports = {
doAuthentication, doAuthentication,
MTProtoSender, MTProtoSender,
UpdateConnectionState, UpdateConnectionState,
} };

View File

@ -8,11 +8,49 @@ class Session {
* @param toInstance {Session|null} * @param toInstance {Session|null}
* @returns {Session} * @returns {Session}
*/ */
/* CONTEST /* CONTEST
clone(toInstance = null) { clone(toInstance = null) {
return toInstance || new this.constructor() 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 * Sets the information of the data center address and port that
* the library should connect to, as well as the data center ID, * the library should connect to, as well as the data center ID,
@ -22,44 +60,7 @@ class Session {
* @param port {number} * @param port {number}
*/ */
setDC(dcId, serverAddress, port) { setDC(dcId, serverAddress, port) {
throw new Error('Not implemented') 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')
} }
/** /**
@ -112,6 +113,7 @@ class Session {
* Called on client disconnection. Should be used to * Called on client disconnection. Should be used to
* free any used resources. Can be left empty if none. * free any used resources. Can be left empty if none.
*/ */
/*CONTEST /*CONTEST
close() { close() {
@ -124,7 +126,7 @@ class Session {
* make persist the relevant session information to disk. * make persist the relevant session information to disk.
*/ */
save() { save() {
throw new Error('Not Implemented') throw new Error('Not Implemented');
} }
/** /**
@ -133,7 +135,7 @@ class Session {
*/ */
delete() { delete() {
throw new Error('Not Implemented') throw new Error('Not Implemented');
} }
/** /**
@ -172,4 +174,4 @@ class Session {
*/ */
} }
module.exports = Session module.exports = Session;

View File

@ -1,27 +1,27 @@
const StorageSession = require('./StorageSession') const StorageSession = require('./StorageSession');
const CACHE_NAME = 'GramJs' const CACHE_NAME = 'GramJs';
class CacheApiSession extends StorageSession { class CacheApiSession extends StorageSession {
async _delete() { async _delete() {
const request = new Request(this._storageKey) const request = new Request(this._storageKey);
const cache = await self.caches.open(CACHE_NAME) const cache = await self.caches.open(CACHE_NAME);
return cache.delete(request) return cache.delete(request);
} }
async _fetchFromCache() { async _fetchFromCache() {
const request = new Request(this._storageKey) const request = new Request(this._storageKey);
const cache = await self.caches.open(CACHE_NAME) const cache = await self.caches.open(CACHE_NAME);
const cached = await cache.match(request) const cached = await cache.match(request);
return cached ? cached.text() : null return cached ? cached.text() : null;
} }
async _saveToCache(data) { async _saveToCache(data) {
const request = new Request(this._storageKey) const request = new Request(this._storageKey);
const response = new Response(data) const response = new Response(data);
const cache = await self.caches.open(CACHE_NAME) const cache = await self.caches.open(CACHE_NAME);
return cache.put(request, response) return cache.put(request, response);
} }
} }
module.exports = CacheApiSession module.exports = CacheApiSession;

View File

@ -1,20 +1,20 @@
const StorageSession = require('./StorageSession') const StorageSession = require('./StorageSession');
const idb = require('idb-keyval') const idb = require('idb-keyval');
const CACHE_NAME = 'GramJs' const CACHE_NAME = 'GramJs';
class IdbSession extends StorageSession { class IdbSession extends StorageSession {
async _delete() { async _delete() {
return idb.del(`${CACHE_NAME}:${this._storageKey}`) return idb.del(`${CACHE_NAME}:${this._storageKey}`);
} }
async _fetchFromCache() { async _fetchFromCache() {
return idb.get(`${CACHE_NAME}:${this._storageKey}`) return idb.get(`${CACHE_NAME}:${this._storageKey}`);
} }
async _saveToCache(data) { 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;

View File

@ -1,17 +1,17 @@
const StorageSession = require('./StorageSession') const StorageSession = require('./StorageSession');
class LocalStorageSession extends StorageSession { class LocalStorageSession extends StorageSession {
async _delete() { async _delete() {
return localStorage.removeItem(this._storageKey) return localStorage.removeItem(this._storageKey);
} }
async _fetchFromCache(key) { async _fetchFromCache(key) {
return localStorage.getItem(this._storageKey) return localStorage.getItem(this._storageKey);
} }
async _saveToCache(key, data) { async _saveToCache(key, data) {
return localStorage.setItem(this._storageKey, data) return localStorage.setItem(this._storageKey, data);
} }
} }
module.exports = LocalStorageSession module.exports = LocalStorageSession;

View File

@ -1,45 +1,46 @@
const utils = require('../Utils') const utils = require('../Utils');
const types = require('../tl').constructors const types = require('../tl').constructors;
const Session = require('./Abstract') const Session = require('./Abstract');
class MemorySession extends Session { class MemorySession extends Session {
constructor() { constructor() {
super() super();
this._serverAddress = null this._serverAddress = null;
this._dcId = 0 this._dcId = 0;
this._port = null this._port = null;
this._takeoutId = null this._takeoutId = null;
this._entities = new Set() this._entities = new Set();
this._updateStates = {} this._updateStates = {};
}
setDC(dcId, serverAddress, port) {
this._dcId = dcId | 0
this._serverAddress = serverAddress
this._port = port
} }
get dcId() { get dcId() {
return this._dcId return this._dcId;
} }
get serverAddress() { get serverAddress() {
return this._serverAddress return this._serverAddress;
} }
get port() { get port() {
return this._port return this._port;
} }
get authKey() { get authKey() {
return this._authKey return this._authKey;
} }
set authKey(value) { set authKey(value) {
this._authKey = value this._authKey = value;
} }
setDC(dcId, serverAddress, port) {
this._dcId = dcId | 0;
this._serverAddress = serverAddress;
this._port = port;
}
/* CONTEST /* CONTEST
get takeoutId() { get takeoutId() {
return this._takeoutId return this._takeoutId
@ -253,4 +254,4 @@ class MemorySession extends Session {
}*/ }*/
} }
module.exports = MemorySession module.exports = MemorySession;

View File

@ -1,135 +1,144 @@
const MemorySession = require('./Memory') const MemorySession = require('./Memory');
const AuthKey = require('../crypto/AuthKey') const AuthKey = require('../crypto/AuthKey');
const utils = require('../Utils') const utils = require('../Utils');
const STORAGE_KEY_BASE = 'GramJs-session-' const STORAGE_KEY_BASE = 'GramJs-session-';
class StorageSession extends MemorySession { class StorageSession extends MemorySession {
constructor(sessionId) { constructor(sessionId) {
super() super();
this._storageKey = sessionId this._storageKey = sessionId;
this._authKeys = {} this._authKeys = {};
}
get authKey() {
throw new Error('Not supported');
}
set authKey(value) {
throw new Error('Not supported');
} }
async load() { async load() {
if (!this._storageKey) { if (!this._storageKey) {
return return;
} }
try { try {
const json = await this._fetchFromCache() const json = await this._fetchFromCache();
const { mainDcId, keys, hashes } = JSON.parse(json) const {
const { ipAddress, port } = utils.getDC(mainDcId) 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) => { Object.keys(keys)
if (keys[dcId] && hashes[dcId]){ .forEach((dcId) => {
this._authKeys[dcId] = new AuthKey( if (keys[dcId] && hashes[dcId]) {
Buffer.from(keys[dcId].data), this._authKeys[dcId] = new AuthKey(
Buffer.from(hashes[dcId].data) Buffer.from(keys[dcId].data),
) Buffer.from(hashes[dcId].data),
} );
}) }
});
} catch (err) { } catch (err) {
console.warn('Failed to retrieve or parse session from storage') console.warn('Failed to retrieve or parse session from storage');
console.warn(err) console.warn(err);
} }
} }
setDC(dcId, serverAddress, port, skipUpdateStorage = false) { setDC(dcId, serverAddress, port, skipUpdateStorage = false) {
this._dcId = dcId this._dcId = dcId;
this._serverAddress = serverAddress this._serverAddress = serverAddress;
this._port = port this._port = port;
delete this._authKeys[dcId] delete this._authKeys[dcId];
if (!skipUpdateStorage) { if (!skipUpdateStorage) {
void this._updateStorage() void this._updateStorage();
} }
} }
async save() { async save() {
if (!this._storageKey) { if (!this._storageKey) {
this._storageKey = generateStorageKey() this._storageKey = generateStorageKey();
} }
await this._updateStorage() await this._updateStorage();
return this._storageKey return this._storageKey;
}
get authKey() {
throw new Error('Not supported')
}
set authKey(value) {
throw new Error('Not supported')
} }
getAuthKey(dcId = this._dcId) { getAuthKey(dcId = this._dcId) {
return this._authKeys[dcId] return this._authKeys[dcId];
} }
setAuthKey(authKey, dcId = this._dcId) { setAuthKey(authKey, dcId = this._dcId) {
this._authKeys[dcId] = authKey this._authKeys[dcId] = authKey;
void this._updateStorage() void this._updateStorage();
} }
async _updateStorage() { async _updateStorage() {
if (!this._storageKey) { if (!this._storageKey) {
return return;
} }
const sessionData = { const sessionData = {
mainDcId: this._dcId, mainDcId: this._dcId,
keys: {}, keys: {},
hashes: {} hashes: {},
} };
Object.keys(this._authKeys).map((dcId) => { Object.keys(this._authKeys)
const authKey = this._authKeys[dcId] .map((dcId) => {
sessionData.keys[dcId] = authKey._key const authKey = this._authKeys[dcId];
sessionData.hashes[dcId] = authKey._hash sessionData.keys[dcId] = authKey._key;
}) sessionData.hashes[dcId] = authKey._hash;
});
try { try {
await this._saveToCache(JSON.stringify(sessionData)) await this._saveToCache(JSON.stringify(sessionData));
} catch (err) { } catch (err) {
console.warn('Failed to update session in storage') console.warn('Failed to update session in storage');
console.warn(err) console.warn(err);
} }
} }
async delete() { async delete() {
try { try {
return await this._delete() return await this._delete();
} catch (err) { } catch (err) {
console.warn('Failed to delete session from storage') console.warn('Failed to delete session from storage');
console.warn(err) console.warn(err);
} }
} }
// @abstract // @abstract
async _delete() { async _delete() {
throw new Error('Not Implemented') throw new Error('Not Implemented');
} }
// @abstract // @abstract
async _fetchFromCache() { async _fetchFromCache() {
throw new Error('Not Implemented') throw new Error('Not Implemented');
} }
// @abstract // @abstract
async _saveToCache(data) { async _saveToCache(data) {
throw new Error('Not Implemented') throw new Error('Not Implemented');
} }
} }
function generateStorageKey() { function generateStorageKey() {
// Creating two sessions at the same moment is not expected nor supported. // 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;

View File

@ -1,7 +1,7 @@
const MemorySession = require('./Memory') const MemorySession = require('./Memory');
const AuthKey = require('../crypto/AuthKey') const AuthKey = require('../crypto/AuthKey');
const BinaryReader = require('../extensions/BinaryReader') const BinaryReader = require('../extensions/BinaryReader');
const CURRENT_VERSION = '1' const CURRENT_VERSION = '1';
class StringSession extends MemorySession { class StringSession extends MemorySession {
@ -21,22 +21,22 @@ class StringSession extends MemorySession {
* @param session {string|null} * @param session {string|null}
*/ */
constructor(session = null) { constructor(session = null) {
super() super();
if (session) { if (session) {
if (session[0] !== CURRENT_VERSION) { if (session[0] !== CURRENT_VERSION) {
throw new Error('Not a valid string') throw new Error('Not a valid string');
} }
session = session.slice(1) session = session.slice(1);
const r = StringSession.decode(session) const r = StringSession.decode(session);
const reader = new BinaryReader(r) const reader = new BinaryReader(r);
this._dcId = reader.read(1) this._dcId = reader.read(1)
.readUInt8(0) .readUInt8(0);
const serverAddressLen = reader.read(2) const serverAddressLen = reader.read(2)
.readInt16BE(0) .readInt16BE(0);
this._serverAddress = String(reader.read(serverAddressLen)) this._serverAddress = String(reader.read(serverAddressLen));
this._port = reader.read(2) this._port = reader.read(2)
.readInt16BE(0) .readInt16BE(0);
this._key = reader.read(-1) this._key = reader.read(-1);
} }
} }
@ -45,7 +45,7 @@ class StringSession extends MemorySession {
* @returns {string} * @returns {string}
*/ */
static encode(x) { static encode(x) {
return x.toString('base64') return x.toString('base64');
} }
/** /**
@ -53,26 +53,26 @@ class StringSession extends MemorySession {
* @returns {Buffer} * @returns {Buffer}
*/ */
static decode(x) { static decode(x) {
return Buffer.from(x, 'base64') return Buffer.from(x, 'base64');
} }
async load() { async load() {
if (this._key) { if (this._key) {
this._authKey = new AuthKey() this._authKey = new AuthKey();
await this._authKey.setKey(this._key) await this._authKey.setKey(this._key);
} }
} }
save() { save() {
if (!this.authKey) { if (!this.authKey) {
return '' return '';
} }
const dcBuffer = Buffer.from([this.dcId]) const dcBuffer = Buffer.from([this.dcId]);
const addressBuffer = Buffer.from(this.serverAddress) const addressBuffer = Buffer.from(this.serverAddress);
const addressLengthBuffer = Buffer.alloc(2) const addressLengthBuffer = Buffer.alloc(2);
addressLengthBuffer.writeInt16BE(addressBuffer.length, 0) addressLengthBuffer.writeInt16BE(addressBuffer.length, 0);
const portBuffer = Buffer.alloc(2) const portBuffer = Buffer.alloc(2);
portBuffer.writeInt16BE(this.port, 0) portBuffer.writeInt16BE(this.port, 0);
return CURRENT_VERSION + StringSession.encode(Buffer.concat([ return CURRENT_VERSION + StringSession.encode(Buffer.concat([
dcBuffer, dcBuffer,
@ -80,26 +80,26 @@ class StringSession extends MemorySession {
addressBuffer, addressBuffer,
portBuffer, portBuffer,
this.authKey.getKey(), this.authKey.getKey(),
])) ]));
} }
getAuthKey(dcId) { getAuthKey(dcId) {
if (dcId && dcId !== this.dcId) { if (dcId && dcId !== this.dcId) {
// Not supported. // Not supported.
return undefined return undefined;
} }
return this.authKey return this.authKey;
} }
setAuthKey(authKey, dcId) { setAuthKey(authKey, dcId) {
if (dcId && dcId !== this.dcId) { if (dcId && dcId !== this.dcId) {
// Not supported. // Not supported.
return undefined return undefined;
} }
this.authKey = authKey this.authKey = authKey;
} }
} }
module.exports = StringSession module.exports = StringSession;

View File

@ -1,8 +1,8 @@
const Memory = require('./Memory') const Memory = require('./Memory');
const StringSession = require('./StringSession') const StringSession = require('./StringSession');
const CacheApiSession = require('./CacheApiSession') const CacheApiSession = require('./CacheApiSession');
const LocalStorageSession = require('./LocalStorageSession') const LocalStorageSession = require('./LocalStorageSession');
const IdbSession = require('./IdbSession') const IdbSession = require('./IdbSession');
module.exports = { module.exports = {
Memory, Memory,
@ -10,4 +10,4 @@ module.exports = {
CacheApiSession, CacheApiSession,
LocalStorageSession, LocalStorageSession,
IdbSession, IdbSession,
} };

View File

@ -1,19 +1,19 @@
const api = require('./api') const api = require('./api');
const LAYER = 121 const LAYER = 121;
const tlobjects = {} const tlobjects = {};
for (const tl of Object.values(api)) { for (const tl of Object.values(api)) {
if (tl.CONSTRUCTOR_ID) { if (tl.CONSTRUCTOR_ID) {
tlobjects[tl.CONSTRUCTOR_ID] = tl tlobjects[tl.CONSTRUCTOR_ID] = tl;
} else { } else {
for (const sub of Object.values(tl)) { for (const sub of Object.values(tl)) {
tlobjects[sub.CONSTRUCTOR_ID] = sub tlobjects[sub.CONSTRUCTOR_ID] = sub;
} }
} }
} }
module.exports = { module.exports = {
LAYER, LAYER,
tlobjects tlobjects,
} };

View File

@ -1,42 +1,44 @@
class MTProtoRequest { class MTProtoRequest {
constructor() { constructor() {
this.sent = false this.sent = false;
this.msgId = 0 // long this.msgId = 0; // long
this.sequence = 0 this.sequence = 0;
this.dirty = false this.dirty = false;
this.sendTime = 0 this.sendTime = 0;
this.confirmReceived = false this.confirmReceived = false;
// These should be overrode // These should be overrode
this.constructorId = 0 this.constructorId = 0;
this.confirmed = false this.confirmed = false;
this.responded = false this.responded = false;
} }
// these should not be overrode // these should not be overrode
onSendSuccess() { onSendSuccess() {
this.sendTime = new Date().getTime() this.sendTime = new Date().getTime();
this.sent = true this.sent = true;
} }
onConfirm() { onConfirm() {
this.confirmReceived = true this.confirmReceived = true;
} }
needResend() { 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 // These should be overrode
onSend() { 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;

View File

@ -1,12 +1,15 @@
const { const {
parseTl, parseTl,
serializeBytes, serializeBytes,
serializeDate serializeDate,
} = require('./generationHelpers') } = require('./generationHelpers');
const { readBufferFromBigInt,toSignedLittleBuffer } = require('../Helpers') const {
readBufferFromBigInt,
toSignedLittleBuffer,
} = require('../Helpers');
const tlContent = require('./apiTl.js') const tlContent = require('./apiTl.js');
const schemeContent = require('./schemaTl.js') const schemeContent = require('./schemaTl.js');
/*CONTEST /*CONTEST
const NAMED_AUTO_CASTS = new Set([ 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() { function buildApiFromTlSchema() {
let definitions; let definitions;
const fromCache = CACHING_SUPPORTED && loadFromCache() const fromCache = CACHING_SUPPORTED && loadFromCache();
if (fromCache) { if (fromCache) {
definitions = fromCache definitions = fromCache;
} else { } else {
definitions = loadFromTlSchemas() definitions = loadFromTlSchemas();
if (CACHING_SUPPORTED) { if (CACHING_SUPPORTED) {
localStorage.setItem(CACHE_KEY, JSON.stringify(definitions)) localStorage.setItem(CACHE_KEY, JSON.stringify(definitions));
} }
} }
return mergeWithNamespaces( return mergeWithNamespaces(
createClasses('constructor', definitions.constructors), createClasses('constructor', definitions.constructors),
createClasses('request', definitions.requests) createClasses('request', definitions.requests),
) );
} }
function loadFromCache() { function loadFromCache() {
const jsonCache = localStorage.getItem(CACHE_KEY) const jsonCache = localStorage.getItem(CACHE_KEY);
return jsonCache && JSON.parse(jsonCache) return jsonCache && JSON.parse(jsonCache);
} }
function loadFromTlSchemas() { function loadFromTlSchemas() {
const [constructorParamsApi, functionParamsApi] = extractParams(tlContent) const [constructorParamsApi, functionParamsApi] = extractParams(tlContent);
const [constructorParamsSchema, functionParamsSchema] = extractParams(schemeContent) const [constructorParamsSchema, functionParamsSchema] = extractParams(schemeContent);
const constructors = [].concat(constructorParamsApi, constructorParamsSchema) const constructors = [].concat(constructorParamsApi, constructorParamsSchema);
const requests = [].concat(functionParamsApi, functionParamsSchema) const requests = [].concat(functionParamsApi, functionParamsSchema);
return { constructors, requests } return {
constructors,
requests,
};
} }
function mergeWithNamespaces(obj1, obj2) { function mergeWithNamespaces(obj1, obj2) {
const result = { ...obj1 } const result = { ...obj1 };
Object.keys(obj2).forEach((key) => { Object.keys(obj2)
if (typeof obj2[key] === 'function' || !result[key]) { .forEach((key) => {
result[key] = obj2[key] if (typeof obj2[key] === 'function' || !result[key]) {
} else { result[key] = obj2[key];
Object.assign(result[key], obj2[key]) } else {
} Object.assign(result[key], obj2[key]);
}) }
});
return result return result;
} }
function extractParams(fileContent) { function extractParams(fileContent) {
const f = parseTl(fileContent, 109) const f = parseTl(fileContent, 109);
const constructors = [] const constructors = [];
const functions = [] const functions = [];
for (const d of f) { 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) { function argToBytes(x, type) {
switch (type) { switch (type) {
case 'int': case 'int':
const i = Buffer.alloc(4) const i = Buffer.alloc(4);
i.writeInt32LE(x, 0) i.writeInt32LE(x, 0);
return i return i;
case 'long': case 'long':
return toSignedLittleBuffer(x, 8) return toSignedLittleBuffer(x, 8);
case 'int128': case 'int128':
return toSignedLittleBuffer(x, 16) return toSignedLittleBuffer(x, 16);
case 'int256': case 'int256':
return toSignedLittleBuffer(x, 32) return toSignedLittleBuffer(x, 32);
case 'double': case 'double':
const d = Buffer.alloc(8) const d = Buffer.alloc(8);
d.writeDoubleLE(x, 0) d.writeDoubleLE(x, 0);
return d return d;
case 'string': case 'string':
return serializeBytes(x) return serializeBytes(x);
case 'Bool': case 'Bool':
return x ? Buffer.from('b5757299', 'hex') : Buffer.from('379779bc', 'hex') return x ? Buffer.from('b5757299', 'hex') : Buffer.from('379779bc', 'hex');
case 'true': case 'true':
return Buffer.alloc(0) return Buffer.alloc(0);
case 'bytes': case 'bytes':
return serializeBytes(x) return serializeBytes(x);
case 'date': case 'date':
return serializeDate(x) return serializeDate(x);
default: default:
return x.getBytes() return x.getBytes();
} }
} }
/* /*
CONTEST CONTEST
async function getInputFromResolve(utils, client, peer, peerType) { async function getInputFromResolve(utils, client, peer, peerType) {
@ -155,181 +163,189 @@ async function getInputFromResolve(utils, client, peer, peerType) {
function getArgFromReader(reader, arg) { function getArgFromReader(reader, arg) {
if (arg.isVector) { if (arg.isVector) {
if (arg.useVectorId) { if (arg.useVectorId) {
reader.readInt() reader.readInt();
} }
const temp = [] const temp = [];
const len = reader.readInt() const len = reader.readInt();
arg.isVector = false arg.isVector = false;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
temp.push(getArgFromReader(reader, arg)) temp.push(getArgFromReader(reader, arg));
} }
arg.isVector = true arg.isVector = true;
return temp return temp;
} else if (arg.flagIndicator) { } else if (arg.flagIndicator) {
return reader.readInt() return reader.readInt();
} else { } else {
switch (arg.type) { switch (arg.type) {
case 'int': case 'int':
return reader.readInt() return reader.readInt();
case 'long': case 'long':
return reader.readLong() return reader.readLong();
case 'int128': case 'int128':
return reader.readLargeInt(128) return reader.readLargeInt(128);
case 'int256': case 'int256':
return reader.readLargeInt(256) return reader.readLargeInt(256);
case 'double': case 'double':
return reader.readDouble() return reader.readDouble();
case 'string': case 'string':
return reader.tgReadString() return reader.tgReadString();
case 'Bool': case 'Bool':
return reader.tgReadBool() return reader.tgReadBool();
case 'true': case 'true':
return true return true;
case 'bytes': case 'bytes':
return reader.tgReadBytes() return reader.tgReadBytes();
case 'date': case 'date':
return reader.tgReadDate() return reader.tgReadDate();
default: default:
if (!arg.skipConstructorId) { if (!arg.skipConstructorId) {
return reader.tgReadObject() return reader.tgReadObject();
} else { } else {
return api.constructors[arg.type].fromReader(reader) return api.constructors[arg.type].fromReader(reader);
} }
} }
} }
} }
function createClasses(classesType, params) { function createClasses(classesType, params) {
const classes = {} const classes = {};
for (const classParams of params) { for (const classParams of params) {
const { name, constructorId, subclassOfId, argsConfig, namespace, result } = classParams const {
const fullName = [namespace, name].join('.').replace(/^\./, '') name,
constructorId,
subclassOfId,
argsConfig,
namespace,
result,
} = classParams;
const fullName = [namespace, name].join('.')
.replace(/^\./, '');
class VirtualClass { class VirtualClass {
static CONSTRUCTOR_ID = constructorId static CONSTRUCTOR_ID = constructorId;
static SUBCLASS_OF_ID = subclassOfId static SUBCLASS_OF_ID = subclassOfId;
static className = fullName static className = fullName;
static classType = classesType static classType = classesType;
CONSTRUCTOR_ID = constructorId CONSTRUCTOR_ID = constructorId;
SUBCLASS_OF_ID = subclassOfId SUBCLASS_OF_ID = subclassOfId;
className = fullName className = fullName;
classType = classesType classType = classesType;
constructor(args) { constructor(args) {
args = args || {} args = args || {};
Object.keys(args) Object.keys(args)
.forEach((argName) => { .forEach((argName) => {
this[argName] = args[argName] this[argName] = args[argName];
}) });
} }
static fromReader(reader) { static fromReader(reader) {
const args = {} const args = {};
for (const argName in argsConfig) { for (const argName in argsConfig) {
if (argsConfig.hasOwnProperty(argName)) { if (argsConfig.hasOwnProperty(argName)) {
const arg = argsConfig[argName] const arg = argsConfig[argName];
if (arg.isFlag) { if (arg.isFlag) {
if (arg.type === 'true') { if (arg.type === 'true') {
args[argName] = Boolean(args['flags'] & 1 << arg.flagIndex) args[argName] = Boolean(args['flags'] & 1 << arg.flagIndex);
continue continue;
} }
if (args['flags'] & 1 << arg.flagIndex) { if (args['flags'] & 1 << arg.flagIndex) {
args[argName] = getArgFromReader(reader, arg) args[argName] = getArgFromReader(reader, arg);
} else { } else {
args[argName] = null args[argName] = null;
} }
} else { } else {
if (arg.flagIndicator) { 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() { getBytes() {
// The next is pseudo-code: // The next is pseudo-code:
const idForBytes = this.CONSTRUCTOR_ID const idForBytes = this.CONSTRUCTOR_ID;
const c = Buffer.alloc(4) const c = Buffer.alloc(4);
c.writeUInt32LE(idForBytes, 0) c.writeUInt32LE(idForBytes, 0);
const buffers = [c] const buffers = [c];
for (const arg in argsConfig) { for (const arg in argsConfig) {
if (argsConfig.hasOwnProperty(arg)) { if (argsConfig.hasOwnProperty(arg)) {
if (argsConfig[arg].isFlag) { if (argsConfig[arg].isFlag) {
if ((this[arg]===false&&argsConfig[arg].type==="true") || this[arg]===null || this[arg]===undefined){ if ((this[arg] === false && argsConfig[arg].type === 'true') || this[arg] === null || this[arg] === undefined) {
continue continue;
} }
} }
if (argsConfig[arg].isVector) { if (argsConfig[arg].isVector) {
if (argsConfig[arg].useVectorId) { if (argsConfig[arg].useVectorId) {
buffers.push(Buffer.from('15c4b51c', 'hex')) buffers.push(Buffer.from('15c4b51c', 'hex'));
} }
const l = Buffer.alloc(4) const l = Buffer.alloc(4);
l.writeInt32LE(this[arg].length, 0) l.writeInt32LE(this[arg].length, 0);
buffers.push(l, Buffer.concat(this[arg].map(x => argToBytes(x, argsConfig[arg].type)))) buffers.push(l, Buffer.concat(this[arg].map(x => argToBytes(x, argsConfig[arg].type))));
} else if (argsConfig[arg].flagIndicator) { } else if (argsConfig[arg].flagIndicator) {
if (!Object.values(argsConfig) if (!Object.values(argsConfig)
.some((f) => f.isFlag)) { .some((f) => f.isFlag)) {
buffers.push(Buffer.alloc(4)) buffers.push(Buffer.alloc(4));
} else { } else {
let flagCalculate = 0 let flagCalculate = 0;
for (const f in argsConfig) { for (const f in argsConfig) {
if (argsConfig[f].isFlag) { if (argsConfig[f].isFlag) {
if ((this[f]===false&&argsConfig[f].type==="true") || this[f]===undefined || this[f]===null) { if ((this[f] === false && argsConfig[f].type === 'true') || this[f] === undefined || this[f] === null) {
flagCalculate |= 0 flagCalculate |= 0;
} else { } else {
flagCalculate |= 1 << argsConfig[f].flagIndex flagCalculate |= 1 << argsConfig[f].flagIndex;
} }
} }
} }
const f = Buffer.alloc(4) const f = Buffer.alloc(4);
f.writeUInt32LE(flagCalculate, 0) f.writeUInt32LE(flagCalculate, 0);
buffers.push(f) buffers.push(f);
} }
} else { } 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') { if (this[arg] && typeof this[arg].getBytes === 'function') {
let boxed = (argsConfig[arg].type.charAt(argsConfig[arg].type.indexOf('.') + 1)) let boxed = (argsConfig[arg].type.charAt(argsConfig[arg].type.indexOf('.') + 1));
boxed = boxed === boxed.toUpperCase() boxed = boxed === boxed.toUpperCase();
if (!boxed) { if (!boxed) {
buffers.shift() buffers.shift();
} }
} }
} }
} }
} }
return Buffer.concat(buffers) return Buffer.concat(buffers);
} }
readResult(reader) { readResult(reader) {
if (classesType !== 'request') { 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) { if (m) {
reader.readInt() reader.readInt();
let temp = [] let temp = [];
let len = reader.readInt() let len = reader.readInt();
if (m[1] === 'int') { if (m[1] === 'int') {
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
temp.push(reader.readInt()) temp.push(reader.readInt());
} }
} else { } else {
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
temp.push(reader.readLong()) temp.push(reader.readLong());
} }
} }
return temp return temp;
} else { } else {
return reader.tgReadObject() return reader.tgReadObject();
} }
} }
@ -368,16 +384,16 @@ function createClasses(classesType, params) {
if (namespace) { if (namespace) {
if (!classes[namespace]) { if (!classes[namespace]) {
classes[namespace] = {} classes[namespace] = {};
} }
classes[namespace][name] = VirtualClass classes[namespace][name] = VirtualClass;
} else { } else {
classes[name] = VirtualClass classes[name] = VirtualClass;
} }
} }
return classes return classes;
} }
module.exports = buildApiFromTlSchema() module.exports = buildApiFromTlSchema();

View File

@ -1,57 +1,58 @@
const { serializeBytes } = require('../index') const { serializeBytes } = require('../index');
const { inflate } = require('pako/dist/pako_inflate') const { inflate } = require('pako/dist/pako_inflate');
//CONTEST const { deflate } = require('pako/dist/pako_deflate') //CONTEST const { deflate } = require('pako/dist/pako_deflate')
class GZIPPacked { class GZIPPacked {
static CONSTRUCTOR_ID = 0x3072cfa1 static CONSTRUCTOR_ID = 0x3072cfa1;
static classType = 'constructor' static classType = 'constructor';
constructor(data) { constructor(data) {
this.data = data this.data = data;
this.CONSTRUCTOR_ID = 0x3072cfa1 this.CONSTRUCTOR_ID = 0x3072cfa1;
this.classType = 'constructor' this.classType = 'constructor';
} }
static async gzipIfSmaller(contentRelated, data) { static async gzipIfSmaller(contentRelated, data) {
if (contentRelated && data.length > 512) { if (contentRelated && data.length > 512) {
const gzipped = await (new GZIPPacked(data)).toBytes() const gzipped = await (new GZIPPacked(data)).toBytes();
if (gzipped.length < data.length) { if (gzipped.length < data.length) {
return gzipped return gzipped;
} }
} }
return data return data;
} }
static gzip(input) { static gzip(input) {
return Buffer.from(input) return Buffer.from(input);
// TODO this usually makes it faster for large requests // TODO this usually makes it faster for large requests
//return Buffer.from(deflate(input, { level: 9, gzip: true })) //return Buffer.from(deflate(input, { level: 9, gzip: true }))
} }
static ungzip(input) { static ungzip(input) {
return Buffer.from(inflate(input)) return Buffer.from(inflate(input));
}
async toBytes() {
const g = Buffer.alloc(4)
g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0)
return Buffer.concat([
g,
serializeBytes(await GZIPPacked.gzip(this.data)),
])
} }
static async read(reader) { static async read(reader) {
const constructor = reader.readInt(false) const constructor = reader.readInt(false);
if (constructor !== GZIPPacked.CONSTRUCTOR_ID) { 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) { 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;

View File

@ -1,8 +1,8 @@
const TLMessage = require('./TLMessage') const TLMessage = require('./TLMessage');
class MessageContainer { class MessageContainer {
static CONSTRUCTOR_ID = 0x73f1f8dc; static CONSTRUCTOR_ID = 0x73f1f8dc;
static classType = "constructor" static classType = 'constructor';
// Maximum size in bytes for the inner payload of the container. // Maximum size in bytes for the inner payload of the container.
// Telegram will close the connection if the payload is bigger. // Telegram will close the connection if the payload is bigger.
// The overhead of the container itself is subtracted. // The overhead of the container itself is subtracted.
@ -20,26 +20,26 @@ class MessageContainer {
constructor(messages) { constructor(messages) {
this.CONSTRUCTOR_ID = 0x73f1f8dc this.CONSTRUCTOR_ID = 0x73f1f8dc;
this.messages = messages this.messages = messages;
this.classType = "constructor" this.classType = 'constructor';
} }
static async fromReader(reader) { static async fromReader(reader) {
const messages = [] const messages = [];
const length = reader.readInt() const length = reader.readInt();
for (let x = 0; x < length; x++) { for (let x = 0; x < length; x++) {
const msgId = reader.readLong() const msgId = reader.readLong();
const seqNo = reader.readInt() const seqNo = reader.readInt();
const length = reader.readInt() const length = reader.readInt();
const before = reader.tellPosition() const before = reader.tellPosition();
const obj = reader.tgReadObject() const obj = reader.tgReadObject();
reader.setPosition(before + length) reader.setPosition(before + length);
const tlMessage = new TLMessage(msgId, seqNo, obj) const tlMessage = new TLMessage(msgId, seqNo, obj);
messages.push(tlMessage) messages.push(tlMessage);
} }
return new MessageContainer(messages) return new MessageContainer(messages);
} }
} }
module.exports = MessageContainer module.exports = MessageContainer;

View File

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

View File

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

View File

@ -1,12 +1,12 @@
const TLMessage = require('./TLMessage') const TLMessage = require('./TLMessage');
const RPCResult = require('./RPCResult') const RPCResult = require('./RPCResult');
const MessageContainer = require('./MessageContainer') const MessageContainer = require('./MessageContainer');
const GZIPPacked = require('./GZIPPacked') const GZIPPacked = require('./GZIPPacked');
const coreObjects = { const coreObjects = {
[RPCResult.CONSTRUCTOR_ID]: RPCResult, [RPCResult.CONSTRUCTOR_ID]: RPCResult,
[GZIPPacked.CONSTRUCTOR_ID]: GZIPPacked, [GZIPPacked.CONSTRUCTOR_ID]: GZIPPacked,
[MessageContainer.CONSTRUCTOR_ID]: MessageContainer, [MessageContainer.CONSTRUCTOR_ID]: MessageContainer,
} };
module.exports = { module.exports = {
TLMessage, TLMessage,
@ -14,4 +14,4 @@ module.exports = {
MessageContainer, MessageContainer,
GZIPPacked, GZIPPacked,
coreObjects, coreObjects,
} };

View File

@ -1,21 +1,21 @@
const snakeToCamelCase = (name) => { const snakeToCamelCase = (name) => {
const result = name.replace(/(?:^|_)([a-z])/g, (_, g) => g.toUpperCase()) const result = name.replace(/(?:^|_)([a-z])/g, (_, g) => g.toUpperCase());
return result.replace(/_/g, '') return result.replace(/_/g, '');
} };
const variableSnakeToCamelCase = (str) => str.replace( const variableSnakeToCamelCase = (str) => str.replace(
/([-_][a-z])/g, /([-_][a-z])/g,
(group) => group.toUpperCase() (group) => group.toUpperCase()
.replace('-', '') .replace('-', '')
.replace('_', '') .replace('_', ''),
) );
const CORE_TYPES = new Set([ const CORE_TYPES = new Set([
0xbc799737, // boolFalse#bc799737 = Bool; 0xbc799737, // boolFalse#bc799737 = Bool;
0x997275b5, // boolTrue#997275b5 = Bool; 0x997275b5, // boolTrue#997275b5 = Bool;
0x3fedd339, // true#3fedd339 = True; 0x3fedd339, // true#3fedd339 = True;
0xc4b9f9bb, // error#c4b9f9bb code:int text:string = Error; 0xc4b9f9bb, // error#c4b9f9bb code:int text:string = Error;
0x56730bcc // null#56730bcc = Null; 0x56730bcc, // null#56730bcc = Null;
]) ]);
const AUTH_KEY_TYPES = new Set([ const AUTH_KEY_TYPES = new Set([
0x05162463, // resPQ, 0x05162463, // resPQ,
0x83c95aec, // p_q_inner_data 0x83c95aec, // p_q_inner_data
@ -27,49 +27,49 @@ const AUTH_KEY_TYPES = new Set([
0x6643b654, // client_DH_inner_data 0x6643b654, // client_DH_inner_data
0xd712e4be, // req_DH_params 0xd712e4be, // req_DH_params
0xf5045f1f, // set_client_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 // This is copy-pasted from `gramjs/Helpers.js` to not depend on TypeScript modules
function makeCRCTable() { function makeCRCTable() {
let c let c;
const crcTable = [] const crcTable = [];
for (let n = 0; n < 256; n++) { for (let n = 0; n < 256; n++) {
c = n c = n;
for (let k = 0; k < 8; k++) { 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) { function crc32(buf) {
if (!crcTable) { if (!crcTable) {
crcTable = makeCRCTable() crcTable = makeCRCTable();
} }
if (!Buffer.isBuffer(buf)) { 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++) { for (let index = 0; index < buf.length; index++) {
const byte = buf[index] const byte = buf[index];
crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8) crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
} }
return (crc ^ (-1)) >>> 0 return (crc ^ (-1)) >>> 0;
} }
const fromLine = (line, isFunction) => { 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) { if (!match) {
// Probably "vector#1cb5c415 {t:Type} # [ t ] = Vector t;" // 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 = { const currentConfig = {
name: match[1], name: match[1],
constructorId: parseInt(match[2], 16), constructorId: parseInt(match[2], 16),
@ -77,24 +77,26 @@ const fromLine = (line, isFunction) => {
subclassOfId: crc32(match[3]), subclassOfId: crc32(match[3]),
result: match[3], result: match[3],
isFunction: isFunction, isFunction: isFunction,
namespace: null namespace: null,
} };
if (!currentConfig.constructorId) { if (!currentConfig.constructorId) {
let hexId = '' let hexId = '';
let args let args;
if (Object.values(currentConfig.argsConfig).length) { 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 { } else {
args = '' args = '';
} }
const representation = `${currentConfig.name}${hexId}${args} = ${currentConfig.result}` const representation = `${currentConfig.name}${hexId}${args} = ${currentConfig.result}`
.replace(/(:|\?)bytes /g, '$1string ') .replace(/(:|\?)bytes /g, '$1string ')
.replace(/</g, ' ') .replace(/</g, ' ')
.replace(/>|{|}/g, '') .replace(/>|{|}/g, '')
.replace(/ \w+:flags\.\d+\?true/g, '') .replace(/ \w+:flags\.\d+\?true/g, '');
if (currentConfig.name === 'inputMediaInvoice') { if (currentConfig.name === 'inputMediaInvoice') {
// eslint-disable-next-line no-empty // 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) { for (const [brace, name, argType] of argsMatch) {
if (brace === undefined) { if (brace === undefined) {
currentConfig.argsConfig[variableSnakeToCamelCase(name)] = buildArgConfig(name, argType) currentConfig.argsConfig[variableSnakeToCamelCase(name)] = buildArgConfig(name, argType);
} }
} }
if (currentConfig.name.includes('.')) { 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){ for (const arg in currentConfig.argsConfig){
if (currentConfig.argsConfig.hasOwnProperty(arg)){ if (currentConfig.argsConfig.hasOwnProperty(arg)){
@ -121,11 +123,11 @@ const fromLine = (line, isFunction) => {
} }
} }
}*/ }*/
return currentConfig return currentConfig;
} };
function buildArgConfig(name, argType) { function buildArgConfig(name, argType) {
name = name === 'self' ? 'is_self' : name name = name === 'self' ? 'is_self' : name;
// Default values // Default values
const currentConfig = { const currentConfig = {
isVector: false, isVector: false,
@ -134,41 +136,41 @@ function buildArgConfig(name, argType) {
flagIndex: -1, flagIndex: -1,
flagIndicator: true, flagIndicator: true,
type: null, type: null,
useVectorId: null useVectorId: null,
} };
// Special case: some types can be inferred, which makes it // Special case: some types can be inferred, which makes it
// less annoying to type. Currently the only type that can // less annoying to type. Currently the only type that can
// be inferred is if the name is 'random_id', to which a // be inferred is if the name is 'random_id', to which a
// random ID will be assigned if left as None (the default) // 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 // The type can be an indicator that other arguments will be flags
if (argType !== '#') { if (argType !== '#') {
currentConfig.flagIndicator = false currentConfig.flagIndicator = false;
// Strip the exclamation mark always to have only the name // 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) // The type may be a flag (flags.IDX?REAL_TYPE)
// Note that 'flags' is NOT the flags name; this // Note that 'flags' is NOT the flags name; this
// is determined by a previous argument // is determined by a previous argument
// However, we assume that the argument will always be called 'flags' // However, we assume that the argument will always be called 'flags'
// @ts-ignore // @ts-ignore
const flagMatch = currentConfig.type.match(/flags.(\d+)\?([\w<>.]+)/) const flagMatch = currentConfig.type.match(/flags.(\d+)\?([\w<>.]+)/);
if (flagMatch) { if (flagMatch) {
currentConfig.isFlag = true currentConfig.isFlag = true;
currentConfig.flagIndex = Number(flagMatch[1]); currentConfig.flagIndex = Number(flagMatch[1]);
// Update the type to match the exact type, not the "flagged" one // 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<REAL_TYPE> // Then check if the type is a Vector<REAL_TYPE>
// @ts-ignore // @ts-ignore
const vectorMatch = currentConfig.type.match(/[Vv]ector<([\w\d.]+)>/) const vectorMatch = currentConfig.type.match(/[Vv]ector<([\w\d.]+)>/);
if (vectorMatch) { if (vectorMatch) {
currentConfig.isVector = true currentConfig.isVector = true;
// If the type's first letter is not uppercase, then // If the type's first letter is not uppercase, then
// it is a constructor and we use (read/write) its ID. // 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'; currentConfig.useVectorId = currentConfig.type.charAt(0) === 'V';
// Update the type to match the one inside the vector // 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 // See use_vector_id. An example of such case is ipPort in
@ -184,10 +186,10 @@ function buildArgConfig(name, argType) {
// @ts-ignore // @ts-ignore
if (/^[a-z]$/.test(currentConfig.type.split('.') if (/^[a-z]$/.test(currentConfig.type.split('.')
.pop() .pop()
.charAt(0) .charAt(0),
) )
) { ) {
currentConfig.skipConstructorId = true currentConfig.skipConstructorId = true;
} }
// The name may contain "date" in it, if this is the case and // The name may contain "date" in it, if this is the case and
@ -202,61 +204,65 @@ function buildArgConfig(name, argType) {
// this.type = 'date'; // this.type = 'date';
// } // }
} }
return currentConfig return currentConfig;
} }
const parseTl = function* (content, layer, methods = [], ignoreIds = CORE_TYPES) { const parseTl = function* (content, layer, methods = [], ignoreIds = CORE_TYPES) {
const methodInfo = (methods || []).reduce((o, m) => ({ ...o, [m.name]: m }), {}) const methodInfo = (methods || []).reduce((o, m) => ({
const objAll = [] ...o,
const objByName = {} [m.name]: m,
const objByType = {} }), {});
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')) { for (let line of file.split('\n')) {
const commentIndex = line.indexOf('//') const commentIndex = line.indexOf('//');
if (commentIndex !== -1) { if (commentIndex !== -1) {
line = line.slice(0, commentIndex) line = line.slice(0, commentIndex);
} }
line = line.trim() line = line.trim();
if (!line) { if (!line) {
continue continue;
} }
const match = line.match(/---(\w+)---/) const match = line.match(/---(\w+)---/);
if (match) { if (match) {
const [, followingTypes] = match const [, followingTypes] = match;
isFunction = followingTypes === 'functions' isFunction = followingTypes === 'functions';
continue continue;
} }
try { try {
const result = fromLine(line, isFunction) const result = fromLine(line, isFunction);
if (ignoreIds.has(result.constructorId)) { if (ignoreIds.has(result.constructorId)) {
continue continue;
} }
objAll.push(result) objAll.push(result);
if (!result.isFunction) { if (!result.isFunction) {
if (!objByType[result.result]) { if (!objByType[result.result]) {
objByType[result.result] = [] objByType[result.result] = [];
} }
objByName[result.name] = result objByName[result.name] = result;
objByType[result.result].push(result) objByType[result.result].push(result);
} }
} catch (e) { } catch (e) {
if (!e.toString().includes('vector#1cb5c415')) { if (!e.toString()
throw e .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)) { if (AUTH_KEY_TYPES.has(obj.constructorId)) {
for (const arg in obj.argsConfig) { for (const arg in obj.argsConfig) {
if (obj.argsConfig[arg].type === 'string') { if (obj.argsConfig[arg].type === 'string') {
obj.argsConfig[arg].type = 'bytes' obj.argsConfig[arg].type = 'bytes';
} }
} }
} }
} }
for (const obj of objAll) { for (const obj of objAll) {
yield obj yield obj;
} }
} };
const findAll = (regex, str, matches = []) => { const findAll = (regex, str, matches = []) => {
if (!regex.flags.includes(`g`)) { 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) { if (res) {
matches.push(res.slice(1)) matches.push(res.slice(1));
findAll(regex, str, matches) findAll(regex, str, matches);
} }
return matches return matches;
} };
function serializeBytes(data) { function serializeBytes(data) {
if (!(data instanceof Buffer)) { if (!(data instanceof Buffer)) {
if (typeof data == 'string') { if (typeof data == 'string') {
data = Buffer.from(data) data = Buffer.from(data);
} else { } else {
throw Error(`Bytes or str expected, not ${data.constructor.name}`) throw Error(`Bytes or str expected, not ${data.constructor.name}`);
} }
} }
const r = [] const r = [];
let padding let padding;
if (data.length < 254) { if (data.length < 254) {
padding = (data.length + 1) % 4 padding = (data.length + 1) % 4;
if (padding !== 0) { if (padding !== 0) {
padding = 4 - padding padding = 4 - padding;
} }
r.push(Buffer.from([data.length])) r.push(Buffer.from([data.length]));
r.push(data) r.push(data);
} else { } else {
padding = data.length % 4 padding = data.length % 4;
if (padding !== 0) { 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(Buffer.from([254, data.length % 256, (data.length >> 8) % 256, (data.length >> 16) % 256]));
r.push(data) r.push(data);
} }
r.push(Buffer.alloc(padding) r.push(Buffer.alloc(padding)
.fill(0)) .fill(0));
return Buffer.concat(r) return Buffer.concat(r);
} }
function serializeDate(dt) { function serializeDate(dt) {
if (!dt) { if (!dt) {
return Buffer.alloc(4) return Buffer.alloc(4)
.fill(0) .fill(0);
} }
if (dt instanceof Date) { if (dt instanceof Date) {
dt = Math.floor((Date.now() - dt.getTime()) / 1000) dt = Math.floor((Date.now() - dt.getTime()) / 1000);
} }
if (typeof dt == 'number') { if (typeof dt == 'number') {
const t = Buffer.alloc(4) const t = Buffer.alloc(4);
t.writeInt32LE(dt, 0) t.writeInt32LE(dt, 0);
return t return t;
} }
throw Error(`Cannot interpret "${dt}" as a date`) throw Error(`Cannot interpret "${dt}" as a date`);
} }
module.exports = { module.exports = {
@ -352,5 +358,5 @@ module.exports = {
serializeDate, serializeDate,
serializeBytes, serializeBytes,
snakeToCamelCase, snakeToCamelCase,
variableSnakeToCamelCase variableSnakeToCamelCase,
} };

View File

@ -1,6 +1,9 @@
const api = require('./api') const api = require('./api');
const { serializeBytes, serializeDate } = require('./generationHelpers') const {
const patched = null serializeBytes,
serializeDate,
} = require('./generationHelpers');
const patched = null;
module.exports = { module.exports = {
// TODO Refactor internal usages to always use `api`. // TODO Refactor internal usages to always use `api`.
@ -8,5 +11,5 @@ module.exports = {
requests: api, requests: api,
patched, patched,
serializeBytes, serializeBytes,
serializeDate serializeDate,
} };

View File

@ -1,57 +1,61 @@
const path = require('path') const path = require('path');
const fs = require('fs') const fs = require('fs');
const { parseTl } = require('../generationHelpers') const { parseTl } = require('../generationHelpers');
const templateFn = require('./template') const templateFn = require('./template');
const INPUT_FILE = path.resolve(__dirname, '../static/api.tl') const INPUT_FILE = path.resolve(__dirname, '../static/api.tl');
const SCHEMA_FILE = path.resolve(__dirname, '../static/schema.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() { function main() {
const tlContent = fs.readFileSync(INPUT_FILE, 'utf-8') const tlContent = fs.readFileSync(INPUT_FILE, 'utf-8');
const apiConfig = extractParams(tlContent) const apiConfig = extractParams(tlContent);
const schemeContent = fs.readFileSync(SCHEMA_FILE, 'utf-8') const schemeContent = fs.readFileSync(SCHEMA_FILE, 'utf-8');
const schemeConfig = extractParams(schemeContent) const schemeConfig = extractParams(schemeContent);
const types = [...apiConfig.types, ...schemeConfig.types] const types = [...apiConfig.types, ...schemeConfig.types];
const functions = [...apiConfig.functions, ...schemeConfig.functions] const functions = [...apiConfig.functions, ...schemeConfig.functions];
const constructors = [...apiConfig.constructors, ...schemeConfig.constructors] const constructors = [...apiConfig.constructors, ...schemeConfig.constructors];
const generated = templateFn({ types: types, functions: functions, constructors: constructors }) const generated = templateFn({
types: types,
functions: functions,
constructors: constructors,
});
fs.writeFileSync(OUTPUT_FILE, generated) fs.writeFileSync(OUTPUT_FILE, generated);
} }
function extractParams(fileContent) { function extractParams(fileContent) {
const defInterator = parseTl(fileContent, 109) const defInterator = parseTl(fileContent, 109);
const types = {} const types = {};
const constructors = [] const constructors = [];
const functions = [] const functions = [];
for (const def of defInterator) { for (const def of defInterator) {
if (def.isFunction) { if (def.isFunction) {
functions.push(def) functions.push(def);
} else { } else {
if (!types[def.result]) { 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] = { types[def.result] = {
namespace, namespace,
name, name,
constructors: [] constructors: [],
} };
} }
types[def.result].constructors.push(def.namespace ? `${def.namespace}.${def.name}` : def.name) types[def.result].constructors.push(def.namespace ? `${def.namespace}.${def.name}` : def.name);
constructors.push(def) constructors.push(def);
} }
} }
return { return {
types: Object.values(types), types: Object.values(types),
constructors, constructors,
functions functions,
} };
} }
main() main();