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

View File

@ -1,10 +1,16 @@
const BigInt = require('big-integer')
const Factorizator = require('./crypto/Factorizator')
const { constructors } = require('./tl')
const { readBigIntFromBuffer, readBufferFromBigInt, sha256, bigIntMod, modExp,
generateRandomBytes } = require('./Helpers')
const crypto = require('./crypto/crypto')
const SIZE_FOR_HASH = 256
const BigInt = require('big-integer');
const Factorizator = require('./crypto/Factorizator');
const { constructors } = require('./tl');
const {
readBigIntFromBuffer,
readBufferFromBigInt,
sha256,
bigIntMod,
modExp,
generateRandomBytes,
} = require('./Helpers');
const crypto = require('./crypto/crypto');
const SIZE_FOR_HASH = 256;
/**
*
@ -12,6 +18,7 @@ const SIZE_FOR_HASH = 256
* @param prime{BigInteger}
* @param g{BigInteger}
*/
/*
We don't support changing passwords yet
function checkPrimeAndGoodCheck(prime, g) {
@ -80,13 +87,13 @@ function checkPrimeAndGood(primeBytes, g) {
0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F,
0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF,
0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B,
])
]);
if (goodPrime.equals(primeBytes)) {
if ([ 3, 4, 5, 7 ].includes(g)) {
return // It's good
if ([3, 4, 5, 7].includes(g)) {
return; // It's good
}
}
throw new Error("Changing passwords unsupported")
throw new Error('Changing passwords unsupported');
//checkPrimeAndGoodCheck(readBigIntFromBuffer(primeBytes, false), g)
}
@ -97,7 +104,8 @@ function checkPrimeAndGood(primeBytes, g) {
* @returns {boolean}
*/
function isGoodLarge(number, p) {
return (number.greater(BigInt(0)) && (p.subtract(number).greater(BigInt(0))))
return (number.greater(BigInt(0)) && (p.subtract(number)
.greater(BigInt(0))));
}
/**
@ -106,7 +114,7 @@ function isGoodLarge(number, p) {
* @returns {Buffer}
*/
function numBytesForHash(number) {
return Buffer.concat([ Buffer.alloc(SIZE_FOR_HASH - number.length), number ])
return Buffer.concat([Buffer.alloc(SIZE_FOR_HASH - number.length), number]);
}
/**
@ -115,7 +123,7 @@ function numBytesForHash(number) {
* @returns {Buffer}
*/
function bigNumForHash(g) {
return readBufferFromBigInt(g, SIZE_FOR_HASH, false)
return readBufferFromBigInt(g, SIZE_FOR_HASH, false);
}
/**
@ -125,24 +133,24 @@ function bigNumForHash(g) {
* @returns {Boolean}
*/
function isGoodModExpFirst(modexp, prime) {
const diff = prime.subtract(modexp)
const diff = prime.subtract(modexp);
const minDiffBitsCount = 2048 - 64
const maxModExpSize = 256
const minDiffBitsCount = 2048 - 64;
const maxModExpSize = 256;
return !(diff.lesser(BigInt(0)) || diff.bitLength() < minDiffBitsCount ||
modexp.bitLength() < minDiffBitsCount ||
Math.floor((modexp.bitLength() + 7) / 8) > maxModExpSize)
Math.floor((modexp.bitLength() + 7) / 8) > maxModExpSize);
}
function xor(a, b) {
const length = Math.min(a.length, b.length)
const length = Math.min(a.length, b.length);
for (let i = 0; i < length; i++) {
a[i] = a[i] ^ b[i]
a[i] = a[i] ^ b[i];
}
return a
return a;
}
/**
@ -154,7 +162,7 @@ function xor(a, b) {
*/
function pbkdf2sha512(password, salt, iterations) {
return crypto.pbkdf2(password, salt, iterations, 64, 'sha512')
return crypto.pbkdf2(password, salt, iterations, 64, 'sha512');
}
/**
@ -164,10 +172,10 @@ function pbkdf2sha512(password, salt, iterations) {
* @returns {Buffer|*}
*/
async function computeHash(algo, password) {
const hash1 = await sha256(Buffer.concat([ algo.salt1, Buffer.from(password, 'utf-8'), algo.salt1 ]))
const hash2 = await sha256(Buffer.concat([ algo.salt2, hash1, algo.salt2 ]))
const hash3 = await pbkdf2sha512(hash2, algo.salt1, 100000)
return sha256(Buffer.concat([ algo.salt2, hash3, algo.salt2 ]))
const hash1 = await sha256(Buffer.concat([algo.salt1, Buffer.from(password, 'utf-8'), algo.salt1]));
const hash2 = await sha256(Buffer.concat([algo.salt2, hash1, algo.salt2]));
const hash3 = await pbkdf2sha512(hash2, algo.salt1, 100000);
return sha256(Buffer.concat([algo.salt2, hash3, algo.salt2]));
}
/**
@ -177,15 +185,15 @@ async function computeHash(algo, password) {
*/
async function computeDigest(algo, password) {
try {
checkPrimeAndGood(algo.p, algo.g)
checkPrimeAndGood(algo.p, algo.g);
} catch (e) {
throw new Error('bad p/g in password')
throw new Error('bad p/g in password');
}
const value = modExp(BigInt(algo.g),
readBigIntFromBuffer(await computeHash(algo, password), false),
readBigIntFromBuffer(algo.p, false))
return bigNumForHash(value)
readBigIntFromBuffer(algo.p, false));
return bigNumForHash(value);
}
/**
@ -194,70 +202,70 @@ async function computeDigest(algo, password) {
* @param password {string}
*/
async function computeCheck(request, password) {
const algo = request.currentAlgo
const algo = request.currentAlgo;
if (!(algo instanceof constructors.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)) {
throw new Error(`Unsupported password algorithm ${algo.className}`)
throw new Error(`Unsupported password algorithm ${algo.className}`);
}
const pwHash = await computeHash(algo, password)
const p = readBigIntFromBuffer(algo.p, false)
const g = algo.g
const B = readBigIntFromBuffer(request.srp_B, false)
const pwHash = await computeHash(algo, password);
const p = readBigIntFromBuffer(algo.p, false);
const g = algo.g;
const B = readBigIntFromBuffer(request.srp_B, false);
try {
checkPrimeAndGood(algo.p, g)
checkPrimeAndGood(algo.p, g);
} catch (e) {
throw new Error('bad /g in password')
throw new Error('bad /g in password');
}
if (!isGoodLarge(B, p)) {
throw new Error('bad b in check')
throw new Error('bad b in check');
}
const x = readBigIntFromBuffer(pwHash, false)
const pForHash = numBytesForHash(algo.p)
const gForHash = bigNumForHash(g)
const bForHash = numBytesForHash(request.srp_B)
const gX = modExp(BigInt(g), x, p)
const k = readBigIntFromBuffer(await sha256(Buffer.concat([ pForHash, gForHash ])), false)
const kgX = bigIntMod(k.multiply(gX),p)
const generateAndCheckRandom =async () => {
const randomSize = 256
const x = readBigIntFromBuffer(pwHash, false);
const pForHash = numBytesForHash(algo.p);
const gForHash = bigNumForHash(g);
const bForHash = numBytesForHash(request.srp_B);
const gX = modExp(BigInt(g), x, p);
const k = readBigIntFromBuffer(await sha256(Buffer.concat([pForHash, gForHash])), false);
const kgX = bigIntMod(k.multiply(gX), p);
const generateAndCheckRandom = async () => {
const randomSize = 256;
// eslint-disable-next-line no-constant-condition
while (true) {
const random = generateRandomBytes(randomSize)
const a = readBigIntFromBuffer(random, false)
const A = modExp(BigInt(g), a, p)
const random = generateRandomBytes(randomSize);
const a = readBigIntFromBuffer(random, false);
const A = modExp(BigInt(g), a, p);
if (isGoodModExpFirst(A, p)) {
const aForHash = bigNumForHash(A)
const u = readBigIntFromBuffer(await sha256(Buffer.concat([ aForHash, bForHash ])), false)
const aForHash = bigNumForHash(A);
const u = readBigIntFromBuffer(await sha256(Buffer.concat([aForHash, bForHash])), false);
if (u.greater(BigInt(0))) {
return [ a, aForHash, u ]
return [a, aForHash, u];
}
}
}
}
const [ a, aForHash, u ] =await generateAndCheckRandom()
const gB = bigIntMod(B.subtract(kgX),p)
};
const [a, aForHash, u] = await generateAndCheckRandom();
const gB = bigIntMod(B.subtract(kgX), p);
if (!isGoodModExpFirst(gB, p)) {
throw new Error('bad gB')
throw new Error('bad gB');
}
const ux = u.multiply(x)
const aUx = a.add(ux)
const S = modExp(gB, aUx, p)
const [K, pSha ,gSha, salt1Sha, salt2Sha] = await Promise.all([
const ux = u.multiply(x);
const aUx = a.add(ux);
const S = modExp(gB, aUx, p);
const [K, pSha, gSha, salt1Sha, salt2Sha] = await Promise.all([
sha256(bigNumForHash(S)),
sha256(pForHash),
sha256(gForHash),
sha256(algo.salt1),
sha256(algo.salt2)
])
sha256(algo.salt2),
]);
const M1 = await sha256(Buffer.concat([
xor(pSha,gSha),
xor(pSha, gSha),
salt1Sha,
salt2Sha,
aForHash,
bForHash,
K,
]))
]));
return new constructors.InputCheckPasswordSRP({
@ -265,11 +273,11 @@ async function computeCheck(request, password) {
A: Buffer.from(aForHash),
M1: M1,
})
});
}
module.exports = {
computeCheck,
computeDigest,
}
};

View File

@ -1,18 +1,18 @@
const { constructors } = require('./tl')
const { constructors } = require('./tl');
const USERNAME_RE = new RegExp('@|(?:https?:\\/\\/)?(?:www\\.)?' +
'(?:telegram\\.(?:me|dog)|t\\.me)\\/(@|joinchat\\/)?')
'(?:telegram\\.(?:me|dog)|t\\.me)\\/(@|joinchat\\/)?');
const JPEG_HEADER = Buffer.from('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00', 'hex')
const JPEG_FOOTER = Buffer.from('ffd9', 'hex')
const JPEG_HEADER = Buffer.from('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00', 'hex');
const JPEG_FOOTER = Buffer.from('ffd9', 'hex');
const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite=')
const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite=');
const VALID_USERNAME_RE = new RegExp('^([a-z]((?!__)[\\w\\d]){3,30}[a-z\\d]|gif|vid|' +
'pic|bing|wiki|imdb|bold|vote|like|coub)$')
'pic|bing|wiki|imdb|bold|vote|like|coub)$');
function _raiseCastFail(entity, target) {
throw new Error(`Cannot cast ${entity.className} to any kind of ${target}`)
throw new Error(`Cannot cast ${entity.className} to any kind of ${target}`);
}
/**
@ -37,41 +37,41 @@ function getInputPeer(entity, allowSelf = true, checkHash = true) {
// e.g. custom.Dialog (can't cyclic import).
if (allowSelf && 'inputEntity' in entity) {
return entity.inputEntity
return entity.inputEntity;
} else if ('entity' in entity) {
return getInputPeer(entity.entity)
return getInputPeer(entity.entity);
} else {
_raiseCastFail(entity, 'InputPeer')
_raiseCastFail(entity, 'InputPeer');
}
}
if (entity.SUBCLASS_OF_ID === 0xc91c90b6) { // crc32(b'InputPeer')
return entity
return entity;
}
if (entity instanceof constructors.User) {
if (entity.isSelf && allowSelf) {
return new constructors.InputPeerSelf()
return new constructors.InputPeerSelf();
} else if (entity.accessHash !== undefined || !checkHash) {
return new constructors.InputPeerUser({
userId: entity.id,
accessHash: entity.accessHash,
})
});
} else {
throw new Error('User without accessHash or min info cannot be input')
throw new Error('User without accessHash or min info cannot be input');
}
}
if (entity instanceof constructors.Chat || entity instanceof constructors.ChatEmpty ||
entity instanceof constructors.ChatForbidden) {
return new constructors.InputPeerChat({ chatId: entity.id })
return new constructors.InputPeerChat({ chatId: entity.id });
}
if (entity instanceof constructors.Channel) {
if (entity.accessHash !== undefined || !checkHash) {
return new constructors.InputPeerChannel({
channelId: entity.id,
accessHash: entity.accessHash
})
accessHash: entity.accessHash,
});
} else {
throw new TypeError('Channel without accessHash or min info cannot be input')
throw new TypeError('Channel without accessHash or min info cannot be input');
}
}
if (entity instanceof constructors.ChannelForbidden) {
@ -79,38 +79,38 @@ function getInputPeer(entity, allowSelf = true, checkHash = true) {
// also not optional, we assume that this truly is the case.
return new constructors.InputPeerChannel({
channelId: entity.id,
accessHash: entity.accessHash
})
accessHash: entity.accessHash,
});
}
if (entity instanceof constructors.InputUser) {
return new constructors.InputPeerUser({
userId: entity.userId,
accessHash: entity.accessHash
})
accessHash: entity.accessHash,
});
}
if (entity instanceof constructors.InputChannel) {
return new constructors.InputPeerChannel({
channelId: entity.channelId,
accessHash: entity.accessHash
})
accessHash: entity.accessHash,
});
}
if (entity instanceof constructors.UserEmpty) {
return new constructors.InputPeerEmpty()
return new constructors.InputPeerEmpty();
}
if (entity instanceof constructors.UserFull) {
return getInputPeer(entity.user)
return getInputPeer(entity.user);
}
if (entity instanceof constructors.ChatFull) {
return new constructors.InputPeerChat({ chatId: entity.id })
return new constructors.InputPeerChat({ chatId: entity.id });
}
if (entity instanceof constructors.PeerChat) {
return new constructors.InputPeerChat(entity.chatId)
return new constructors.InputPeerChat(entity.chatId);
}
_raiseCastFail(entity, 'InputPeer')
_raiseCastFail(entity, 'InputPeer');
}
/**
@ -228,6 +228,7 @@ function getInputDialog(dialog) {
_raiseCastFail(dialog, 'InputDialogPeer')
}
*/
/*CONTEST
function getInputMessage(message) {
@ -259,12 +260,12 @@ function getInputMessage(message) {
function strippedPhotoToJpg(stripped) {
// Note: Changes here should update _stripped_real_length
if (stripped.length < 3 || stripped[0] !== 1) {
return stripped
return stripped;
}
const header = Buffer.from(JPEG_HEADER)
header[164] = stripped[1]
header[166] = stripped[2]
return Buffer.concat([header, stripped.slice(3), JPEG_FOOTER])
const header = Buffer.from(JPEG_HEADER);
header[164] = stripped[1];
header[166] = stripped[2];
return Buffer.concat([header, stripped.slice(3), JPEG_FOOTER]);
}
/*CONTEST
@ -329,16 +330,16 @@ function getInputLocation(location) {
*/
function getAppropriatedPartSize(fileSize) {
if (fileSize <= 104857600) { // 100MB
return 128
return 128;
}
if (fileSize <= 786432000) { // 750MB
return 256
return 256;
}
if (fileSize <= 1572864000) { // 1500MB
return 512
return 512;
}
throw new Error('File size too large')
throw new Error('File size too large');
}
/*CONTEST
@ -480,6 +481,7 @@ function resolveId(markedId) {
* @returns {{inputEntity: *, entity: *}}
* @private
*/
/*CONTEST
function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer) {
@ -501,15 +503,15 @@ function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer)
function getMessageId(message) {
if (message === null || message === undefined) {
return null
return null;
}
if (typeof message == 'number') {
return message
return message;
}
if (message.SUBCLASS_OF_ID === 0x790009e3) { // crc32(b'Message')
return message.id
return message.id;
}
throw new Error(`Invalid message type: ${message.constructor.name}`)
throw new Error(`Invalid message type: ${message.constructor.name}`);
}
@ -518,7 +520,8 @@ function getMessageId(message) {
* @param phone
*/
function parsePhone(phone) {
return phone.toString().replace(/[+()\s-]/gm, '')
return phone.toString()
.replace(/[+()\s-]/gm, '');
}
/**
@ -531,6 +534,7 @@ function parsePhone(phone) {
* @param username {string}
*/
/*CONTEST
function parseUsername(username) {
@ -577,18 +581,18 @@ function rtrim(s, mask) {
function getDisplayName(entity) {
if (entity instanceof constructors.User) {
if (entity.lastName && entity.firstName) {
return `${entity.firstName} ${entity.lastName}`
return `${entity.firstName} ${entity.lastName}`;
} else if (entity.firstName) {
return entity.firstName
return entity.firstName;
} else if (entity.lastName) {
return entity.lastName
return entity.lastName;
} else {
return ''
return '';
}
} else if (entity instanceof constructors.Chat || entity instanceof constructors.Channel) {
return entity.title
return entity.title;
}
return ''
return '';
}
/**
@ -596,6 +600,7 @@ function getDisplayName(entity) {
* @param item
* @returns {boolean}
*/
/*CONTEST
Duplicate ?
function isListLike(item) {
@ -618,34 +623,34 @@ function getDC(dcId, cdn = false) {
return {
id: 1,
ipAddress: 'pluto.web.telegram.org',
port: 443
}
port: 443,
};
case 2:
return {
id: 2,
ipAddress: 'venus.web.telegram.org',
port: 443
}
port: 443,
};
case 3:
return {
id: 3,
ipAddress: 'aurora.web.telegram.org',
port: 443
}
port: 443,
};
case 4:
return {
id: 4,
ipAddress: 'vesta.web.telegram.org',
port: 443
}
port: 443,
};
case 5:
return {
id: 5,
ipAddress: 'flora.web.telegram.org',
port: 443
}
port: 443,
};
default:
throw new Error(`Cannot find the DC with the ID of ${dcId}`)
throw new Error(`Cannot find the DC with the ID of ${dcId}`);
}
// TODO chose based on current connection method
/*
@ -683,5 +688,5 @@ module.exports = {
getAppropriatedPartSize,
//getInputLocation,
strippedPhotoToJpg,
getDC
}
getDC,
};

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 { uploadFile, UploadFileParams } from './uploadFile';
import { downloadFile, DownloadFileParams } from './downloadFile';
import { updateTwoFaSettings, TwoFaParams } from './2fa';
import { TwoFaParams, updateTwoFaSettings } from './2fa';
declare class TelegramClient {
constructor(...args: any)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,11 +8,49 @@ class Session {
* @param toInstance {Session|null}
* @returns {Session}
*/
/* CONTEST
clone(toInstance = null) {
return toInstance || new this.constructor()
}*/
/**
* Returns the currently-used data center ID.
*/
get dcId() {
throw new Error('Not Implemented');
}
/**
* Returns the server address where the library should connect to.
*/
get serverAddress() {
throw new Error('Not Implemented');
}
/**
* Returns the port to which the library should connect to.
*/
get port() {
throw new Error('Not Implemented');
}
/**
* Returns an ``AuthKey`` instance associated with the saved
* data center, or `None` if a new one should be generated.
*/
get authKey() {
throw new Error('Not Implemented');
}
/**
* Sets the ``AuthKey`` to be used for the saved data center.
* @param value
*/
set authKey(value) {
throw new Error('Not Implemented');
}
/**
* Sets the information of the data center address and port that
* the library should connect to, as well as the data center ID,
@ -22,44 +60,7 @@ class Session {
* @param port {number}
*/
setDC(dcId, serverAddress, port) {
throw new Error('Not implemented')
}
/**
* Returns the currently-used data center ID.
*/
get dcId() {
throw new Error('Not Implemented')
}
/**
* Returns the server address where the library should connect to.
*/
get serverAddress() {
throw new Error('Not Implemented')
}
/**
* Returns the port to which the library should connect to.
*/
get port() {
throw new Error('Not Implemented')
}
/**
* Returns an ``AuthKey`` instance associated with the saved
* data center, or `None` if a new one should be generated.
*/
get authKey() {
throw new Error('Not Implemented')
}
/**
* Sets the ``AuthKey`` to be used for the saved data center.
* @param value
*/
set authKey(value) {
throw new Error('Not Implemented')
throw new Error('Not implemented');
}
/**
@ -112,6 +113,7 @@ class Session {
* Called on client disconnection. Should be used to
* free any used resources. Can be left empty if none.
*/
/*CONTEST
close() {
@ -124,7 +126,7 @@ class Session {
* make persist the relevant session information to disk.
*/
save() {
throw new Error('Not Implemented')
throw new Error('Not Implemented');
}
/**
@ -133,7 +135,7 @@ class Session {
*/
delete() {
throw new Error('Not Implemented')
throw new Error('Not Implemented');
}
/**
@ -172,4 +174,4 @@ class Session {
*/
}
module.exports = Session
module.exports = Session;

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 {
async _delete() {
const request = new Request(this._storageKey)
const cache = await self.caches.open(CACHE_NAME)
return cache.delete(request)
const request = new Request(this._storageKey);
const cache = await self.caches.open(CACHE_NAME);
return cache.delete(request);
}
async _fetchFromCache() {
const request = new Request(this._storageKey)
const cache = await self.caches.open(CACHE_NAME)
const cached = await cache.match(request)
return cached ? cached.text() : null
const request = new Request(this._storageKey);
const cache = await self.caches.open(CACHE_NAME);
const cached = await cache.match(request);
return cached ? cached.text() : null;
}
async _saveToCache(data) {
const request = new Request(this._storageKey)
const response = new Response(data)
const cache = await self.caches.open(CACHE_NAME)
return cache.put(request, response)
const request = new Request(this._storageKey);
const response = new Response(data);
const cache = await self.caches.open(CACHE_NAME);
return cache.put(request, response);
}
}
module.exports = CacheApiSession
module.exports = CacheApiSession;

View File

@ -1,20 +1,20 @@
const StorageSession = require('./StorageSession')
const idb = require('idb-keyval')
const StorageSession = require('./StorageSession');
const idb = require('idb-keyval');
const CACHE_NAME = 'GramJs'
const CACHE_NAME = 'GramJs';
class IdbSession extends StorageSession {
async _delete() {
return idb.del(`${CACHE_NAME}:${this._storageKey}`)
return idb.del(`${CACHE_NAME}:${this._storageKey}`);
}
async _fetchFromCache() {
return idb.get(`${CACHE_NAME}:${this._storageKey}`)
return idb.get(`${CACHE_NAME}:${this._storageKey}`);
}
async _saveToCache(data) {
return idb.set(`${CACHE_NAME}:${this._storageKey}`, data)
return idb.set(`${CACHE_NAME}:${this._storageKey}`, data);
}
}
module.exports = IdbSession
module.exports = IdbSession;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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