GramJs: Code style fixes by WebStorm
This commit is contained in:
parent
99cfeb6e09
commit
559ef6a53b
@ -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,
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -1 +1 @@
|
||||
module.exports = '0.0.2'
|
||||
module.exports = '0.0.2';
|
||||
|
||||
2
src/lib/gramjs/client/TelegramClient.d.ts
vendored
2
src/lib/gramjs/client/TelegramClient.d.ts
vendored
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -169,7 +169,7 @@ async function signInUserWithQrCode(
|
||||
|
||||
const inputPromise = (async () => {
|
||||
while (1) {
|
||||
if (isScanningComplete){
|
||||
if (isScanningComplete) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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'),
|
||||
}
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user