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 crypto = require('./crypto/crypto');
|
||||||
const BigInt = require('big-integer')
|
const BigInt = require('big-integer');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* converts a buffer to big int
|
* converts a buffer to big int
|
||||||
@ -9,17 +9,17 @@ const BigInt = require('big-integer')
|
|||||||
* @returns {bigInt.BigInteger}
|
* @returns {bigInt.BigInteger}
|
||||||
*/
|
*/
|
||||||
function readBigIntFromBuffer(buffer, little = true, signed = false) {
|
function readBigIntFromBuffer(buffer, little = true, signed = false) {
|
||||||
let randBuffer = Buffer.from(buffer)
|
let randBuffer = Buffer.from(buffer);
|
||||||
const bytesNumber = randBuffer.length
|
const bytesNumber = randBuffer.length;
|
||||||
if (little) {
|
if (little) {
|
||||||
randBuffer = randBuffer.reverse()
|
randBuffer = randBuffer.reverse();
|
||||||
}
|
}
|
||||||
let bigInt = BigInt(randBuffer.toString('hex'), 16)
|
let bigInt = BigInt(randBuffer.toString('hex'), 16);
|
||||||
if (signed && Math.floor(bigInt.toString('2').length / 8) >= bytesNumber) {
|
if (signed && Math.floor(bigInt.toString('2').length / 8) >= bytesNumber) {
|
||||||
bigInt = bigInt.subtract(BigInt(2)
|
bigInt = bigInt.subtract(BigInt(2)
|
||||||
.pow(BigInt(bytesNumber * 8)))
|
.pow(BigInt(bytesNumber * 8)));
|
||||||
}
|
}
|
||||||
return bigInt
|
return bigInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,12 +29,13 @@ function readBigIntFromBuffer(buffer, little = true, signed = false) {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
function toSignedLittleBuffer(big, number = 8) {
|
function toSignedLittleBuffer(big, number = 8) {
|
||||||
const bigNumber = BigInt(big)
|
const bigNumber = BigInt(big);
|
||||||
const byteArray = []
|
const byteArray = [];
|
||||||
for (let i = 0; i < number; i++) {
|
for (let i = 0; i < number; i++) {
|
||||||
byteArray[i] = bigNumber.shiftRight(8 * i).and(255)
|
byteArray[i] = bigNumber.shiftRight(8 * i)
|
||||||
|
.and(255);
|
||||||
}
|
}
|
||||||
return Buffer.from(byteArray)
|
return Buffer.from(byteArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -47,54 +48,54 @@ function toSignedLittleBuffer(big, number = 8) {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) {
|
function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) {
|
||||||
bigInt = BigInt(bigInt)
|
bigInt = BigInt(bigInt);
|
||||||
const bitLength = bigInt.bitLength()
|
const bitLength = bigInt.bitLength();
|
||||||
|
|
||||||
const bytes = Math.ceil(bitLength / 8)
|
const bytes = Math.ceil(bitLength / 8);
|
||||||
if (bytesNumber < bytes) {
|
if (bytesNumber < bytes) {
|
||||||
throw new Error('OverflowError: int too big to convert')
|
throw new Error('OverflowError: int too big to convert');
|
||||||
}
|
}
|
||||||
if (!signed && bigInt.lesser(BigInt(0))) {
|
if (!signed && bigInt.lesser(BigInt(0))) {
|
||||||
throw new Error('Cannot convert to unsigned')
|
throw new Error('Cannot convert to unsigned');
|
||||||
}
|
}
|
||||||
let below = false
|
let below = false;
|
||||||
if (bigInt.lesser(BigInt(0))) {
|
if (bigInt.lesser(BigInt(0))) {
|
||||||
below = true
|
below = true;
|
||||||
bigInt = bigInt.abs()
|
bigInt = bigInt.abs();
|
||||||
}
|
}
|
||||||
|
|
||||||
const hex = bigInt.toString('16')
|
const hex = bigInt.toString('16')
|
||||||
.padStart(bytesNumber * 2, '0')
|
.padStart(bytesNumber * 2, '0');
|
||||||
let l = Buffer.from(hex, 'hex')
|
let l = Buffer.from(hex, 'hex');
|
||||||
if (little) {
|
if (little) {
|
||||||
l = l.reverse()
|
l = l.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signed && below) {
|
if (signed && below) {
|
||||||
if (little) {
|
if (little) {
|
||||||
let reminder = false
|
let reminder = false;
|
||||||
if (l[0] !== 0) {
|
if (l[0] !== 0) {
|
||||||
l[0] -= 1
|
l[0] -= 1;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < l.length; i++) {
|
for (let i = 0; i < l.length; i++) {
|
||||||
if (l[i] === 0) {
|
if (l[i] === 0) {
|
||||||
reminder = true
|
reminder = true;
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
if (reminder) {
|
if (reminder) {
|
||||||
l[i] -= 1
|
l[i] -= 1;
|
||||||
reminder = false
|
reminder = false;
|
||||||
}
|
}
|
||||||
l[i] = 255 - l[i]
|
l[i] = 255 - l[i];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
l[l.length - 1] = 256 - l[l.length - 1]
|
l[l.length - 1] = 256 - l[l.length - 1];
|
||||||
for (let i = 0; i < l.length - 1; i++) {
|
for (let i = 0; i < l.length - 1; i++) {
|
||||||
l[i] = 255 - l[i]
|
l[i] = 255 - l[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return l
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,7 +103,7 @@ function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false
|
|||||||
* @returns {BigInteger}
|
* @returns {BigInteger}
|
||||||
*/
|
*/
|
||||||
function generateRandomLong(signed = true) {
|
function generateRandomLong(signed = true) {
|
||||||
return readBigIntFromBuffer(generateRandomBytes(8), true, signed)
|
return readBigIntFromBuffer(generateRandomBytes(8), true, signed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,7 +113,7 @@ function generateRandomLong(signed = true) {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
function mod(n, m) {
|
function mod(n, m) {
|
||||||
return ((n % m) + m) % m
|
return ((n % m) + m) % m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,7 +123,7 @@ function mod(n, m) {
|
|||||||
* @returns {BigInt}
|
* @returns {BigInt}
|
||||||
*/
|
*/
|
||||||
function bigIntMod(n, m) {
|
function bigIntMod(n, m) {
|
||||||
return ((n.remainder(m)).add(m)).remainder(m)
|
return ((n.remainder(m)).add(m)).remainder(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,7 +132,7 @@ function bigIntMod(n, m) {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
function generateRandomBytes(count) {
|
function generateRandomBytes(count) {
|
||||||
return Buffer.from(crypto.randomBytes(count))
|
return Buffer.from(crypto.randomBytes(count));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,6 +142,7 @@ function generateRandomBytes(count) {
|
|||||||
* @param client
|
* @param client
|
||||||
* @returns {{iv: Buffer, key: Buffer}}
|
* @returns {{iv: Buffer, key: Buffer}}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*CONTEST
|
/*CONTEST
|
||||||
this is mtproto 1 (mostly used for secret chats)
|
this is mtproto 1 (mostly used for secret chats)
|
||||||
async function calcKey(sharedKey, msgKey, client) {
|
async function calcKey(sharedKey, msgKey, client) {
|
||||||
@ -168,26 +170,26 @@ async function calcKey(sharedKey, msgKey, client) {
|
|||||||
* @returns {{key: Buffer, iv: Buffer}}
|
* @returns {{key: Buffer, iv: Buffer}}
|
||||||
*/
|
*/
|
||||||
async function generateKeyDataFromNonce(serverNonce, newNonce) {
|
async function generateKeyDataFromNonce(serverNonce, newNonce) {
|
||||||
serverNonce = toSignedLittleBuffer(serverNonce, 16)
|
serverNonce = toSignedLittleBuffer(serverNonce, 16);
|
||||||
newNonce = toSignedLittleBuffer(newNonce, 32)
|
newNonce = toSignedLittleBuffer(newNonce, 32);
|
||||||
const [hash1, hash2, hash3] = await Promise.all([
|
const [hash1, hash2, hash3] = await Promise.all([
|
||||||
sha1(Buffer.concat([newNonce, serverNonce])),
|
sha1(Buffer.concat([newNonce, serverNonce])),
|
||||||
sha1(Buffer.concat([serverNonce, newNonce])),
|
sha1(Buffer.concat([serverNonce, newNonce])),
|
||||||
sha1(Buffer.concat([newNonce, newNonce]))
|
sha1(Buffer.concat([newNonce, newNonce])),
|
||||||
])
|
]);
|
||||||
const keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)])
|
const keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)]);
|
||||||
const ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)])
|
const ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]);
|
||||||
return {
|
return {
|
||||||
key: keyBuffer,
|
key: keyBuffer,
|
||||||
iv: ivBuffer
|
iv: ivBuffer,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertToLittle(buf) {
|
function convertToLittle(buf) {
|
||||||
const correct = Buffer.alloc(buf.length * 4);
|
const correct = Buffer.alloc(buf.length * 4);
|
||||||
|
|
||||||
for (let i = 0; i < buf.length; i++) {
|
for (let i = 0; i < buf.length; i++) {
|
||||||
correct.writeUInt32BE(buf[i], i * 4)
|
correct.writeUInt32BE(buf[i], i * 4);
|
||||||
}
|
}
|
||||||
return correct;
|
return correct;
|
||||||
}
|
}
|
||||||
@ -198,9 +200,9 @@ function convertToLittle(buf) {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
function sha1(data) {
|
function sha1(data) {
|
||||||
const shaSum = crypto.createHash('sha1')
|
const shaSum = crypto.createHash('sha1');
|
||||||
shaSum.update(data)
|
shaSum.update(data);
|
||||||
return shaSum.digest()
|
return shaSum.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -210,9 +212,9 @@ function sha1(data) {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
function sha256(data) {
|
function sha256(data) {
|
||||||
const shaSum = crypto.createHash('sha256')
|
const shaSum = crypto.createHash('sha256');
|
||||||
shaSum.update(data)
|
shaSum.update(data);
|
||||||
return shaSum.digest()
|
return shaSum.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -223,20 +225,20 @@ function sha256(data) {
|
|||||||
* @returns {bigInt.BigInteger}
|
* @returns {bigInt.BigInteger}
|
||||||
*/
|
*/
|
||||||
function modExp(a, b, n) {
|
function modExp(a, b, n) {
|
||||||
a = a.remainder(n)
|
a = a.remainder(n);
|
||||||
let result = BigInt.one
|
let result = BigInt.one;
|
||||||
let x = a
|
let x = a;
|
||||||
while (b.greater(BigInt.zero)) {
|
while (b.greater(BigInt.zero)) {
|
||||||
const leastSignificantBit = b.remainder(BigInt(2))
|
const leastSignificantBit = b.remainder(BigInt(2));
|
||||||
b = b.divide(BigInt(2))
|
b = b.divide(BigInt(2));
|
||||||
if (leastSignificantBit.eq(BigInt.one)) {
|
if (leastSignificantBit.eq(BigInt.one)) {
|
||||||
result = result.multiply(x)
|
result = result.multiply(x);
|
||||||
result = result.remainder(n)
|
result = result.remainder(n);
|
||||||
}
|
}
|
||||||
x = x.multiply(x)
|
x = x.multiply(x);
|
||||||
x = x.remainder(n)
|
x = x.remainder(n);
|
||||||
}
|
}
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -247,9 +249,9 @@ function modExp(a, b, n) {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
function getByteArray(integer, signed = false) {
|
function getByteArray(integer, signed = false) {
|
||||||
const bits = integer.toString(2).length
|
const bits = integer.toString(2).length;
|
||||||
const byteLength = Math.floor((bits + 8 - 1) / 8)
|
const byteLength = Math.floor((bits + 8 - 1) / 8);
|
||||||
return readBufferFromBigInt(BigInt(integer), byteLength, false, signed)
|
return readBufferFromBigInt(BigInt(integer), byteLength, false, signed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -259,9 +261,9 @@ function getByteArray(integer, signed = false) {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
function getRandomInt(min, max) {
|
function getRandomInt(min, max) {
|
||||||
min = Math.ceil(min)
|
min = Math.ceil(min);
|
||||||
max = Math.floor(max)
|
max = Math.floor(max);
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -269,7 +271,7 @@ function getRandomInt(min, max) {
|
|||||||
* @param ms time in milliseconds
|
* @param ms time in milliseconds
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the obj is an array
|
* Checks if the obj is an array
|
||||||
@ -293,36 +295,37 @@ function isArrayLike(obj) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Taken from https://stackoverflow.com/questions/18638900/javascript-crc32/18639999#18639999
|
// Taken from https://stackoverflow.com/questions/18638900/javascript-crc32/18639999#18639999
|
||||||
function makeCRCTable() {
|
function makeCRCTable() {
|
||||||
let c
|
let c;
|
||||||
const crcTable = []
|
const crcTable = [];
|
||||||
for (let n = 0; n < 256; n++) {
|
for (let n = 0; n < 256; n++) {
|
||||||
c = n
|
c = n;
|
||||||
for (let k = 0; k < 8; k++) {
|
for (let k = 0; k < 8; k++) {
|
||||||
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1))
|
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
||||||
}
|
}
|
||||||
crcTable[n] = c
|
crcTable[n] = c;
|
||||||
}
|
}
|
||||||
return crcTable
|
return crcTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
let crcTable = null
|
let crcTable = null;
|
||||||
|
|
||||||
function crc32(buf) {
|
function crc32(buf) {
|
||||||
if (!crcTable) {
|
if (!crcTable) {
|
||||||
crcTable = makeCRCTable()
|
crcTable = makeCRCTable();
|
||||||
}
|
}
|
||||||
if (!Buffer.isBuffer(buf)) {
|
if (!Buffer.isBuffer(buf)) {
|
||||||
buf = Buffer.from(buf)
|
buf = Buffer.from(buf);
|
||||||
}
|
}
|
||||||
let crc = -1
|
let crc = -1;
|
||||||
|
|
||||||
for (let index = 0; index < buf.length; index++) {
|
for (let index = 0; index < buf.length; index++) {
|
||||||
const byte = buf[index]
|
const byte = buf[index];
|
||||||
crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8)
|
crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
||||||
}
|
}
|
||||||
return (crc ^ (-1)) >>> 0
|
return (crc ^ (-1)) >>> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -343,6 +346,6 @@ module.exports = {
|
|||||||
getByteArray,
|
getByteArray,
|
||||||
//isArrayLike,
|
//isArrayLike,
|
||||||
toSignedLittleBuffer,
|
toSignedLittleBuffer,
|
||||||
convertToLittle
|
convertToLittle,
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
const BigInt = require('big-integer')
|
const BigInt = require('big-integer');
|
||||||
const Factorizator = require('./crypto/Factorizator')
|
const Factorizator = require('./crypto/Factorizator');
|
||||||
const { constructors } = require('./tl')
|
const { constructors } = require('./tl');
|
||||||
const { readBigIntFromBuffer, readBufferFromBigInt, sha256, bigIntMod, modExp,
|
const {
|
||||||
generateRandomBytes } = require('./Helpers')
|
readBigIntFromBuffer,
|
||||||
const crypto = require('./crypto/crypto')
|
readBufferFromBigInt,
|
||||||
const SIZE_FOR_HASH = 256
|
sha256,
|
||||||
|
bigIntMod,
|
||||||
|
modExp,
|
||||||
|
generateRandomBytes,
|
||||||
|
} = require('./Helpers');
|
||||||
|
const crypto = require('./crypto/crypto');
|
||||||
|
const SIZE_FOR_HASH = 256;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -12,6 +18,7 @@ const SIZE_FOR_HASH = 256
|
|||||||
* @param prime{BigInteger}
|
* @param prime{BigInteger}
|
||||||
* @param g{BigInteger}
|
* @param g{BigInteger}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We don't support changing passwords yet
|
We don't support changing passwords yet
|
||||||
function checkPrimeAndGoodCheck(prime, g) {
|
function checkPrimeAndGoodCheck(prime, g) {
|
||||||
@ -80,13 +87,13 @@ function checkPrimeAndGood(primeBytes, g) {
|
|||||||
0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F,
|
0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F,
|
||||||
0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF,
|
0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF,
|
||||||
0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B,
|
0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B,
|
||||||
])
|
]);
|
||||||
if (goodPrime.equals(primeBytes)) {
|
if (goodPrime.equals(primeBytes)) {
|
||||||
if ([ 3, 4, 5, 7 ].includes(g)) {
|
if ([3, 4, 5, 7].includes(g)) {
|
||||||
return // It's good
|
return; // It's good
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error("Changing passwords unsupported")
|
throw new Error('Changing passwords unsupported');
|
||||||
//checkPrimeAndGoodCheck(readBigIntFromBuffer(primeBytes, false), g)
|
//checkPrimeAndGoodCheck(readBigIntFromBuffer(primeBytes, false), g)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +104,8 @@ function checkPrimeAndGood(primeBytes, g) {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
function isGoodLarge(number, p) {
|
function isGoodLarge(number, p) {
|
||||||
return (number.greater(BigInt(0)) && (p.subtract(number).greater(BigInt(0))))
|
return (number.greater(BigInt(0)) && (p.subtract(number)
|
||||||
|
.greater(BigInt(0))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,7 +114,7 @@ function isGoodLarge(number, p) {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
function numBytesForHash(number) {
|
function numBytesForHash(number) {
|
||||||
return Buffer.concat([ Buffer.alloc(SIZE_FOR_HASH - number.length), number ])
|
return Buffer.concat([Buffer.alloc(SIZE_FOR_HASH - number.length), number]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,7 +123,7 @@ function numBytesForHash(number) {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
function bigNumForHash(g) {
|
function bigNumForHash(g) {
|
||||||
return readBufferFromBigInt(g, SIZE_FOR_HASH, false)
|
return readBufferFromBigInt(g, SIZE_FOR_HASH, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,24 +133,24 @@ function bigNumForHash(g) {
|
|||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
function isGoodModExpFirst(modexp, prime) {
|
function isGoodModExpFirst(modexp, prime) {
|
||||||
const diff = prime.subtract(modexp)
|
const diff = prime.subtract(modexp);
|
||||||
|
|
||||||
const minDiffBitsCount = 2048 - 64
|
const minDiffBitsCount = 2048 - 64;
|
||||||
const maxModExpSize = 256
|
const maxModExpSize = 256;
|
||||||
|
|
||||||
return !(diff.lesser(BigInt(0)) || diff.bitLength() < minDiffBitsCount ||
|
return !(diff.lesser(BigInt(0)) || diff.bitLength() < minDiffBitsCount ||
|
||||||
modexp.bitLength() < minDiffBitsCount ||
|
modexp.bitLength() < minDiffBitsCount ||
|
||||||
Math.floor((modexp.bitLength() + 7) / 8) > maxModExpSize)
|
Math.floor((modexp.bitLength() + 7) / 8) > maxModExpSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
function xor(a, b) {
|
function xor(a, b) {
|
||||||
const length = Math.min(a.length, b.length)
|
const length = Math.min(a.length, b.length);
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
a[i] = a[i] ^ b[i]
|
a[i] = a[i] ^ b[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return a
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,7 +162,7 @@ function xor(a, b) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function pbkdf2sha512(password, salt, iterations) {
|
function pbkdf2sha512(password, salt, iterations) {
|
||||||
return crypto.pbkdf2(password, salt, iterations, 64, 'sha512')
|
return crypto.pbkdf2(password, salt, iterations, 64, 'sha512');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -164,10 +172,10 @@ function pbkdf2sha512(password, salt, iterations) {
|
|||||||
* @returns {Buffer|*}
|
* @returns {Buffer|*}
|
||||||
*/
|
*/
|
||||||
async function computeHash(algo, password) {
|
async function computeHash(algo, password) {
|
||||||
const hash1 = await sha256(Buffer.concat([ algo.salt1, Buffer.from(password, 'utf-8'), algo.salt1 ]))
|
const hash1 = await sha256(Buffer.concat([algo.salt1, Buffer.from(password, 'utf-8'), algo.salt1]));
|
||||||
const hash2 = await sha256(Buffer.concat([ algo.salt2, hash1, algo.salt2 ]))
|
const hash2 = await sha256(Buffer.concat([algo.salt2, hash1, algo.salt2]));
|
||||||
const hash3 = await pbkdf2sha512(hash2, algo.salt1, 100000)
|
const hash3 = await pbkdf2sha512(hash2, algo.salt1, 100000);
|
||||||
return sha256(Buffer.concat([ algo.salt2, hash3, algo.salt2 ]))
|
return sha256(Buffer.concat([algo.salt2, hash3, algo.salt2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -177,15 +185,15 @@ async function computeHash(algo, password) {
|
|||||||
*/
|
*/
|
||||||
async function computeDigest(algo, password) {
|
async function computeDigest(algo, password) {
|
||||||
try {
|
try {
|
||||||
checkPrimeAndGood(algo.p, algo.g)
|
checkPrimeAndGood(algo.p, algo.g);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('bad p/g in password')
|
throw new Error('bad p/g in password');
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = modExp(BigInt(algo.g),
|
const value = modExp(BigInt(algo.g),
|
||||||
readBigIntFromBuffer(await computeHash(algo, password), false),
|
readBigIntFromBuffer(await computeHash(algo, password), false),
|
||||||
readBigIntFromBuffer(algo.p, false))
|
readBigIntFromBuffer(algo.p, false));
|
||||||
return bigNumForHash(value)
|
return bigNumForHash(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,70 +202,70 @@ async function computeDigest(algo, password) {
|
|||||||
* @param password {string}
|
* @param password {string}
|
||||||
*/
|
*/
|
||||||
async function computeCheck(request, password) {
|
async function computeCheck(request, password) {
|
||||||
const algo = request.currentAlgo
|
const algo = request.currentAlgo;
|
||||||
if (!(algo instanceof constructors.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)) {
|
if (!(algo instanceof constructors.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)) {
|
||||||
throw new Error(`Unsupported password algorithm ${algo.className}`)
|
throw new Error(`Unsupported password algorithm ${algo.className}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pwHash = await computeHash(algo, password)
|
const pwHash = await computeHash(algo, password);
|
||||||
const p = readBigIntFromBuffer(algo.p, false)
|
const p = readBigIntFromBuffer(algo.p, false);
|
||||||
const g = algo.g
|
const g = algo.g;
|
||||||
const B = readBigIntFromBuffer(request.srp_B, false)
|
const B = readBigIntFromBuffer(request.srp_B, false);
|
||||||
try {
|
try {
|
||||||
checkPrimeAndGood(algo.p, g)
|
checkPrimeAndGood(algo.p, g);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('bad /g in password')
|
throw new Error('bad /g in password');
|
||||||
}
|
}
|
||||||
if (!isGoodLarge(B, p)) {
|
if (!isGoodLarge(B, p)) {
|
||||||
throw new Error('bad b in check')
|
throw new Error('bad b in check');
|
||||||
}
|
}
|
||||||
const x = readBigIntFromBuffer(pwHash, false)
|
const x = readBigIntFromBuffer(pwHash, false);
|
||||||
const pForHash = numBytesForHash(algo.p)
|
const pForHash = numBytesForHash(algo.p);
|
||||||
const gForHash = bigNumForHash(g)
|
const gForHash = bigNumForHash(g);
|
||||||
const bForHash = numBytesForHash(request.srp_B)
|
const bForHash = numBytesForHash(request.srp_B);
|
||||||
const gX = modExp(BigInt(g), x, p)
|
const gX = modExp(BigInt(g), x, p);
|
||||||
const k = readBigIntFromBuffer(await sha256(Buffer.concat([ pForHash, gForHash ])), false)
|
const k = readBigIntFromBuffer(await sha256(Buffer.concat([pForHash, gForHash])), false);
|
||||||
const kgX = bigIntMod(k.multiply(gX),p)
|
const kgX = bigIntMod(k.multiply(gX), p);
|
||||||
const generateAndCheckRandom =async () => {
|
const generateAndCheckRandom = async () => {
|
||||||
const randomSize = 256
|
const randomSize = 256;
|
||||||
// eslint-disable-next-line no-constant-condition
|
// eslint-disable-next-line no-constant-condition
|
||||||
while (true) {
|
while (true) {
|
||||||
const random = generateRandomBytes(randomSize)
|
const random = generateRandomBytes(randomSize);
|
||||||
const a = readBigIntFromBuffer(random, false)
|
const a = readBigIntFromBuffer(random, false);
|
||||||
const A = modExp(BigInt(g), a, p)
|
const A = modExp(BigInt(g), a, p);
|
||||||
if (isGoodModExpFirst(A, p)) {
|
if (isGoodModExpFirst(A, p)) {
|
||||||
const aForHash = bigNumForHash(A)
|
const aForHash = bigNumForHash(A);
|
||||||
const u = readBigIntFromBuffer(await sha256(Buffer.concat([ aForHash, bForHash ])), false)
|
const u = readBigIntFromBuffer(await sha256(Buffer.concat([aForHash, bForHash])), false);
|
||||||
if (u.greater(BigInt(0))) {
|
if (u.greater(BigInt(0))) {
|
||||||
return [ a, aForHash, u ]
|
return [a, aForHash, u];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
const [ a, aForHash, u ] =await generateAndCheckRandom()
|
const [a, aForHash, u] = await generateAndCheckRandom();
|
||||||
const gB = bigIntMod(B.subtract(kgX),p)
|
const gB = bigIntMod(B.subtract(kgX), p);
|
||||||
if (!isGoodModExpFirst(gB, p)) {
|
if (!isGoodModExpFirst(gB, p)) {
|
||||||
throw new Error('bad gB')
|
throw new Error('bad gB');
|
||||||
}
|
}
|
||||||
|
|
||||||
const ux = u.multiply(x)
|
const ux = u.multiply(x);
|
||||||
const aUx = a.add(ux)
|
const aUx = a.add(ux);
|
||||||
const S = modExp(gB, aUx, p)
|
const S = modExp(gB, aUx, p);
|
||||||
const [K, pSha ,gSha, salt1Sha, salt2Sha] = await Promise.all([
|
const [K, pSha, gSha, salt1Sha, salt2Sha] = await Promise.all([
|
||||||
sha256(bigNumForHash(S)),
|
sha256(bigNumForHash(S)),
|
||||||
sha256(pForHash),
|
sha256(pForHash),
|
||||||
sha256(gForHash),
|
sha256(gForHash),
|
||||||
sha256(algo.salt1),
|
sha256(algo.salt1),
|
||||||
sha256(algo.salt2)
|
sha256(algo.salt2),
|
||||||
])
|
]);
|
||||||
const M1 = await sha256(Buffer.concat([
|
const M1 = await sha256(Buffer.concat([
|
||||||
xor(pSha,gSha),
|
xor(pSha, gSha),
|
||||||
salt1Sha,
|
salt1Sha,
|
||||||
salt2Sha,
|
salt2Sha,
|
||||||
aForHash,
|
aForHash,
|
||||||
bForHash,
|
bForHash,
|
||||||
K,
|
K,
|
||||||
]))
|
]));
|
||||||
|
|
||||||
|
|
||||||
return new constructors.InputCheckPasswordSRP({
|
return new constructors.InputCheckPasswordSRP({
|
||||||
@ -265,11 +273,11 @@ async function computeCheck(request, password) {
|
|||||||
A: Buffer.from(aForHash),
|
A: Buffer.from(aForHash),
|
||||||
M1: M1,
|
M1: M1,
|
||||||
|
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
computeCheck,
|
computeCheck,
|
||||||
computeDigest,
|
computeDigest,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
const { constructors } = require('./tl')
|
const { constructors } = require('./tl');
|
||||||
|
|
||||||
const USERNAME_RE = new RegExp('@|(?:https?:\\/\\/)?(?:www\\.)?' +
|
const USERNAME_RE = new RegExp('@|(?:https?:\\/\\/)?(?:www\\.)?' +
|
||||||
'(?:telegram\\.(?:me|dog)|t\\.me)\\/(@|joinchat\\/)?')
|
'(?:telegram\\.(?:me|dog)|t\\.me)\\/(@|joinchat\\/)?');
|
||||||
|
|
||||||
const JPEG_HEADER = Buffer.from('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00', 'hex')
|
const JPEG_HEADER = Buffer.from('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00', 'hex');
|
||||||
const JPEG_FOOTER = Buffer.from('ffd9', 'hex')
|
const JPEG_FOOTER = Buffer.from('ffd9', 'hex');
|
||||||
|
|
||||||
const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite=')
|
const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite=');
|
||||||
|
|
||||||
const VALID_USERNAME_RE = new RegExp('^([a-z]((?!__)[\\w\\d]){3,30}[a-z\\d]|gif|vid|' +
|
const VALID_USERNAME_RE = new RegExp('^([a-z]((?!__)[\\w\\d]){3,30}[a-z\\d]|gif|vid|' +
|
||||||
'pic|bing|wiki|imdb|bold|vote|like|coub)$')
|
'pic|bing|wiki|imdb|bold|vote|like|coub)$');
|
||||||
|
|
||||||
function _raiseCastFail(entity, target) {
|
function _raiseCastFail(entity, target) {
|
||||||
throw new Error(`Cannot cast ${entity.className} to any kind of ${target}`)
|
throw new Error(`Cannot cast ${entity.className} to any kind of ${target}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,41 +37,41 @@ function getInputPeer(entity, allowSelf = true, checkHash = true) {
|
|||||||
// e.g. custom.Dialog (can't cyclic import).
|
// e.g. custom.Dialog (can't cyclic import).
|
||||||
|
|
||||||
if (allowSelf && 'inputEntity' in entity) {
|
if (allowSelf && 'inputEntity' in entity) {
|
||||||
return entity.inputEntity
|
return entity.inputEntity;
|
||||||
} else if ('entity' in entity) {
|
} else if ('entity' in entity) {
|
||||||
return getInputPeer(entity.entity)
|
return getInputPeer(entity.entity);
|
||||||
} else {
|
} else {
|
||||||
_raiseCastFail(entity, 'InputPeer')
|
_raiseCastFail(entity, 'InputPeer');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (entity.SUBCLASS_OF_ID === 0xc91c90b6) { // crc32(b'InputPeer')
|
if (entity.SUBCLASS_OF_ID === 0xc91c90b6) { // crc32(b'InputPeer')
|
||||||
return entity
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity instanceof constructors.User) {
|
if (entity instanceof constructors.User) {
|
||||||
if (entity.isSelf && allowSelf) {
|
if (entity.isSelf && allowSelf) {
|
||||||
return new constructors.InputPeerSelf()
|
return new constructors.InputPeerSelf();
|
||||||
} else if (entity.accessHash !== undefined || !checkHash) {
|
} else if (entity.accessHash !== undefined || !checkHash) {
|
||||||
return new constructors.InputPeerUser({
|
return new constructors.InputPeerUser({
|
||||||
userId: entity.id,
|
userId: entity.id,
|
||||||
accessHash: entity.accessHash,
|
accessHash: entity.accessHash,
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('User without accessHash or min info cannot be input')
|
throw new Error('User without accessHash or min info cannot be input');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (entity instanceof constructors.Chat || entity instanceof constructors.ChatEmpty ||
|
if (entity instanceof constructors.Chat || entity instanceof constructors.ChatEmpty ||
|
||||||
entity instanceof constructors.ChatForbidden) {
|
entity instanceof constructors.ChatForbidden) {
|
||||||
return new constructors.InputPeerChat({ chatId: entity.id })
|
return new constructors.InputPeerChat({ chatId: entity.id });
|
||||||
}
|
}
|
||||||
if (entity instanceof constructors.Channel) {
|
if (entity instanceof constructors.Channel) {
|
||||||
if (entity.accessHash !== undefined || !checkHash) {
|
if (entity.accessHash !== undefined || !checkHash) {
|
||||||
return new constructors.InputPeerChannel({
|
return new constructors.InputPeerChannel({
|
||||||
channelId: entity.id,
|
channelId: entity.id,
|
||||||
accessHash: entity.accessHash
|
accessHash: entity.accessHash,
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new TypeError('Channel without accessHash or min info cannot be input')
|
throw new TypeError('Channel without accessHash or min info cannot be input');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (entity instanceof constructors.ChannelForbidden) {
|
if (entity instanceof constructors.ChannelForbidden) {
|
||||||
@ -79,38 +79,38 @@ function getInputPeer(entity, allowSelf = true, checkHash = true) {
|
|||||||
// also not optional, we assume that this truly is the case.
|
// also not optional, we assume that this truly is the case.
|
||||||
return new constructors.InputPeerChannel({
|
return new constructors.InputPeerChannel({
|
||||||
channelId: entity.id,
|
channelId: entity.id,
|
||||||
accessHash: entity.accessHash
|
accessHash: entity.accessHash,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity instanceof constructors.InputUser) {
|
if (entity instanceof constructors.InputUser) {
|
||||||
return new constructors.InputPeerUser({
|
return new constructors.InputPeerUser({
|
||||||
userId: entity.userId,
|
userId: entity.userId,
|
||||||
accessHash: entity.accessHash
|
accessHash: entity.accessHash,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
if (entity instanceof constructors.InputChannel) {
|
if (entity instanceof constructors.InputChannel) {
|
||||||
return new constructors.InputPeerChannel({
|
return new constructors.InputPeerChannel({
|
||||||
channelId: entity.channelId,
|
channelId: entity.channelId,
|
||||||
accessHash: entity.accessHash
|
accessHash: entity.accessHash,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
if (entity instanceof constructors.UserEmpty) {
|
if (entity instanceof constructors.UserEmpty) {
|
||||||
return new constructors.InputPeerEmpty()
|
return new constructors.InputPeerEmpty();
|
||||||
}
|
}
|
||||||
if (entity instanceof constructors.UserFull) {
|
if (entity instanceof constructors.UserFull) {
|
||||||
return getInputPeer(entity.user)
|
return getInputPeer(entity.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity instanceof constructors.ChatFull) {
|
if (entity instanceof constructors.ChatFull) {
|
||||||
return new constructors.InputPeerChat({ chatId: entity.id })
|
return new constructors.InputPeerChat({ chatId: entity.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity instanceof constructors.PeerChat) {
|
if (entity instanceof constructors.PeerChat) {
|
||||||
return new constructors.InputPeerChat(entity.chatId)
|
return new constructors.InputPeerChat(entity.chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_raiseCastFail(entity, 'InputPeer')
|
_raiseCastFail(entity, 'InputPeer');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -228,6 +228,7 @@ function getInputDialog(dialog) {
|
|||||||
_raiseCastFail(dialog, 'InputDialogPeer')
|
_raiseCastFail(dialog, 'InputDialogPeer')
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*CONTEST
|
/*CONTEST
|
||||||
|
|
||||||
function getInputMessage(message) {
|
function getInputMessage(message) {
|
||||||
@ -259,12 +260,12 @@ function getInputMessage(message) {
|
|||||||
function strippedPhotoToJpg(stripped) {
|
function strippedPhotoToJpg(stripped) {
|
||||||
// Note: Changes here should update _stripped_real_length
|
// Note: Changes here should update _stripped_real_length
|
||||||
if (stripped.length < 3 || stripped[0] !== 1) {
|
if (stripped.length < 3 || stripped[0] !== 1) {
|
||||||
return stripped
|
return stripped;
|
||||||
}
|
}
|
||||||
const header = Buffer.from(JPEG_HEADER)
|
const header = Buffer.from(JPEG_HEADER);
|
||||||
header[164] = stripped[1]
|
header[164] = stripped[1];
|
||||||
header[166] = stripped[2]
|
header[166] = stripped[2];
|
||||||
return Buffer.concat([header, stripped.slice(3), JPEG_FOOTER])
|
return Buffer.concat([header, stripped.slice(3), JPEG_FOOTER]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*CONTEST
|
/*CONTEST
|
||||||
@ -329,16 +330,16 @@ function getInputLocation(location) {
|
|||||||
*/
|
*/
|
||||||
function getAppropriatedPartSize(fileSize) {
|
function getAppropriatedPartSize(fileSize) {
|
||||||
if (fileSize <= 104857600) { // 100MB
|
if (fileSize <= 104857600) { // 100MB
|
||||||
return 128
|
return 128;
|
||||||
}
|
}
|
||||||
if (fileSize <= 786432000) { // 750MB
|
if (fileSize <= 786432000) { // 750MB
|
||||||
return 256
|
return 256;
|
||||||
}
|
}
|
||||||
if (fileSize <= 1572864000) { // 1500MB
|
if (fileSize <= 1572864000) { // 1500MB
|
||||||
return 512
|
return 512;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('File size too large')
|
throw new Error('File size too large');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*CONTEST
|
/*CONTEST
|
||||||
@ -480,6 +481,7 @@ function resolveId(markedId) {
|
|||||||
* @returns {{inputEntity: *, entity: *}}
|
* @returns {{inputEntity: *, entity: *}}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*CONTEST
|
/*CONTEST
|
||||||
|
|
||||||
function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer) {
|
function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer) {
|
||||||
@ -501,15 +503,15 @@ function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer)
|
|||||||
|
|
||||||
function getMessageId(message) {
|
function getMessageId(message) {
|
||||||
if (message === null || message === undefined) {
|
if (message === null || message === undefined) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
if (typeof message == 'number') {
|
if (typeof message == 'number') {
|
||||||
return message
|
return message;
|
||||||
}
|
}
|
||||||
if (message.SUBCLASS_OF_ID === 0x790009e3) { // crc32(b'Message')
|
if (message.SUBCLASS_OF_ID === 0x790009e3) { // crc32(b'Message')
|
||||||
return message.id
|
return message.id;
|
||||||
}
|
}
|
||||||
throw new Error(`Invalid message type: ${message.constructor.name}`)
|
throw new Error(`Invalid message type: ${message.constructor.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -518,7 +520,8 @@ function getMessageId(message) {
|
|||||||
* @param phone
|
* @param phone
|
||||||
*/
|
*/
|
||||||
function parsePhone(phone) {
|
function parsePhone(phone) {
|
||||||
return phone.toString().replace(/[+()\s-]/gm, '')
|
return phone.toString()
|
||||||
|
.replace(/[+()\s-]/gm, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -531,6 +534,7 @@ function parsePhone(phone) {
|
|||||||
|
|
||||||
* @param username {string}
|
* @param username {string}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*CONTEST
|
/*CONTEST
|
||||||
|
|
||||||
function parseUsername(username) {
|
function parseUsername(username) {
|
||||||
@ -577,18 +581,18 @@ function rtrim(s, mask) {
|
|||||||
function getDisplayName(entity) {
|
function getDisplayName(entity) {
|
||||||
if (entity instanceof constructors.User) {
|
if (entity instanceof constructors.User) {
|
||||||
if (entity.lastName && entity.firstName) {
|
if (entity.lastName && entity.firstName) {
|
||||||
return `${entity.firstName} ${entity.lastName}`
|
return `${entity.firstName} ${entity.lastName}`;
|
||||||
} else if (entity.firstName) {
|
} else if (entity.firstName) {
|
||||||
return entity.firstName
|
return entity.firstName;
|
||||||
} else if (entity.lastName) {
|
} else if (entity.lastName) {
|
||||||
return entity.lastName
|
return entity.lastName;
|
||||||
} else {
|
} else {
|
||||||
return ''
|
return '';
|
||||||
}
|
}
|
||||||
} else if (entity instanceof constructors.Chat || entity instanceof constructors.Channel) {
|
} else if (entity instanceof constructors.Chat || entity instanceof constructors.Channel) {
|
||||||
return entity.title
|
return entity.title;
|
||||||
}
|
}
|
||||||
return ''
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -596,6 +600,7 @@ function getDisplayName(entity) {
|
|||||||
* @param item
|
* @param item
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*CONTEST
|
/*CONTEST
|
||||||
Duplicate ?
|
Duplicate ?
|
||||||
function isListLike(item) {
|
function isListLike(item) {
|
||||||
@ -618,34 +623,34 @@ function getDC(dcId, cdn = false) {
|
|||||||
return {
|
return {
|
||||||
id: 1,
|
id: 1,
|
||||||
ipAddress: 'pluto.web.telegram.org',
|
ipAddress: 'pluto.web.telegram.org',
|
||||||
port: 443
|
port: 443,
|
||||||
}
|
};
|
||||||
case 2:
|
case 2:
|
||||||
return {
|
return {
|
||||||
id: 2,
|
id: 2,
|
||||||
ipAddress: 'venus.web.telegram.org',
|
ipAddress: 'venus.web.telegram.org',
|
||||||
port: 443
|
port: 443,
|
||||||
}
|
};
|
||||||
case 3:
|
case 3:
|
||||||
return {
|
return {
|
||||||
id: 3,
|
id: 3,
|
||||||
ipAddress: 'aurora.web.telegram.org',
|
ipAddress: 'aurora.web.telegram.org',
|
||||||
port: 443
|
port: 443,
|
||||||
}
|
};
|
||||||
case 4:
|
case 4:
|
||||||
return {
|
return {
|
||||||
id: 4,
|
id: 4,
|
||||||
ipAddress: 'vesta.web.telegram.org',
|
ipAddress: 'vesta.web.telegram.org',
|
||||||
port: 443
|
port: 443,
|
||||||
}
|
};
|
||||||
case 5:
|
case 5:
|
||||||
return {
|
return {
|
||||||
id: 5,
|
id: 5,
|
||||||
ipAddress: 'flora.web.telegram.org',
|
ipAddress: 'flora.web.telegram.org',
|
||||||
port: 443
|
port: 443,
|
||||||
}
|
};
|
||||||
default:
|
default:
|
||||||
throw new Error(`Cannot find the DC with the ID of ${dcId}`)
|
throw new Error(`Cannot find the DC with the ID of ${dcId}`);
|
||||||
}
|
}
|
||||||
// TODO chose based on current connection method
|
// TODO chose based on current connection method
|
||||||
/*
|
/*
|
||||||
@ -683,5 +688,5 @@ module.exports = {
|
|||||||
getAppropriatedPartSize,
|
getAppropriatedPartSize,
|
||||||
//getInputLocation,
|
//getInputLocation,
|
||||||
strippedPhotoToJpg,
|
strippedPhotoToJpg,
|
||||||
getDC
|
getDC,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -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 { BotAuthParams, UserAuthParams } from './auth';
|
||||||
import { uploadFile, UploadFileParams } from './uploadFile';
|
import { uploadFile, UploadFileParams } from './uploadFile';
|
||||||
import { downloadFile, DownloadFileParams } from './downloadFile';
|
import { downloadFile, DownloadFileParams } from './downloadFile';
|
||||||
import { updateTwoFaSettings, TwoFaParams } from './2fa';
|
import { TwoFaParams, updateTwoFaSettings } from './2fa';
|
||||||
|
|
||||||
declare class TelegramClient {
|
declare class TelegramClient {
|
||||||
constructor(...args: any)
|
constructor(...args: any)
|
||||||
|
|||||||
@ -1,28 +1,34 @@
|
|||||||
const Logger = require('../extensions/Logger')
|
const Logger = require('../extensions/Logger');
|
||||||
const { sleep } = require('../Helpers')
|
const { sleep } = require('../Helpers');
|
||||||
const errors = require('../errors')
|
const errors = require('../errors');
|
||||||
const MemorySession = require('../sessions/Memory')
|
const MemorySession = require('../sessions/Memory');
|
||||||
const Helpers = require('../Helpers')
|
const Helpers = require('../Helpers');
|
||||||
const { BinaryWriter } = require('../extensions')
|
const { BinaryWriter } = require('../extensions');
|
||||||
const utils = require('../Utils')
|
const utils = require('../Utils');
|
||||||
const Session = require('../sessions/Abstract')
|
const Session = require('../sessions/Abstract');
|
||||||
const os = require('os')
|
const os = require('os');
|
||||||
const { LAYER } = require('../tl/AllTLObjects')
|
const { LAYER } = require('../tl/AllTLObjects');
|
||||||
const { constructors, requests } = require('../tl')
|
const {
|
||||||
const MTProtoSender = require('../network/MTProtoSender')
|
constructors,
|
||||||
const { UpdateConnectionState } = require("../network")
|
requests,
|
||||||
const { ConnectionTCPObfuscated } = require('../network/connection/TCPObfuscated')
|
} = require('../tl');
|
||||||
const { authFlow, checkAuthorization } = require('./auth')
|
const MTProtoSender = require('../network/MTProtoSender');
|
||||||
const { downloadFile } = require('./downloadFile')
|
const { UpdateConnectionState } = require('../network');
|
||||||
const { uploadFile } = require('./uploadFile')
|
const { ConnectionTCPObfuscated } = require('../network/connection/TCPObfuscated');
|
||||||
const { updateTwoFaSettings } = require('./2fa')
|
const {
|
||||||
|
authFlow,
|
||||||
|
checkAuthorization,
|
||||||
|
} = require('./auth');
|
||||||
|
const { downloadFile } = require('./downloadFile');
|
||||||
|
const { uploadFile } = require('./uploadFile');
|
||||||
|
const { updateTwoFaSettings } = require('./2fa');
|
||||||
|
|
||||||
const DEFAULT_DC_ID = 2
|
const DEFAULT_DC_ID = 2;
|
||||||
const DEFAULT_IPV4_IP = 'venus.web.telegram.org'
|
const DEFAULT_IPV4_IP = 'venus.web.telegram.org';
|
||||||
const DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]'
|
const DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]';
|
||||||
|
|
||||||
// All types
|
// All types
|
||||||
const sizeTypes = ['w', 'y', 'd', 'x', 'c', 'm', 'b', 'a', 's']
|
const sizeTypes = ['w', 'y', 'd', 'x', 'c', 'm', 'b', 'a', 's'];
|
||||||
|
|
||||||
|
|
||||||
class TelegramClient {
|
class TelegramClient {
|
||||||
@ -45,7 +51,7 @@ class TelegramClient {
|
|||||||
baseLogger: 'gramjs',
|
baseLogger: 'gramjs',
|
||||||
useWSS: false,
|
useWSS: false,
|
||||||
additionalDcsDisabled: false,
|
additionalDcsDisabled: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -56,52 +62,52 @@ class TelegramClient {
|
|||||||
*/
|
*/
|
||||||
constructor(session, apiId, apiHash, opts = TelegramClient.DEFAULT_OPTIONS) {
|
constructor(session, apiId, apiHash, opts = TelegramClient.DEFAULT_OPTIONS) {
|
||||||
if (apiId === undefined || apiHash === undefined) {
|
if (apiId === undefined || apiHash === undefined) {
|
||||||
throw Error('Your API ID or Hash are invalid. Please read "Requirements" on README.md')
|
throw Error('Your API ID or Hash are invalid. Please read "Requirements" on README.md');
|
||||||
}
|
}
|
||||||
const args = { ...TelegramClient.DEFAULT_OPTIONS, ...opts }
|
const args = { ...TelegramClient.DEFAULT_OPTIONS, ...opts };
|
||||||
this.apiId = apiId
|
this.apiId = apiId;
|
||||||
this.apiHash = apiHash
|
this.apiHash = apiHash;
|
||||||
this._useIPV6 = args.useIPV6
|
this._useIPV6 = args.useIPV6;
|
||||||
// this._entityCache = new Set()
|
// this._entityCache = new Set()
|
||||||
if (typeof args.baseLogger == 'string') {
|
if (typeof args.baseLogger == 'string') {
|
||||||
this._log = new Logger()
|
this._log = new Logger();
|
||||||
} else {
|
} else {
|
||||||
this._log = args.baseLogger
|
this._log = args.baseLogger;
|
||||||
}
|
}
|
||||||
// Determine what session we will use
|
// Determine what session we will use
|
||||||
if (typeof session === 'string' || !session) {
|
if (typeof session === 'string' || !session) {
|
||||||
try {
|
try {
|
||||||
throw new Error('not implemented')
|
throw new Error('not implemented');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
session = new MemorySession()
|
session = new MemorySession();
|
||||||
}
|
}
|
||||||
} else if (!(session instanceof Session)) {
|
} else if (!(session instanceof Session)) {
|
||||||
throw new Error('The given session must be str or a session instance')
|
throw new Error('The given session must be str or a session instance');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.floodSleepLimit = args.floodSleepLimit
|
this.floodSleepLimit = args.floodSleepLimit;
|
||||||
this._eventBuilders = []
|
this._eventBuilders = [];
|
||||||
|
|
||||||
this._phoneCodeHash = {}
|
this._phoneCodeHash = {};
|
||||||
this.session = session
|
this.session = session;
|
||||||
// this._entityCache = EntityCache();
|
// this._entityCache = EntityCache();
|
||||||
this.apiId = parseInt(apiId)
|
this.apiId = parseInt(apiId);
|
||||||
this.apiHash = apiHash
|
this.apiHash = apiHash;
|
||||||
|
|
||||||
this._requestRetries = args.requestRetries
|
this._requestRetries = args.requestRetries;
|
||||||
this._connectionRetries = args.connectionRetries
|
this._connectionRetries = args.connectionRetries;
|
||||||
this._retryDelay = args.retryDelay || 0
|
this._retryDelay = args.retryDelay || 0;
|
||||||
if (args.proxy) {
|
if (args.proxy) {
|
||||||
this._log.warn('proxies are not supported')
|
this._log.warn('proxies are not supported');
|
||||||
}
|
}
|
||||||
this._proxy = args.proxy
|
this._proxy = args.proxy;
|
||||||
this._timeout = args.timeout
|
this._timeout = args.timeout;
|
||||||
this._autoReconnect = args.autoReconnect
|
this._autoReconnect = args.autoReconnect;
|
||||||
|
|
||||||
this._connection = args.connection
|
this._connection = args.connection;
|
||||||
// TODO add proxy support
|
// TODO add proxy support
|
||||||
|
|
||||||
this._floodWaitedRequests = {}
|
this._floodWaitedRequests = {};
|
||||||
|
|
||||||
this._initWith = (x) => {
|
this._initWith = (x) => {
|
||||||
return new requests.InvokeWithLayer({
|
return new requests.InvokeWithLayer({
|
||||||
@ -119,14 +125,14 @@ class TelegramClient {
|
|||||||
query: x,
|
query: x,
|
||||||
proxy: null, // no proxies yet.
|
proxy: null, // no proxies yet.
|
||||||
}),
|
}),
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
this._args = args
|
this._args = args;
|
||||||
// These will be set later
|
// These will be set later
|
||||||
this._config = null
|
this._config = null;
|
||||||
this.phoneCodeHashes = []
|
this.phoneCodeHashes = [];
|
||||||
this._borrowedSenderPromises = {}
|
this._borrowedSenderPromises = {};
|
||||||
this._additionalDcsDisabled = args.additionalDcsDisabled;
|
this._additionalDcsDisabled = args.additionalDcsDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +146,7 @@ class TelegramClient {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async connect() {
|
async connect() {
|
||||||
await this._initSession()
|
await this._initSession();
|
||||||
|
|
||||||
this._sender = new MTProtoSender(this.session.getAuthKey(), {
|
this._sender = new MTProtoSender(this.session.getAuthKey(), {
|
||||||
logger: this._log,
|
logger: this._log,
|
||||||
@ -152,45 +158,45 @@ class TelegramClient {
|
|||||||
authKeyCallback: this._authKeyCallback.bind(this),
|
authKeyCallback: this._authKeyCallback.bind(this),
|
||||||
updateCallback: this._handleUpdate.bind(this),
|
updateCallback: this._handleUpdate.bind(this),
|
||||||
isMainSender: true,
|
isMainSender: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
const connection = new this._connection(
|
const connection = new this._connection(
|
||||||
this.session.serverAddress, this.session.port, this.session.dcId, this._log
|
this.session.serverAddress, this.session.port, this.session.dcId, this._log,
|
||||||
)
|
);
|
||||||
|
|
||||||
await this._sender.connect(connection)
|
await this._sender.connect(connection);
|
||||||
|
|
||||||
this.session.setAuthKey(this._sender.authKey)
|
this.session.setAuthKey(this._sender.authKey);
|
||||||
await this._sender.send(this._initWith(
|
await this._sender.send(this._initWith(
|
||||||
new requests.help.GetConfig({}),
|
new requests.help.GetConfig({}),
|
||||||
))
|
));
|
||||||
|
|
||||||
this._updateLoop()
|
this._updateLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _initSession() {
|
async _initSession() {
|
||||||
await this.session.load()
|
await this.session.load();
|
||||||
|
|
||||||
if (!this.session.serverAddress || (this.session.serverAddress.includes(':') !== this._useIPV6)) {
|
if (!this.session.serverAddress || (this.session.serverAddress.includes(':') !== this._useIPV6)) {
|
||||||
this.session.setDC(DEFAULT_DC_ID, this._useIPV6 ? DEFAULT_IPV6_IP : DEFAULT_IPV4_IP, this._args.useWSS ? 443 : 80)
|
this.session.setDC(DEFAULT_DC_ID, this._useIPV6 ? DEFAULT_IPV6_IP : DEFAULT_IPV4_IP, this._args.useWSS ? 443 : 80);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateLoop() {
|
async _updateLoop() {
|
||||||
while (this.isConnected()) {
|
while (this.isConnected()) {
|
||||||
await Helpers.sleep(3 * 1000)
|
await Helpers.sleep(3 * 1000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await attempts(() => {
|
await attempts(() => {
|
||||||
return timeout(this._sender.send(new requests.Ping({
|
return timeout(this._sender.send(new requests.Ping({
|
||||||
pingId: Helpers.getRandomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)
|
pingId: Helpers.getRandomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER),
|
||||||
})), 1500)
|
})), 1500);
|
||||||
}, 3, 100)
|
}, 3, 100);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await this.disconnect()
|
await this.disconnect();
|
||||||
this.connect()
|
this.connect();
|
||||||
|
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to send some content-related request at least hourly
|
// We need to send some content-related request at least hourly
|
||||||
@ -200,7 +206,7 @@ class TelegramClient {
|
|||||||
// TODO Call getDifference instead since it's more relevant
|
// TODO Call getDifference instead since it's more relevant
|
||||||
if (new Date().getTime() - this._lastRequest > 30 * 60 * 1000) {
|
if (new Date().getTime() - this._lastRequest > 30 * 60 * 1000) {
|
||||||
try {
|
try {
|
||||||
await this.invoke(new requests.updates.GetState())
|
await this.invoke(new requests.updates.GetState());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -214,20 +220,21 @@ class TelegramClient {
|
|||||||
*/
|
*/
|
||||||
async disconnect() {
|
async disconnect() {
|
||||||
if (this._sender) {
|
if (this._sender) {
|
||||||
await this._sender.disconnect()
|
await this._sender.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.values(this._borrowedSenderPromises).map((promise) => {
|
Object.values(this._borrowedSenderPromises)
|
||||||
return promise.then((sender) => {
|
.map((promise) => {
|
||||||
if (sender) {
|
return promise.then((sender) => {
|
||||||
return sender.disconnect()
|
if (sender) {
|
||||||
}
|
return sender.disconnect();
|
||||||
})
|
}
|
||||||
})
|
});
|
||||||
)
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this._borrowedSenderPromises = {}
|
this._borrowedSenderPromises = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -235,55 +242,55 @@ class TelegramClient {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async destroy() {
|
async destroy() {
|
||||||
await this.disconnect()
|
await this.disconnect();
|
||||||
this.session.delete()
|
this.session.delete();
|
||||||
this._eventBuilders = []
|
this._eventBuilders = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async _switchDC(newDc) {
|
async _switchDC(newDc) {
|
||||||
this._log.info(`Reconnecting to new data center ${newDc}`)
|
this._log.info(`Reconnecting to new data center ${newDc}`);
|
||||||
const DC = utils.getDC(newDc)
|
const DC = utils.getDC(newDc);
|
||||||
this.session.setDC(newDc, DC.ipAddress, DC.port)
|
this.session.setDC(newDc, DC.ipAddress, DC.port);
|
||||||
// authKey's are associated with a server, which has now changed
|
// authKey's are associated with a server, which has now changed
|
||||||
// so it's not valid anymore. Set to None to force recreating it.
|
// so it's not valid anymore. Set to None to force recreating it.
|
||||||
await this._sender.authKey.setKey(null)
|
await this._sender.authKey.setKey(null);
|
||||||
this.session.setAuthKey(null)
|
this.session.setAuthKey(null);
|
||||||
await this.disconnect()
|
await this.disconnect();
|
||||||
return this.connect()
|
return this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _authKeyCallback(authKey, dcId) {
|
async _authKeyCallback(authKey, dcId) {
|
||||||
this.session.setAuthKey(authKey, dcId)
|
this.session.setAuthKey(authKey, dcId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
// export region
|
// export region
|
||||||
|
|
||||||
_cleanupBorrowedSender(dcId) {
|
_cleanupBorrowedSender(dcId) {
|
||||||
delete this._borrowedSenderPromises[dcId]
|
delete this._borrowedSenderPromises[dcId];
|
||||||
}
|
}
|
||||||
|
|
||||||
async _borrowExportedSender(dcId) {
|
async _borrowExportedSender(dcId) {
|
||||||
if (this._additionalDcsDisabled) {
|
if (this._additionalDcsDisabled) {
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let senderPromise = this._borrowedSenderPromises[dcId]
|
let senderPromise = this._borrowedSenderPromises[dcId];
|
||||||
if (!senderPromise) {
|
if (!senderPromise) {
|
||||||
senderPromise = this._createExportedSender(dcId)
|
senderPromise = this._createExportedSender(dcId);
|
||||||
this._borrowedSenderPromises[dcId] = senderPromise
|
this._borrowedSenderPromises[dcId] = senderPromise;
|
||||||
|
|
||||||
senderPromise.then((sender) => {
|
senderPromise.then((sender) => {
|
||||||
if (!sender) {
|
if (!sender) {
|
||||||
delete this._borrowedSenderPromises[dcId]
|
delete this._borrowedSenderPromises[dcId];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return senderPromise
|
return senderPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _createExportedSender(dcId) {
|
async _createExportedSender(dcId) {
|
||||||
const dc = utils.getDC(dcId)
|
const dc = utils.getDC(dcId);
|
||||||
const sender = new MTProtoSender(this.session.getAuthKey(dcId),
|
const sender = new MTProtoSender(this.session.getAuthKey(dcId),
|
||||||
{
|
{
|
||||||
logger: this._log,
|
logger: this._log,
|
||||||
@ -295,7 +302,7 @@ class TelegramClient {
|
|||||||
authKeyCallback: this._authKeyCallback.bind(this),
|
authKeyCallback: this._authKeyCallback.bind(this),
|
||||||
isMainSender: dcId === this.session.dcId,
|
isMainSender: dcId === this.session.dcId,
|
||||||
onConnectionBreak: this._cleanupBorrowedSender.bind(this),
|
onConnectionBreak: this._cleanupBorrowedSender.bind(this),
|
||||||
})
|
});
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
try {
|
try {
|
||||||
await sender.connect(new this._connection(
|
await sender.connect(new this._connection(
|
||||||
@ -303,24 +310,24 @@ class TelegramClient {
|
|||||||
dc.port,
|
dc.port,
|
||||||
dcId,
|
dcId,
|
||||||
this._log,
|
this._log,
|
||||||
))
|
));
|
||||||
if (this.session.dcId !== dcId) {
|
if (this.session.dcId !== dcId) {
|
||||||
this._log.info(`Exporting authorization for data center ${dc.ipAddress}`)
|
this._log.info(`Exporting authorization for data center ${dc.ipAddress}`);
|
||||||
const auth = await this.invoke(new requests.auth.ExportAuthorization({ dcId: dcId }))
|
const auth = await this.invoke(new requests.auth.ExportAuthorization({ dcId: dcId }));
|
||||||
const req = this._initWith(new requests.auth.ImportAuthorization({
|
const req = this._initWith(new requests.auth.ImportAuthorization({
|
||||||
id: auth.id,
|
id: auth.id,
|
||||||
bytes: auth.bytes,
|
bytes: auth.bytes,
|
||||||
},
|
},
|
||||||
))
|
));
|
||||||
await sender.send(req)
|
await sender.send(req);
|
||||||
}
|
}
|
||||||
sender.dcId = dcId
|
sender.dcId = dcId;
|
||||||
return sender
|
return sender;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await sender.disconnect()
|
await sender.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// end region
|
// end region
|
||||||
@ -340,85 +347,85 @@ class TelegramClient {
|
|||||||
* @returns {Promise<Buffer>}
|
* @returns {Promise<Buffer>}
|
||||||
*/
|
*/
|
||||||
async downloadFile(inputLocation, args = {}) {
|
async downloadFile(inputLocation, args = {}) {
|
||||||
return downloadFile(this, inputLocation, args)
|
return downloadFile(this, inputLocation, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadMedia(messageOrMedia, args) {
|
async downloadMedia(messageOrMedia, args) {
|
||||||
let date
|
let date;
|
||||||
let media
|
let media;
|
||||||
if (messageOrMedia instanceof constructors.Message) {
|
if (messageOrMedia instanceof constructors.Message) {
|
||||||
date = messageOrMedia.date
|
date = messageOrMedia.date;
|
||||||
media = messageOrMedia.media
|
media = messageOrMedia.media;
|
||||||
} else {
|
} else {
|
||||||
date = new Date().getTime()
|
date = new Date().getTime();
|
||||||
media = messageOrMedia
|
media = messageOrMedia;
|
||||||
}
|
}
|
||||||
if (typeof media == 'string') {
|
if (typeof media == 'string') {
|
||||||
throw new Error('not implemented')
|
throw new Error('not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media instanceof constructors.MessageMediaWebPage) {
|
if (media instanceof constructors.MessageMediaWebPage) {
|
||||||
if (media.webpage instanceof constructors.WebPage) {
|
if (media.webpage instanceof constructors.WebPage) {
|
||||||
media = media.webpage.document || media.webpage.photo
|
media = media.webpage.document || media.webpage.photo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (media instanceof constructors.MessageMediaPhoto || media instanceof constructors.Photo) {
|
if (media instanceof constructors.MessageMediaPhoto || media instanceof constructors.Photo) {
|
||||||
return this._downloadPhoto(media, args)
|
return this._downloadPhoto(media, args);
|
||||||
} else if (media instanceof constructors.MessageMediaDocument || media instanceof constructors.Document) {
|
} else if (media instanceof constructors.MessageMediaDocument || media instanceof constructors.Document) {
|
||||||
return this._downloadDocument(media, args)
|
return this._downloadDocument(media, args);
|
||||||
} else if (media instanceof constructors.MessageMediaContact) {
|
} else if (media instanceof constructors.MessageMediaContact) {
|
||||||
return this._downloadContact(media, args)
|
return this._downloadContact(media, args);
|
||||||
} else if (media instanceof constructors.WebDocument || media instanceof constructors.WebDocumentNoProxy) {
|
} else if (media instanceof constructors.WebDocument || media instanceof constructors.WebDocumentNoProxy) {
|
||||||
return this._downloadWebDocument(media, args)
|
return this._downloadWebDocument(media, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadProfilePhoto(entity, isBig = false) {
|
async downloadProfilePhoto(entity, isBig = false) {
|
||||||
// ('User', 'Chat', 'UserFull', 'ChatFull')
|
// ('User', 'Chat', 'UserFull', 'ChatFull')
|
||||||
const ENTITIES = [0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697]
|
const ENTITIES = [0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697];
|
||||||
// ('InputPeer', 'InputUser', 'InputChannel')
|
// ('InputPeer', 'InputUser', 'InputChannel')
|
||||||
// const INPUTS = [0xc91c90b6, 0xe669bf46, 0x40f202fd]
|
// const INPUTS = [0xc91c90b6, 0xe669bf46, 0x40f202fd]
|
||||||
// Todo account for input methods
|
// Todo account for input methods
|
||||||
const sizeType = isBig ? 'x' : 'm'
|
const sizeType = isBig ? 'x' : 'm';
|
||||||
let photo
|
let photo;
|
||||||
if (!(ENTITIES.includes(entity.SUBCLASS_OF_ID))) {
|
if (!(ENTITIES.includes(entity.SUBCLASS_OF_ID))) {
|
||||||
photo = entity
|
photo = entity;
|
||||||
} else {
|
} else {
|
||||||
if (!entity.photo) {
|
if (!entity.photo) {
|
||||||
// Special case: may be a ChatFull with photo:Photo
|
// Special case: may be a ChatFull with photo:Photo
|
||||||
if (!entity.chatPhoto) {
|
if (!entity.chatPhoto) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._downloadPhoto(
|
return this._downloadPhoto(
|
||||||
entity.chatPhoto, { sizeType },
|
entity.chatPhoto, { sizeType },
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
photo = entity.photo
|
photo = entity.photo;
|
||||||
}
|
}
|
||||||
|
|
||||||
let dcId
|
let dcId;
|
||||||
let loc
|
let loc;
|
||||||
if (photo instanceof constructors.UserProfilePhoto || photo instanceof constructors.ChatPhoto) {
|
if (photo instanceof constructors.UserProfilePhoto || photo instanceof constructors.ChatPhoto) {
|
||||||
dcId = photo.dcId
|
dcId = photo.dcId;
|
||||||
const size = isBig ? photo.photoBig : photo.photoSmall
|
const size = isBig ? photo.photoBig : photo.photoSmall;
|
||||||
loc = new constructors.InputPeerPhotoFileLocation({
|
loc = new constructors.InputPeerPhotoFileLocation({
|
||||||
peer: utils.getInputPeer(entity),
|
peer: utils.getInputPeer(entity),
|
||||||
localId: size.localId,
|
localId: size.localId,
|
||||||
volumeId: size.volumeId,
|
volumeId: size.volumeId,
|
||||||
big: isBig,
|
big: isBig,
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
// It doesn't make any sense to check if `photo` can be used
|
// It doesn't make any sense to check if `photo` can be used
|
||||||
// as input location, because then this method would be able
|
// as input location, because then this method would be able
|
||||||
// to "download the profile photo of a message", i.e. its
|
// to "download the profile photo of a message", i.e. its
|
||||||
// media which should be done with `download_media` instead.
|
// media which should be done with `download_media` instead.
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return this.downloadFile(loc, {
|
return this.downloadFile(loc, {
|
||||||
dcId: dcId,
|
dcId: dcId,
|
||||||
})
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO this should never raise
|
// TODO this should never raise
|
||||||
throw e;
|
throw e;
|
||||||
@ -440,65 +447,65 @@ class TelegramClient {
|
|||||||
|
|
||||||
async downloadStickerSetThumb(stickerSet) {
|
async downloadStickerSetThumb(stickerSet) {
|
||||||
if (!stickerSet.thumb || !stickerSet.thumb.location) {
|
if (!stickerSet.thumb || !stickerSet.thumb.location) {
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { location } = stickerSet.thumb
|
const { location } = stickerSet.thumb;
|
||||||
|
|
||||||
return this.downloadFile(
|
return this.downloadFile(
|
||||||
new constructors.InputStickerSetThumb({
|
new constructors.InputStickerSetThumb({
|
||||||
stickerset: new constructors.InputStickerSetID({
|
stickerset: new constructors.InputStickerSetID({
|
||||||
id: stickerSet.id,
|
id: stickerSet.id,
|
||||||
accessHash: stickerSet.accessHash
|
accessHash: stickerSet.accessHash,
|
||||||
}),
|
}),
|
||||||
localId: location.localId,
|
localId: location.localId,
|
||||||
volumeId: location.volumeId,
|
volumeId: location.volumeId,
|
||||||
}),
|
}),
|
||||||
{ dcId: stickerSet.thumbDcId }
|
{ dcId: stickerSet.thumbDcId },
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_pickFileSize(sizes, sizeType) {
|
_pickFileSize(sizes, sizeType) {
|
||||||
if (!sizeType || !sizes || !sizes.length) {
|
if (!sizeType || !sizes || !sizes.length) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
const indexOfSize = sizeTypes.indexOf(sizeType)
|
const indexOfSize = sizeTypes.indexOf(sizeType);
|
||||||
let size
|
let size;
|
||||||
for (let i = indexOfSize; i < sizeTypes.length; i++) {
|
for (let i = indexOfSize; i < sizeTypes.length; i++) {
|
||||||
size = sizes.find((s) => s.type === sizeTypes[i])
|
size = sizes.find((s) => s.type === sizeTypes[i]);
|
||||||
if (size) {
|
if (size) {
|
||||||
return size
|
return size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_downloadCachedPhotoSize(size) {
|
_downloadCachedPhotoSize(size) {
|
||||||
// No need to download anything, simply write the bytes
|
// No need to download anything, simply write the bytes
|
||||||
let data
|
let data;
|
||||||
if (size instanceof constructors.PhotoStrippedSize) {
|
if (size instanceof constructors.PhotoStrippedSize) {
|
||||||
data = utils.strippedPhotoToJpg(size.bytes)
|
data = utils.strippedPhotoToJpg(size.bytes);
|
||||||
} else {
|
} else {
|
||||||
data = size.bytes
|
data = size.bytes;
|
||||||
}
|
}
|
||||||
return data
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _downloadPhoto(photo, args) {
|
async _downloadPhoto(photo, args) {
|
||||||
if (photo instanceof constructors.MessageMediaPhoto) {
|
if (photo instanceof constructors.MessageMediaPhoto) {
|
||||||
photo = photo.photo
|
photo = photo.photo;
|
||||||
}
|
}
|
||||||
if (!(photo instanceof constructors.Photo)) {
|
if (!(photo instanceof constructors.Photo)) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const size = this._pickFileSize(photo.sizes, args.sizeType)
|
const size = this._pickFileSize(photo.sizes, args.sizeType);
|
||||||
if (!size || (size instanceof constructors.PhotoSizeEmpty)) {
|
if (!size || (size instanceof constructors.PhotoSizeEmpty)) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize) {
|
if (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize) {
|
||||||
return this._downloadCachedPhotoSize(size)
|
return this._downloadCachedPhotoSize(size);
|
||||||
}
|
}
|
||||||
return this.downloadFile(
|
return this.downloadFile(
|
||||||
new constructors.InputPhotoFileLocation({
|
new constructors.InputPhotoFileLocation({
|
||||||
@ -512,26 +519,26 @@ class TelegramClient {
|
|||||||
fileSize: size.size,
|
fileSize: size.size,
|
||||||
progressCallback: args.progressCallback,
|
progressCallback: args.progressCallback,
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _downloadDocument(doc, args) {
|
async _downloadDocument(doc, args) {
|
||||||
if (doc instanceof constructors.MessageMediaDocument) {
|
if (doc instanceof constructors.MessageMediaDocument) {
|
||||||
doc = doc.document
|
doc = doc.document;
|
||||||
}
|
}
|
||||||
if (!(doc instanceof constructors.Document)) {
|
if (!(doc instanceof constructors.Document)) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = null
|
let size = null;
|
||||||
if (args.sizeType) {
|
if (args.sizeType) {
|
||||||
size = doc.thumbs ? this._pickFileSize(doc.thumbs, args.sizeType) : null
|
size = doc.thumbs ? this._pickFileSize(doc.thumbs, args.sizeType) : null;
|
||||||
if (!size && doc.mimeType.startsWith('video/')) {
|
if (!size && doc.mimeType.startsWith('video/')) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size && (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize)) {
|
if (size && (size instanceof constructors.PhotoCachedSize || size instanceof constructors.PhotoStrippedSize)) {
|
||||||
return this._downloadCachedPhotoSize(size)
|
return this._downloadCachedPhotoSize(size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,15 +557,15 @@ class TelegramClient {
|
|||||||
dcId: doc.dcId,
|
dcId: doc.dcId,
|
||||||
workers: args.workers,
|
workers: args.workers,
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadContact(media, args) {
|
_downloadContact(media, args) {
|
||||||
throw new Error('not implemented')
|
throw new Error('not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadWebDocument(media, args) {
|
_downloadWebDocument(media, args) {
|
||||||
throw new Error('not implemented')
|
throw new Error('not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
// region Invoking Telegram request
|
// region Invoking Telegram request
|
||||||
@ -569,76 +576,76 @@ class TelegramClient {
|
|||||||
*/
|
*/
|
||||||
async invoke(request) {
|
async invoke(request) {
|
||||||
if (request.classType !== 'request') {
|
if (request.classType !== 'request') {
|
||||||
throw new Error('You can only invoke MTProtoRequests')
|
throw new Error('You can only invoke MTProtoRequests');
|
||||||
}
|
}
|
||||||
// This causes issues for now because not enough utils
|
// This causes issues for now because not enough utils
|
||||||
// await request.resolve(this, utils)
|
// await request.resolve(this, utils)
|
||||||
|
|
||||||
|
|
||||||
this._lastRequest = new Date().getTime()
|
this._lastRequest = new Date().getTime();
|
||||||
let attempt = 0
|
let attempt = 0;
|
||||||
for (attempt = 0; attempt < this._requestRetries; attempt++) {
|
for (attempt = 0; attempt < this._requestRetries; attempt++) {
|
||||||
try {
|
try {
|
||||||
const promise = this._sender.send(request)
|
const promise = this._sender.send(request);
|
||||||
const result = await promise
|
const result = await promise;
|
||||||
//this.session.processEntities(result)
|
//this.session.processEntities(result)
|
||||||
// this._entityCache.add(result)
|
// this._entityCache.add(result)
|
||||||
return result
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof errors.ServerError || e.message === 'RPC_CALL_FAIL' ||
|
if (e instanceof errors.ServerError || e.message === 'RPC_CALL_FAIL' ||
|
||||||
e.message === 'RPC_MCGET_FAIL') {
|
e.message === 'RPC_MCGET_FAIL') {
|
||||||
this._log.warn(`Telegram is having internal issues ${e.constructor.name}`)
|
this._log.warn(`Telegram is having internal issues ${e.constructor.name}`);
|
||||||
await sleep(2000)
|
await sleep(2000);
|
||||||
} else if (e instanceof errors.FloodWaitError || e instanceof errors.FloodTestPhoneWaitError) {
|
} else if (e instanceof errors.FloodWaitError || e instanceof errors.FloodTestPhoneWaitError) {
|
||||||
if (e.seconds <= this.floodSleepLimit) {
|
if (e.seconds <= this.floodSleepLimit) {
|
||||||
this._log.info(`Sleeping for ${e.seconds}s on flood wait`)
|
this._log.info(`Sleeping for ${e.seconds}s on flood wait`);
|
||||||
await sleep(e.seconds * 1000)
|
await sleep(e.seconds * 1000);
|
||||||
} else {
|
} else {
|
||||||
throw e
|
throw e;
|
||||||
}
|
}
|
||||||
} else if (e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError ||
|
} else if (e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError ||
|
||||||
e instanceof errors.UserMigrateError) {
|
e instanceof errors.UserMigrateError) {
|
||||||
this._log.info(`Phone migrated to ${e.newDc}`)
|
this._log.info(`Phone migrated to ${e.newDc}`);
|
||||||
const shouldRaise = e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError
|
const shouldRaise = e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError;
|
||||||
if (shouldRaise && await checkAuthorization(this)) {
|
if (shouldRaise && await checkAuthorization(this)) {
|
||||||
throw e
|
throw e;
|
||||||
}
|
}
|
||||||
await this._switchDC(e.newDc)
|
await this._switchDC(e.newDc);
|
||||||
} else {
|
} else {
|
||||||
throw e
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error(`Request was unsuccessful ${attempt} time(s)`)
|
throw new Error(`Request was unsuccessful ${attempt} time(s)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMe() {
|
async getMe() {
|
||||||
try {
|
try {
|
||||||
return (await this.invoke(new requests.users
|
return (await this.invoke(new requests.users
|
||||||
.GetUsers({ id: [new constructors.InputUserSelf()] })))[0]
|
.GetUsers({ id: [new constructors.InputUserSelf()] })))[0];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(authParams) {
|
async start(authParams) {
|
||||||
if (!this.isConnected()) {
|
if (!this.isConnected()) {
|
||||||
await this.connect()
|
await this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await checkAuthorization(this)) {
|
if (await checkAuthorization(this)) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiCredentials = {
|
const apiCredentials = {
|
||||||
apiId: this.apiId,
|
apiId: this.apiId,
|
||||||
apiHash: this.apiHash
|
apiHash: this.apiHash,
|
||||||
}
|
};
|
||||||
|
|
||||||
await authFlow(this, apiCredentials, authParams)
|
await authFlow(this, apiCredentials, authParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadFile(fileParams) {
|
uploadFile(fileParams) {
|
||||||
return uploadFile(this, fileParams)
|
return uploadFile(this, fileParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTwoFaSettings(params) {
|
updateTwoFaSettings(params) {
|
||||||
@ -647,7 +654,7 @@ class TelegramClient {
|
|||||||
|
|
||||||
// event region
|
// event region
|
||||||
addEventHandler(callback, event) {
|
addEventHandler(callback, event) {
|
||||||
this._eventBuilders.push([event, callback])
|
this._eventBuilders.push([event, callback]);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleUpdate(update) {
|
_handleUpdate(update) {
|
||||||
@ -656,29 +663,29 @@ class TelegramClient {
|
|||||||
|
|
||||||
if (update instanceof constructors.Updates || update instanceof constructors.UpdatesCombined) {
|
if (update instanceof constructors.Updates || update instanceof constructors.UpdatesCombined) {
|
||||||
// TODO deal with entities
|
// TODO deal with entities
|
||||||
const entities = []
|
const entities = [];
|
||||||
for (const x of [...update.users, ...update.chats]) {
|
for (const x of [...update.users, ...update.chats]) {
|
||||||
entities.push(x)
|
entities.push(x);
|
||||||
}
|
}
|
||||||
for (const u of update.updates) {
|
for (const u of update.updates) {
|
||||||
this._processUpdate(u, update.updates, entities)
|
this._processUpdate(u, update.updates, entities);
|
||||||
}
|
}
|
||||||
} else if (update instanceof constructors.UpdateShort) {
|
} else if (update instanceof constructors.UpdateShort) {
|
||||||
this._processUpdate(update.update, null)
|
this._processUpdate(update.update, null);
|
||||||
} else {
|
} else {
|
||||||
this._processUpdate(update, null)
|
this._processUpdate(update, null);
|
||||||
}
|
}
|
||||||
// TODO add caching
|
// TODO add caching
|
||||||
// this._stateCache.update(update)
|
// this._stateCache.update(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
_processUpdate(update, others, entities) {
|
_processUpdate(update, others, entities) {
|
||||||
update._entities = entities || []
|
update._entities = entities || [];
|
||||||
const args = {
|
const args = {
|
||||||
update: update,
|
update: update,
|
||||||
others: others,
|
others: others,
|
||||||
}
|
};
|
||||||
this._dispatchUpdate(args)
|
this._dispatchUpdate(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -830,6 +837,7 @@ class TelegramClient {
|
|||||||
* @param peer
|
* @param peer
|
||||||
* @returns {Promise<>}
|
* @returns {Promise<>}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*CONTEST
|
/*CONTEST
|
||||||
async getInputEntity(peer) {
|
async getInputEntity(peer) {
|
||||||
// Short-circuit if the input parameter directly maps to an InputPeer
|
// Short-circuit if the input parameter directly maps to an InputPeer
|
||||||
@ -918,9 +926,9 @@ class TelegramClient {
|
|||||||
ptsDate: null,
|
ptsDate: null,
|
||||||
}) {
|
}) {
|
||||||
for (const [builder, callback] of this._eventBuilders) {
|
for (const [builder, callback] of this._eventBuilders) {
|
||||||
const event = builder.build(args.update)
|
const event = builder.build(args.update);
|
||||||
if (event) {
|
if (event) {
|
||||||
await callback(event)
|
await callback(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -928,33 +936,34 @@ class TelegramClient {
|
|||||||
isConnected() {
|
isConnected() {
|
||||||
if (this._sender) {
|
if (this._sender) {
|
||||||
if (this._sender.isConnected()) {
|
if (this._sender.isConnected()) {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function timeout(promise, ms) {
|
async function timeout(promise, ms) {
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
promise,
|
promise,
|
||||||
Helpers.sleep(ms).then(() => Promise.reject(new Error('TIMEOUT'))),
|
Helpers.sleep(ms)
|
||||||
])
|
.then(() => Promise.reject(new Error('TIMEOUT'))),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function attempts(cb, times, pause) {
|
async function attempts(cb, times, pause) {
|
||||||
for (let i = 0; i < times; i++) {
|
for (let i = 0; i < times; i++) {
|
||||||
try {
|
try {
|
||||||
// We need to `return await` here so it can be caught locally
|
// We need to `return await` here so it can be caught locally
|
||||||
return await cb()
|
return await cb();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (i === times - 1) {
|
if (i === times - 1) {
|
||||||
throw err
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Helpers.sleep(pause)
|
await Helpers.sleep(pause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TelegramClient
|
module.exports = TelegramClient;
|
||||||
|
|||||||
@ -169,7 +169,7 @@ async function signInUserWithQrCode(
|
|||||||
|
|
||||||
const inputPromise = (async () => {
|
const inputPromise = (async () => {
|
||||||
while (1) {
|
while (1) {
|
||||||
if (isScanningComplete){
|
if (isScanningComplete) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,13 +4,13 @@ import { getAppropriatedPartSize } from '../Utils';
|
|||||||
import { sleep } from '../Helpers';
|
import { sleep } from '../Helpers';
|
||||||
|
|
||||||
export interface progressCallback {
|
export interface progressCallback {
|
||||||
|
isCanceled?: boolean;
|
||||||
|
acceptsBuffer?: boolean;
|
||||||
|
|
||||||
(
|
(
|
||||||
progress: number, // Float between 0 and 1.
|
progress: number, // Float between 0 and 1.
|
||||||
...args: any[]
|
...args: any[]
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
isCanceled?: boolean;
|
|
||||||
acceptsBuffer?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DownloadFileParams {
|
export interface DownloadFileParams {
|
||||||
|
|||||||
@ -5,10 +5,10 @@ import { generateRandomBytes, readBigIntFromBuffer, sleep } from '../Helpers';
|
|||||||
import { getAppropriatedPartSize } from '../Utils';
|
import { getAppropriatedPartSize } from '../Utils';
|
||||||
|
|
||||||
interface OnProgress {
|
interface OnProgress {
|
||||||
|
isCanceled?: boolean;
|
||||||
|
|
||||||
// Float between 0 and 1.
|
// Float between 0 and 1.
|
||||||
(progress: number): void;
|
(progress: number): void;
|
||||||
|
|
||||||
isCanceled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UploadFileParams {
|
export interface UploadFileParams {
|
||||||
|
|||||||
@ -1,49 +1,54 @@
|
|||||||
const { sha1, toSignedLittleBuffer,readBufferFromBigInt, readBigIntFromBuffer } = require('../Helpers')
|
const {
|
||||||
const BinaryReader = require('../extensions/BinaryReader')
|
sha1,
|
||||||
const { sleep } = require('../Helpers')
|
toSignedLittleBuffer,
|
||||||
|
readBufferFromBigInt,
|
||||||
|
readBigIntFromBuffer,
|
||||||
|
} = require('../Helpers');
|
||||||
|
const BinaryReader = require('../extensions/BinaryReader');
|
||||||
|
const { sleep } = require('../Helpers');
|
||||||
|
|
||||||
class AuthKey {
|
class AuthKey {
|
||||||
|
|
||||||
constructor(value, hash) {
|
constructor(value, hash) {
|
||||||
if (!hash || !value) {
|
if (!hash || !value) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
this._key = value
|
this._key = value;
|
||||||
this._hash = hash
|
this._hash = hash;
|
||||||
const reader = new BinaryReader(hash)
|
const reader = new BinaryReader(hash);
|
||||||
this.auxHash = reader.readLong(false)
|
this.auxHash = reader.readLong(false);
|
||||||
reader.read(4)
|
reader.read(4);
|
||||||
this.keyId = reader.readLong(false)
|
this.keyId = reader.readLong(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setKey(value) {
|
async setKey(value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
this._key = this.auxHash = this.keyId = this._hash = null
|
this._key = this.auxHash = this.keyId = this._hash = null;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (value instanceof AuthKey) {
|
if (value instanceof AuthKey) {
|
||||||
this._key = value._key
|
this._key = value._key;
|
||||||
this.auxHash = value.auxHash
|
this.auxHash = value.auxHash;
|
||||||
this.keyId = value.keyId
|
this.keyId = value.keyId;
|
||||||
this._hash = value._hash
|
this._hash = value._hash;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
this._key = value
|
this._key = value;
|
||||||
this._hash = await sha1(this._key)
|
this._hash = await sha1(this._key);
|
||||||
const reader = new BinaryReader(this._hash)
|
const reader = new BinaryReader(this._hash);
|
||||||
this.auxHash = reader.readLong(false)
|
this.auxHash = reader.readLong(false);
|
||||||
reader.read(4)
|
reader.read(4);
|
||||||
this.keyId = reader.readLong(false)
|
this.keyId = reader.readLong(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForKey() {
|
async waitForKey() {
|
||||||
while (!this.keyId) {
|
while (!this.keyId) {
|
||||||
await sleep(20)
|
await sleep(20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getKey() {
|
getKey() {
|
||||||
return this._key
|
return this._key;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO : This doesn't really fit here, it's only used in authentication
|
// TODO : This doesn't really fit here, it's only used in authentication
|
||||||
@ -55,20 +60,21 @@ class AuthKey {
|
|||||||
* @returns {bigint}
|
* @returns {bigint}
|
||||||
*/
|
*/
|
||||||
async calcNewNonceHash(newNonce, number) {
|
async calcNewNonceHash(newNonce, number) {
|
||||||
newNonce = toSignedLittleBuffer(newNonce, 32)
|
newNonce = toSignedLittleBuffer(newNonce, 32);
|
||||||
const n = Buffer.alloc(1)
|
const n = Buffer.alloc(1);
|
||||||
n.writeUInt8(number, 0)
|
n.writeUInt8(number, 0);
|
||||||
const data = Buffer.concat([newNonce,
|
const data = Buffer.concat([newNonce,
|
||||||
Buffer.concat([n, readBufferFromBigInt(this.auxHash, 8, true)])])
|
Buffer.concat([n, readBufferFromBigInt(this.auxHash, 8, true)])]);
|
||||||
|
|
||||||
// Calculates the message key from the given data
|
// Calculates the message key from the given data
|
||||||
const shaData = (await sha1(data)).slice(4, 20)
|
const shaData = (await sha1(data)).slice(4, 20);
|
||||||
return readBigIntFromBuffer(shaData, true, true)
|
return readBigIntFromBuffer(shaData, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other) {
|
equals(other) {
|
||||||
return other instanceof this.constructor && this._key && other.getKey() && other.getKey().equals(this._key)
|
return other instanceof this.constructor && this._key && other.getKey() && other.getKey()
|
||||||
|
.equals(this._key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AuthKey
|
module.exports = AuthKey;
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
const crypto = require('./crypto')
|
const crypto = require('./crypto');
|
||||||
|
|
||||||
class CTR {
|
class CTR {
|
||||||
constructor(key, iv) {
|
constructor(key, iv) {
|
||||||
if (!Buffer.isBuffer(key) || !Buffer.isBuffer(iv) || iv.length !== 16) {
|
if (!Buffer.isBuffer(key) || !Buffer.isBuffer(iv) || iv.length !== 16) {
|
||||||
throw new Error('Key and iv need to be a buffer')
|
throw new Error('Key and iv need to be a buffer');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cipher = crypto.createCipheriv('AES-256-CTR', key, iv)
|
this.cipher = crypto.createCipheriv('AES-256-CTR', key, iv);
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypt(data) {
|
encrypt(data) {
|
||||||
return Buffer.from(this.cipher.update(data))
|
return Buffer.from(this.cipher.update(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = CTR
|
module.exports = CTR;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const BigInt = require('big-integer')
|
const BigInt = require('big-integer');
|
||||||
const { modExp } = require('../Helpers')
|
const { modExp } = require('../Helpers');
|
||||||
|
|
||||||
class Factorizator {
|
class Factorizator {
|
||||||
/**
|
/**
|
||||||
@ -10,11 +10,11 @@ class Factorizator {
|
|||||||
*/
|
*/
|
||||||
static gcd(a, b) {
|
static gcd(a, b) {
|
||||||
while (b.neq(BigInt.zero)) {
|
while (b.neq(BigInt.zero)) {
|
||||||
let temp = b
|
let temp = b;
|
||||||
b = a.remainder(b)
|
b = a.remainder(b);
|
||||||
a = temp
|
a = temp;
|
||||||
}
|
}
|
||||||
return a
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,57 +23,75 @@ class Factorizator {
|
|||||||
* @returns {{p: *, q: *}}
|
* @returns {{p: *, q: *}}
|
||||||
*/
|
*/
|
||||||
static factorize(pq) {
|
static factorize(pq) {
|
||||||
if (pq.remainder(2).equals(BigInt.zero)) {
|
if (pq.remainder(2)
|
||||||
return { p: BigInt(2), q: pq.divide(BigInt(2)) }
|
.equals(BigInt.zero)) {
|
||||||
|
return {
|
||||||
|
p: BigInt(2),
|
||||||
|
q: pq.divide(BigInt(2)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
let y = BigInt.randBetween(BigInt(1),pq.minus(1))
|
let y = BigInt.randBetween(BigInt(1), pq.minus(1));
|
||||||
const c = BigInt.randBetween(BigInt(1),pq.minus(1))
|
const c = BigInt.randBetween(BigInt(1), pq.minus(1));
|
||||||
const m = BigInt.randBetween(BigInt(1),pq.minus(1))
|
const m = BigInt.randBetween(BigInt(1), pq.minus(1));
|
||||||
|
|
||||||
let g = BigInt.one
|
let g = BigInt.one;
|
||||||
let r = BigInt.one
|
let r = BigInt.one;
|
||||||
let q = BigInt.one
|
let q = BigInt.one;
|
||||||
let x = BigInt.zero
|
let x = BigInt.zero;
|
||||||
let ys = BigInt.zero
|
let ys = BigInt.zero;
|
||||||
let k
|
let k;
|
||||||
|
|
||||||
while (g.eq(BigInt.one)) {
|
while (g.eq(BigInt.one)) {
|
||||||
x = y
|
x = y;
|
||||||
for (let i = 0; BigInt(i).lesser(r); i++) {
|
for (let i = 0; BigInt(i)
|
||||||
y = (modExp(y, BigInt(2), pq)).add(c).remainder(pq)
|
.lesser(r); i++) {
|
||||||
|
y = (modExp(y, BigInt(2), pq)).add(c)
|
||||||
|
.remainder(pq);
|
||||||
}
|
}
|
||||||
k = BigInt.zero
|
k = BigInt.zero;
|
||||||
|
|
||||||
while (k.lesser(r) && g.eq(BigInt.one)) {
|
while (k.lesser(r) && g.eq(BigInt.one)) {
|
||||||
|
|
||||||
ys = y
|
ys = y;
|
||||||
let condition = BigInt.min(m, r.minus(k))
|
let condition = BigInt.min(m, r.minus(k));
|
||||||
for (let i = 0; BigInt(i).lesser(condition); i++) {
|
for (let i = 0; BigInt(i)
|
||||||
y = (modExp(y, BigInt(2), pq)).add(c).remainder(pq)
|
.lesser(condition); i++) {
|
||||||
q = q.multiply(x.minus(y).abs()).remainder(pq)
|
y = (modExp(y, BigInt(2), pq)).add(c)
|
||||||
|
.remainder(pq);
|
||||||
|
q = q.multiply(x.minus(y)
|
||||||
|
.abs())
|
||||||
|
.remainder(pq);
|
||||||
}
|
}
|
||||||
g = Factorizator.gcd(q, pq)
|
g = Factorizator.gcd(q, pq);
|
||||||
k = k.add(m)
|
k = k.add(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
r = r.multiply(2)
|
r = r.multiply(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (g.eq(pq)) {
|
if (g.eq(pq)) {
|
||||||
while (true) {
|
while (true) {
|
||||||
ys = (modExp(ys, BigInt(2), pq)).add(c).remainder(pq)
|
ys = (modExp(ys, BigInt(2), pq)).add(c)
|
||||||
g = Factorizator.gcd(x.minus(ys).abs(), pq)
|
.remainder(pq);
|
||||||
|
g = Factorizator.gcd(x.minus(ys)
|
||||||
|
.abs(), pq);
|
||||||
|
|
||||||
if (g.greater(1)) {
|
if (g.greater(1)) {
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const p = g
|
const p = g;
|
||||||
q = pq.divide(g)
|
q = pq.divide(g);
|
||||||
return p < q ? { p: p, q: q } : { p: q, q: p }
|
return p < q ? {
|
||||||
|
p: p,
|
||||||
|
q: q,
|
||||||
|
} : {
|
||||||
|
p: q,
|
||||||
|
q: p,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Factorizator
|
module.exports = Factorizator;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
const Helpers = require("../Helpers");
|
const Helpers = require('../Helpers');
|
||||||
|
|
||||||
const {IGE:aes_ige} = require('@cryptography/aes');
|
const { IGE: aes_ige } = require('@cryptography/aes');
|
||||||
|
|
||||||
class IGENEW {
|
class IGENEW {
|
||||||
constructor(key, iv) {
|
constructor(key, iv) {
|
||||||
this.ige = new aes_ige(key,iv);
|
this.ige = new aes_ige(key, iv);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,9 +22,9 @@ class IGENEW {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
encryptIge(plainText) {
|
encryptIge(plainText) {
|
||||||
const padding = plainText.length % 16
|
const padding = plainText.length % 16;
|
||||||
if (padding) {
|
if (padding) {
|
||||||
plainText = Buffer.concat([plainText, Helpers.generateRandomBytes(16 - padding)])
|
plainText = Buffer.concat([plainText, Helpers.generateRandomBytes(16 - padding)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Helpers.convertToLittle(this.ige.encrypt(plainText));
|
return Helpers.convertToLittle(this.ige.encrypt(plainText));
|
||||||
@ -34,4 +34,4 @@ class IGENEW {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = IGENEW
|
module.exports = IGENEW;
|
||||||
|
|||||||
@ -1,29 +1,39 @@
|
|||||||
const BigInt = require('big-integer')
|
const BigInt = require('big-integer');
|
||||||
const { readBigIntFromBuffer, readBufferFromBigInt, getByteArray, sha1, generateRandomBytes, modExp } = require('../Helpers')
|
const {
|
||||||
|
readBigIntFromBuffer,
|
||||||
|
readBufferFromBigInt,
|
||||||
|
getByteArray,
|
||||||
|
sha1,
|
||||||
|
generateRandomBytes,
|
||||||
|
modExp,
|
||||||
|
} = require('../Helpers');
|
||||||
|
|
||||||
const PUBLIC_KEYS = [{
|
const PUBLIC_KEYS = [{
|
||||||
'fingerprint': [40, 85, 94, 156, 117, 240, 61, 22, 65, 244, 169, 2, 33, 107, 232, 108, 2, 43, 180, 195],
|
'fingerprint': [40, 85, 94, 156, 117, 240, 61, 22, 65, 244, 169, 2, 33, 107, 232, 108, 2, 43, 180, 195],
|
||||||
'n': BigInt('24403446649145068056824081744112065346446136066297307473868293895086332508101251964919587745984311372853053253457835208829824428441874946556659953519213382748319518214765985662663680818277989736779506318868003755216402538945900388706898101286548187286716959100102939636333452457308619454821845196109544157601096359148241435922125602449263164512290854366930013825808102403072317738266383237191313714482187326643144603633877219028262697593882410403273959074350849923041765639673335775605842311578109726403165298875058941765362622936097839775380070572921007586266115476975819175319995527916042178582540628652481530373407'),
|
'n': BigInt('24403446649145068056824081744112065346446136066297307473868293895086332508101251964919587745984311372853053253457835208829824428441874946556659953519213382748319518214765985662663680818277989736779506318868003755216402538945900388706898101286548187286716959100102939636333452457308619454821845196109544157601096359148241435922125602449263164512290854366930013825808102403072317738266383237191313714482187326643144603633877219028262697593882410403273959074350849923041765639673335775605842311578109726403165298875058941765362622936097839775380070572921007586266115476975819175319995527916042178582540628652481530373407'),
|
||||||
'e': 65537
|
'e': 65537,
|
||||||
}, {
|
}, {
|
||||||
'fingerprint': [140, 171, 9, 34, 146, 246, 166, 50, 10, 170, 229, 247, 155, 114, 28, 177, 29, 106, 153, 154],
|
'fingerprint': [140, 171, 9, 34, 146, 246, 166, 50, 10, 170, 229, 247, 155, 114, 28, 177, 29, 106, 153, 154],
|
||||||
'n': BigInt('25081407810410225030931722734886059247598515157516470397242545867550116598436968553551465554653745201634977779380884774534457386795922003815072071558370597290368737862981871277312823942822144802509055492512145589734772907225259038113414940384446493111736999668652848440655603157665903721517224934142301456312994547591626081517162758808439979745328030376796953660042629868902013177751703385501412640560275067171555763725421377065095231095517201241069856888933358280729674273422117201596511978645878544308102076746465468955910659145532699238576978901011112475698963666091510778777356966351191806495199073754705289253783'),
|
'n': BigInt('25081407810410225030931722734886059247598515157516470397242545867550116598436968553551465554653745201634977779380884774534457386795922003815072071558370597290368737862981871277312823942822144802509055492512145589734772907225259038113414940384446493111736999668652848440655603157665903721517224934142301456312994547591626081517162758808439979745328030376796953660042629868902013177751703385501412640560275067171555763725421377065095231095517201241069856888933358280729674273422117201596511978645878544308102076746465468955910659145532699238576978901011112475698963666091510778777356966351191806495199073754705289253783'),
|
||||||
'e': 65537
|
'e': 65537,
|
||||||
}, {
|
}, {
|
||||||
'fingerprint': [243, 218, 109, 239, 16, 202, 176, 78, 167, 8, 255, 209, 120, 234, 205, 112, 111, 42, 91, 176],
|
'fingerprint': [243, 218, 109, 239, 16, 202, 176, 78, 167, 8, 255, 209, 120, 234, 205, 112, 111, 42, 91, 176],
|
||||||
'n': BigInt('22347337644621997830323797217583448833849627595286505527328214795712874535417149457567295215523199212899872122674023936713124024124676488204889357563104452250187725437815819680799441376434162907889288526863223004380906766451781702435861040049293189979755757428366240570457372226323943522935844086838355728767565415115131238950994049041950699006558441163206523696546297006014416576123345545601004508537089192869558480948139679182328810531942418921113328804749485349441503927570568778905918696883174575510385552845625481490900659718413892216221539684717773483326240872061786759868040623935592404144262688161923519030977'),
|
'n': BigInt('22347337644621997830323797217583448833849627595286505527328214795712874535417149457567295215523199212899872122674023936713124024124676488204889357563104452250187725437815819680799441376434162907889288526863223004380906766451781702435861040049293189979755757428366240570457372226323943522935844086838355728767565415115131238950994049041950699006558441163206523696546297006014416576123345545601004508537089192869558480948139679182328810531942418921113328804749485349441503927570568778905918696883174575510385552845625481490900659718413892216221539684717773483326240872061786759868040623935592404144262688161923519030977'),
|
||||||
'e': 65537
|
'e': 65537,
|
||||||
}, {
|
}, {
|
||||||
'fingerprint': [128, 80, 214, 72, 77, 244, 98, 7, 201, 250, 37, 244, 227, 51, 96, 199, 182, 37, 224, 113],
|
'fingerprint': [128, 80, 214, 72, 77, 244, 98, 7, 201, 250, 37, 244, 227, 51, 96, 199, 182, 37, 224, 113],
|
||||||
'n': BigInt('24573455207957565047870011785254215390918912369814947541785386299516827003508659346069416840622922416779652050319196701077275060353178142796963682024347858398319926119639265555410256455471016400261630917813337515247954638555325280392998950756512879748873422896798579889820248358636937659872379948616822902110696986481638776226860777480684653756042166610633513404129518040549077551227082262066602286208338952016035637334787564972991208252928951876463555456715923743181359826124083963758009484867346318483872552977652588089928761806897223231500970500186019991032176060579816348322451864584743414550721639495547636008351'),
|
'n': BigInt('24573455207957565047870011785254215390918912369814947541785386299516827003508659346069416840622922416779652050319196701077275060353178142796963682024347858398319926119639265555410256455471016400261630917813337515247954638555325280392998950756512879748873422896798579889820248358636937659872379948616822902110696986481638776226860777480684653756042166610633513404129518040549077551227082262066602286208338952016035637334787564972991208252928951876463555456715923743181359826124083963758009484867346318483872552977652588089928761806897223231500970500186019991032176060579816348322451864584743414550721639495547636008351'),
|
||||||
'e': 65537
|
'e': 65537,
|
||||||
}]
|
}];
|
||||||
|
|
||||||
const _serverKeys = {}
|
const _serverKeys = {};
|
||||||
|
|
||||||
PUBLIC_KEYS.forEach(({ fingerprint, ...keyInfo }) => {
|
PUBLIC_KEYS.forEach(({
|
||||||
_serverKeys[readBigIntFromBuffer(fingerprint.slice(-8), true, true)] = keyInfo
|
fingerprint,
|
||||||
})
|
...keyInfo
|
||||||
|
}) => {
|
||||||
|
_serverKeys[readBigIntFromBuffer(fingerprint.slice(-8), true, true)] = keyInfo;
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypts the given data known the fingerprint to be used
|
* Encrypts the given data known the fingerprint to be used
|
||||||
@ -34,24 +44,24 @@ PUBLIC_KEYS.forEach(({ fingerprint, ...keyInfo }) => {
|
|||||||
* @returns {Buffer|*|undefined} the cipher text, or None if no key matching this fingerprint is found.
|
* @returns {Buffer|*|undefined} the cipher text, or None if no key matching this fingerprint is found.
|
||||||
*/
|
*/
|
||||||
async function encrypt(fingerprint, data) {
|
async function encrypt(fingerprint, data) {
|
||||||
const key = _serverKeys[fingerprint]
|
const key = _serverKeys[fingerprint];
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// len(sha1.digest) is always 20, so we're left with 255 - 20 - x padding
|
// len(sha1.digest) is always 20, so we're left with 255 - 20 - x padding
|
||||||
const rand = generateRandomBytes(235 - data.length)
|
const rand = generateRandomBytes(235 - data.length);
|
||||||
|
|
||||||
const toEncrypt = Buffer.concat([await sha1(data), data, rand])
|
const toEncrypt = Buffer.concat([await sha1(data), data, rand]);
|
||||||
|
|
||||||
// rsa module rsa.encrypt adds 11 bits for padding which we don't want
|
// rsa module rsa.encrypt adds 11 bits for padding which we don't want
|
||||||
// rsa module uses rsa.transform.bytes2int(to_encrypt), easier way:
|
// rsa module uses rsa.transform.bytes2int(to_encrypt), easier way:
|
||||||
const payload = readBigIntFromBuffer(toEncrypt, false)
|
const payload = readBigIntFromBuffer(toEncrypt, false);
|
||||||
const encrypted = modExp(payload, BigInt(key.e), key.n)
|
const encrypted = modExp(payload, BigInt(key.e), key.n);
|
||||||
// rsa module uses transform.int2bytes(encrypted, keylength), easier:
|
// rsa module uses transform.int2bytes(encrypted, keylength), easier:
|
||||||
return readBufferFromBigInt(encrypted, 256, false)
|
return readBufferFromBigInt(encrypted, 256, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
encrypt,
|
encrypt,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,24 +1,27 @@
|
|||||||
const AES = require('@cryptography/aes').default;
|
const AES = require('@cryptography/aes').default;
|
||||||
const { i2ab, ab2i } = require('./converters');
|
const {
|
||||||
|
i2ab,
|
||||||
|
ab2i,
|
||||||
|
} = require('./converters');
|
||||||
const { getWords } = require('./words');
|
const { getWords } = require('./words');
|
||||||
|
|
||||||
class Counter {
|
class Counter {
|
||||||
constructor(initialValue) {
|
constructor(initialValue) {
|
||||||
this.setBytes(initialValue)
|
this.setBytes(initialValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
setBytes(bytes) {
|
setBytes(bytes) {
|
||||||
bytes = Buffer.from(bytes)
|
bytes = Buffer.from(bytes);
|
||||||
this._counter = bytes
|
this._counter = bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
for (let i = 15; i >= 0; i--) {
|
for (let i = 15; i >= 0; i--) {
|
||||||
if (this._counter[i] === 255) {
|
if (this._counter[i] === 255) {
|
||||||
this._counter[i] = 0
|
this._counter[i] = 0;
|
||||||
} else {
|
} else {
|
||||||
this._counter[i]++
|
this._counter[i]++;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,34 +31,34 @@ class CTR {
|
|||||||
constructor(key, counter) {
|
constructor(key, counter) {
|
||||||
|
|
||||||
if (!(counter instanceof Counter)) {
|
if (!(counter instanceof Counter)) {
|
||||||
counter = new Counter(counter)
|
counter = new Counter(counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._counter = counter
|
this._counter = counter;
|
||||||
|
|
||||||
this._remainingCounter = null
|
this._remainingCounter = null;
|
||||||
this._remainingCounterIndex = 16
|
this._remainingCounterIndex = 16;
|
||||||
|
|
||||||
this._aes = new AES(getWords(key))
|
this._aes = new AES(getWords(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
update(plainText) {
|
update(plainText) {
|
||||||
return this.encrypt(plainText)
|
return this.encrypt(plainText);
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypt(plainText) {
|
encrypt(plainText) {
|
||||||
const encrypted = Buffer.from(plainText)
|
const encrypted = Buffer.from(plainText);
|
||||||
|
|
||||||
for (let i = 0; i < encrypted.length; i++) {
|
for (let i = 0; i < encrypted.length; i++) {
|
||||||
if (this._remainingCounterIndex === 16) {
|
if (this._remainingCounterIndex === 16) {
|
||||||
this._remainingCounter = Buffer.from(i2ab(this._aes.encrypt(ab2i(this._counter._counter))))
|
this._remainingCounter = Buffer.from(i2ab(this._aes.encrypt(ab2i(this._counter._counter))));
|
||||||
this._remainingCounterIndex = 0
|
this._remainingCounterIndex = 0;
|
||||||
this._counter.increment()
|
this._counter.increment();
|
||||||
}
|
}
|
||||||
encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++]
|
encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++];
|
||||||
}
|
}
|
||||||
|
|
||||||
return encrypted
|
return encrypted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +67,7 @@ function createDecipheriv(algorithm, key, iv) {
|
|||||||
if (algorithm.includes('ECB')) {
|
if (algorithm.includes('ECB')) {
|
||||||
throw new Error('Not supported');
|
throw new Error('Not supported');
|
||||||
} else {
|
} else {
|
||||||
return new CTR(key, iv)
|
return new CTR(key, iv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,47 +75,49 @@ function createCipheriv(algorithm, key, iv) {
|
|||||||
if (algorithm.includes('ECB')) {
|
if (algorithm.includes('ECB')) {
|
||||||
throw new Error('Not supported');
|
throw new Error('Not supported');
|
||||||
} else {
|
} else {
|
||||||
return new CTR(key, iv)
|
return new CTR(key, iv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomBytes(count) {
|
function randomBytes(count) {
|
||||||
const bytes = new Uint8Array(count)
|
const bytes = new Uint8Array(count);
|
||||||
crypto.getRandomValues(bytes)
|
crypto.getRandomValues(bytes);
|
||||||
return bytes
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Hash {
|
class Hash {
|
||||||
constructor(algorithm) {
|
constructor(algorithm) {
|
||||||
this.algorithm = algorithm
|
this.algorithm = algorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data) {
|
update(data) {
|
||||||
//We shouldn't be needing new Uint8Array but it doesn't
|
//We shouldn't be needing new Uint8Array but it doesn't
|
||||||
//work without it
|
//work without it
|
||||||
this.data = new Uint8Array(data)
|
this.data = new Uint8Array(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async digest() {
|
async digest() {
|
||||||
if (this.algorithm === 'sha1') {
|
if (this.algorithm === 'sha1') {
|
||||||
return Buffer.from(await self.crypto.subtle.digest('SHA-1', this.data))
|
return Buffer.from(await self.crypto.subtle.digest('SHA-1', this.data));
|
||||||
} else if (this.algorithm === 'sha256') {
|
} else if (this.algorithm === 'sha256') {
|
||||||
return Buffer.from(await self.crypto.subtle.digest('SHA-256', this.data))
|
return Buffer.from(await self.crypto.subtle.digest('SHA-256', this.data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pbkdf2(password, salt, iterations) {
|
async function pbkdf2(password, salt, iterations) {
|
||||||
const passwordKey = await crypto.subtle.importKey('raw', password,
|
const passwordKey = await crypto.subtle.importKey('raw', password,
|
||||||
{name: 'PBKDF2'}, false, ['deriveBits'])
|
{ name: 'PBKDF2' }, false, ['deriveBits']);
|
||||||
return Buffer.from(await crypto.subtle.deriveBits({
|
return Buffer.from(await crypto.subtle.deriveBits({
|
||||||
name: 'PBKDF2',
|
name: 'PBKDF2',
|
||||||
hash: 'SHA-512', salt, iterations
|
hash: 'SHA-512',
|
||||||
|
salt,
|
||||||
|
iterations,
|
||||||
}, passwordKey, 512));
|
}, passwordKey, 512));
|
||||||
}
|
}
|
||||||
|
|
||||||
function createHash(algorithm) {
|
function createHash(algorithm) {
|
||||||
return new Hash(algorithm)
|
return new Hash(algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -120,5 +125,5 @@ module.exports = {
|
|||||||
createDecipheriv,
|
createDecipheriv,
|
||||||
randomBytes,
|
randomBytes,
|
||||||
createHash,
|
createHash,
|
||||||
pbkdf2
|
pbkdf2,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
class ReadCancelledError extends Error {
|
class ReadCancelledError extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('The read operation was cancelled.')
|
super('The read operation was cancelled.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,12 +20,12 @@ class TypeNotFoundError extends Error {
|
|||||||
constructor(invalidConstructorId, remaining) {
|
constructor(invalidConstructorId, remaining) {
|
||||||
super(`Could not find a matching Constructor ID for the TLObject that was supposed to be
|
super(`Could not find a matching Constructor ID for the TLObject that was supposed to be
|
||||||
read with ID ${invalidConstructorId}. Most likely, a TLObject was trying to be read when
|
read with ID ${invalidConstructorId}. Most likely, a TLObject was trying to be read when
|
||||||
it should not be read. Remaining bytes: ${remaining.length}`)
|
it should not be read. Remaining bytes: ${remaining.length}`);
|
||||||
if (typeof alert !== 'undefined') {
|
if (typeof alert !== 'undefined') {
|
||||||
alert(`Missing MTProto Entity: Please, make sure to add TL definition for ID ${invalidConstructorId}`)
|
alert(`Missing MTProto Entity: Please, make sure to add TL definition for ID ${invalidConstructorId}`);
|
||||||
}
|
}
|
||||||
this.invalidConstructorId = invalidConstructorId
|
this.invalidConstructorId = invalidConstructorId;
|
||||||
this.remaining = remaining
|
this.remaining = remaining;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,9 +35,9 @@ class TypeNotFoundError extends Error {
|
|||||||
*/
|
*/
|
||||||
class InvalidChecksumError extends Error {
|
class InvalidChecksumError extends Error {
|
||||||
constructor(checksum, validChecksum) {
|
constructor(checksum, validChecksum) {
|
||||||
super(`Invalid checksum (${checksum} when ${validChecksum} was expected). This packet should be skipped.`)
|
super(`Invalid checksum (${checksum} when ${validChecksum} was expected). This packet should be skipped.`);
|
||||||
this.checksum = checksum
|
this.checksum = checksum;
|
||||||
this.validChecksum = validChecksum
|
this.validChecksum = validChecksum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,15 +47,15 @@ class InvalidChecksumError extends Error {
|
|||||||
*/
|
*/
|
||||||
class InvalidBufferError extends Error {
|
class InvalidBufferError extends Error {
|
||||||
constructor(payload) {
|
constructor(payload) {
|
||||||
let code = null
|
let code = null;
|
||||||
if (payload.length === 4) {
|
if (payload.length === 4) {
|
||||||
code = -payload.readInt32LE(0)
|
code = -payload.readInt32LE(0);
|
||||||
super(`Invalid response buffer (HTTP code ${code})`)
|
super(`Invalid response buffer (HTTP code ${code})`);
|
||||||
} else {
|
} else {
|
||||||
super(`Invalid response buffer (too short ${payload})`)
|
super(`Invalid response buffer (too short ${payload})`);
|
||||||
}
|
}
|
||||||
this.code = code
|
this.code = code;
|
||||||
this.payload = payload
|
this.payload = payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,9 +65,9 @@ class InvalidBufferError extends Error {
|
|||||||
class SecurityError extends Error {
|
class SecurityError extends Error {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
if (!args.length) {
|
if (!args.length) {
|
||||||
args = ['A security check failed.']
|
args = ['A security check failed.'];
|
||||||
}
|
}
|
||||||
super(...args)
|
super(...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ class SecurityError extends Error {
|
|||||||
*/
|
*/
|
||||||
class CdnFileTamperedError extends SecurityError {
|
class CdnFileTamperedError extends SecurityError {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('The CDN file has been altered and its download cancelled.')
|
super('The CDN file has been altered and its download cancelled.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,15 +123,15 @@ class BadMessageError extends Error {
|
|||||||
'the correct salt, and the message is to be re-sent with it).',
|
'the correct salt, and the message is to be re-sent with it).',
|
||||||
|
|
||||||
64: 'Invalid container.',
|
64: 'Invalid container.',
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(request,code) {
|
constructor(request, code) {
|
||||||
let errorMessage = BadMessageError.ErrorMessages[code] ||
|
let errorMessage = BadMessageError.ErrorMessages[code] ||
|
||||||
`Unknown error code (this should not happen): ${code}.`
|
`Unknown error code (this should not happen): ${code}.`;
|
||||||
errorMessage+= ` Caused by ${request.className}`
|
errorMessage += ` Caused by ${request.className}`;
|
||||||
super(errorMessage)
|
super(errorMessage);
|
||||||
this.message = errorMessage
|
this.message = errorMessage;
|
||||||
this.code = code
|
this.code = code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,4 +145,4 @@ module.exports = {
|
|||||||
SecurityError,
|
SecurityError,
|
||||||
CdnFileTamperedError,
|
CdnFileTamperedError,
|
||||||
BadMessageError,
|
BadMessageError,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -7,18 +7,18 @@ class RPCError extends Error {
|
|||||||
'RPCError {0}: {1}{2}'
|
'RPCError {0}: {1}{2}'
|
||||||
.replace('{0}', code)
|
.replace('{0}', code)
|
||||||
.replace('{1}', message)
|
.replace('{1}', message)
|
||||||
.replace('{2}', RPCError._fmtRequest(request))
|
.replace('{2}', RPCError._fmtRequest(request)),
|
||||||
)
|
);
|
||||||
this.code = code
|
this.code = code;
|
||||||
this.message = message
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _fmtRequest(request) {
|
static _fmtRequest(request) {
|
||||||
// TODO fix this
|
// TODO fix this
|
||||||
if (request) {
|
if (request) {
|
||||||
return ` (caused by ${request.className})`
|
return ` (caused by ${request.className})`;
|
||||||
} else {
|
} else {
|
||||||
return ''
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,9 +28,9 @@ class RPCError extends Error {
|
|||||||
*/
|
*/
|
||||||
class InvalidDCError extends RPCError {
|
class InvalidDCError extends RPCError {
|
||||||
constructor(request, message, code) {
|
constructor(request, message, code) {
|
||||||
super(message, request, code)
|
super(message, request, code);
|
||||||
this.code = code || 303
|
this.code = code || 303;
|
||||||
this.message = message || 'ERROR_SEE_OTHER'
|
this.message = message || 'ERROR_SEE_OTHER';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,8 +40,8 @@ class InvalidDCError extends RPCError {
|
|||||||
* notified that the data must be corrected before the query is repeated.
|
* notified that the data must be corrected before the query is repeated.
|
||||||
*/
|
*/
|
||||||
class BadRequestError extends RPCError {
|
class BadRequestError extends RPCError {
|
||||||
code = 400
|
code = 400;
|
||||||
message = 'BAD_REQUEST'
|
message = 'BAD_REQUEST';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,8 +49,8 @@ class BadRequestError extends RPCError {
|
|||||||
* to authorized users.
|
* to authorized users.
|
||||||
*/
|
*/
|
||||||
class UnauthorizedError extends RPCError {
|
class UnauthorizedError extends RPCError {
|
||||||
code = 401
|
code = 401;
|
||||||
message = 'UNAUTHORIZED'
|
message = 'UNAUTHORIZED';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,16 +58,16 @@ class UnauthorizedError extends RPCError {
|
|||||||
* someone who has blacklisted the current user.
|
* someone who has blacklisted the current user.
|
||||||
*/
|
*/
|
||||||
class ForbiddenError extends RPCError {
|
class ForbiddenError extends RPCError {
|
||||||
code = 403
|
code = 403;
|
||||||
message = 'FORBIDDEN'
|
message = 'FORBIDDEN';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An attempt to invoke a non-existent object, such as a method.
|
* An attempt to invoke a non-existent object, such as a method.
|
||||||
*/
|
*/
|
||||||
class NotFoundError extends RPCError {
|
class NotFoundError extends RPCError {
|
||||||
code = 404
|
code = 404;
|
||||||
message = 'NOT_FOUND'
|
message = 'NOT_FOUND';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,8 +75,8 @@ class NotFoundError extends RPCError {
|
|||||||
* AUTH_KEY_DUPLICATED which can cause the connection to fail.
|
* AUTH_KEY_DUPLICATED which can cause the connection to fail.
|
||||||
*/
|
*/
|
||||||
class AuthKeyError extends RPCError {
|
class AuthKeyError extends RPCError {
|
||||||
code = 406
|
code = 406;
|
||||||
message = 'AUTH_KEY'
|
message = 'AUTH_KEY';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,8 +86,8 @@ class AuthKeyError extends RPCError {
|
|||||||
* phone number.
|
* phone number.
|
||||||
*/
|
*/
|
||||||
class FloodError extends RPCError {
|
class FloodError extends RPCError {
|
||||||
code = 420
|
code = 420;
|
||||||
message = 'FLOOD'
|
message = 'FLOOD';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,8 +96,8 @@ class FloodError extends RPCError {
|
|||||||
* storage
|
* storage
|
||||||
*/
|
*/
|
||||||
class ServerError extends RPCError {
|
class ServerError extends RPCError {
|
||||||
code = 500 // Also witnessed as -500
|
code = 500; // Also witnessed as -500
|
||||||
message = 'INTERNAL'
|
message = 'INTERNAL';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,8 +105,8 @@ class ServerError extends RPCError {
|
|||||||
* call ``answerCallbackQuery`` will result in this "special" RPCError.
|
* call ``answerCallbackQuery`` will result in this "special" RPCError.
|
||||||
*/
|
*/
|
||||||
class TimedOutError extends RPCError {
|
class TimedOutError extends RPCError {
|
||||||
code = 503 // Only witnessed as -503
|
code = 503; // Only witnessed as -503
|
||||||
message = 'Timeout'
|
message = 'Timeout';
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -120,4 +120,4 @@ module.exports = {
|
|||||||
FloodError,
|
FloodError,
|
||||||
ServerError,
|
ServerError,
|
||||||
TimedOutError,
|
TimedOutError,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,75 +1,81 @@
|
|||||||
const { RPCError, InvalidDCError, FloodError, BadRequestError } = require('./RPCBaseErrors')
|
const {
|
||||||
|
RPCError,
|
||||||
|
InvalidDCError,
|
||||||
|
FloodError,
|
||||||
|
BadRequestError,
|
||||||
|
} = require('./RPCBaseErrors');
|
||||||
|
|
||||||
|
|
||||||
class UserMigrateError extends InvalidDCError {
|
class UserMigrateError extends InvalidDCError {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
const newDc = Number(args.capture || 0)
|
const newDc = Number(args.capture || 0);
|
||||||
super(`The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request))
|
super(`The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request));
|
||||||
this.message = `The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)
|
this.message = `The user whose identity is being used to execute queries is associated with DC ${newDc}` + RPCError._fmtRequest(args.request);
|
||||||
this.newDc = newDc
|
this.newDc = newDc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PhoneMigrateError extends InvalidDCError {
|
class PhoneMigrateError extends InvalidDCError {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
const newDc = Number(args.capture || 0)
|
const newDc = Number(args.capture || 0);
|
||||||
super(`The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request))
|
super(`The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request));
|
||||||
this.message = `The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)
|
this.message = `The phone number a user is trying to use for authorization is associated with DC ${newDc}` + RPCError._fmtRequest(args.request);
|
||||||
this.newDc = newDc
|
this.newDc = newDc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SlowModeWaitError extends FloodError {
|
class SlowModeWaitError extends FloodError {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
const seconds = Number(args.capture || 0)
|
const seconds = Number(args.capture || 0);
|
||||||
super(`A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request))
|
super(`A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request));
|
||||||
this.message = `A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request)
|
this.message = `A wait of ${seconds} seconds is required before sending another message in this chat` + RPCError._fmtRequest(args.request);
|
||||||
this.seconds = seconds
|
this.seconds = seconds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FloodWaitError extends FloodError {
|
class FloodWaitError extends FloodError {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
const seconds = Number(args.capture || 0)
|
const seconds = Number(args.capture || 0);
|
||||||
super(`A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request))
|
super(`A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request));
|
||||||
this.message = `A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request)
|
this.message = `A wait of ${seconds} seconds is required` + RPCError._fmtRequest(args.request);
|
||||||
this.seconds = seconds
|
this.seconds = seconds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FloodTestPhoneWaitError extends FloodError {
|
class FloodTestPhoneWaitError extends FloodError {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
const seconds = Number(args.capture || 0)
|
const seconds = Number(args.capture || 0);
|
||||||
super(`A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request))
|
super(`A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request));
|
||||||
this.message = `A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request)
|
this.message = `A wait of ${seconds} seconds is required in the test servers` + RPCError._fmtRequest(args.request);
|
||||||
this.seconds = seconds
|
this.seconds = seconds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileMigrateError extends InvalidDCError {
|
class FileMigrateError extends InvalidDCError {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
const newDc = Number(args.capture || 0)
|
const newDc = Number(args.capture || 0);
|
||||||
super(`The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request))
|
super(`The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request));
|
||||||
this.message = `The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request)
|
this.message = `The file to be accessed is currently stored in DC ${newDc}` + RPCError._fmtRequest(args.request);
|
||||||
this.newDc = newDc
|
this.newDc = newDc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NetworkMigrateError extends InvalidDCError {
|
class NetworkMigrateError extends InvalidDCError {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
const newDc = Number(args.capture || 0)
|
const newDc = Number(args.capture || 0);
|
||||||
super(`The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request))
|
super(`The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request));
|
||||||
this.message = `The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request)
|
this.message = `The source IP address is associated with DC ${newDc}` + RPCError._fmtRequest(args.request);
|
||||||
this.newDc = newDc
|
this.newDc = newDc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmailUnconfirmedError extends BadRequestError {
|
class EmailUnconfirmedError extends BadRequestError {
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
const codeLength = Number(args.capture || 0)
|
const codeLength = Number(args.capture || 0);
|
||||||
super(`Email unconfirmed, the length of the code must be ${codeLength}` + RPCError._fmtRequest(args.request))
|
super(`Email unconfirmed, the length of the code must be ${codeLength}` + RPCError._fmtRequest(args.request));
|
||||||
this.message = `Email unconfirmed, the length of the code must be ${codeLength}` + RPCError._fmtRequest(args.request)
|
this.message = `Email unconfirmed, the length of the code must be ${codeLength}` + RPCError._fmtRequest(args.request);
|
||||||
this.codeLength = codeLength
|
this.codeLength = codeLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +89,7 @@ const rpcErrorRe = [
|
|||||||
[/NETWORK_MIGRATE_(\d+)/, NetworkMigrateError],
|
[/NETWORK_MIGRATE_(\d+)/, NetworkMigrateError],
|
||||||
[/EMAIL_UNCONFIRMED_(\d+)/, EmailUnconfirmedError],
|
[/EMAIL_UNCONFIRMED_(\d+)/, EmailUnconfirmedError],
|
||||||
|
|
||||||
]
|
];
|
||||||
module.exports = {
|
module.exports = {
|
||||||
rpcErrorRe,
|
rpcErrorRe,
|
||||||
FileMigrateError,
|
FileMigrateError,
|
||||||
@ -93,5 +99,5 @@ module.exports = {
|
|||||||
SlowModeWaitError,
|
SlowModeWaitError,
|
||||||
UserMigrateError,
|
UserMigrateError,
|
||||||
NetworkMigrateError,
|
NetworkMigrateError,
|
||||||
EmailUnconfirmedError
|
EmailUnconfirmedError,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -4,19 +4,22 @@
|
|||||||
* @param request the request that caused this error
|
* @param request the request that caused this error
|
||||||
* @constructor the RPCError as a Python exception that represents this error
|
* @constructor the RPCError as a Python exception that represents this error
|
||||||
*/
|
*/
|
||||||
const { RPCError } = require("./RPCBaseErrors")
|
const { RPCError } = require('./RPCBaseErrors');
|
||||||
const { rpcErrorRe } = require('./RPCErrorList')
|
const { rpcErrorRe } = require('./RPCErrorList');
|
||||||
|
|
||||||
function RPCMessageToError(rpcError, request) {
|
function RPCMessageToError(rpcError, request) {
|
||||||
for (const [msgRegex, Cls] of rpcErrorRe) {
|
for (const [msgRegex, Cls] of rpcErrorRe) {
|
||||||
const m = rpcError.errorMessage.match(msgRegex)
|
const m = rpcError.errorMessage.match(msgRegex);
|
||||||
if (m) {
|
if (m) {
|
||||||
const capture = m.length === 2 ? parseInt(m[1]) : null
|
const capture = m.length === 2 ? parseInt(m[1]) : null;
|
||||||
return new Cls({ request: request, capture: capture })
|
return new Cls({
|
||||||
|
request: request,
|
||||||
|
capture: capture,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RPCError(rpcError.errorMessage, request)
|
return new RPCError(rpcError.errorMessage, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -24,4 +27,4 @@ module.exports = {
|
|||||||
...require('./Common'),
|
...require('./Common'),
|
||||||
...require('./RPCBaseErrors'),
|
...require('./RPCBaseErrors'),
|
||||||
...require('./RPCErrorList'),
|
...require('./RPCErrorList'),
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
const { EventBuilder } = require('./common')
|
const { EventBuilder } = require('./common');
|
||||||
|
|
||||||
class Raw extends EventBuilder {
|
class Raw extends EventBuilder {
|
||||||
constructor(args = {
|
constructor(args = {
|
||||||
types: null,
|
types: null,
|
||||||
func: null,
|
func: null,
|
||||||
}) {
|
}) {
|
||||||
super()
|
super();
|
||||||
if (!args.types) {
|
if (!args.types) {
|
||||||
this.types = true
|
this.types = true;
|
||||||
} else {
|
} else {
|
||||||
this.types = args.types
|
this.types = args.types;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
build(update, others = null) {
|
build(update, others = null) {
|
||||||
return update
|
return update;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Raw
|
module.exports = Raw;
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
class EventBuilder {
|
class EventBuilder {
|
||||||
constructor(args = {
|
constructor(args = {
|
||||||
chats: null, blacklistChats: null, func: null,
|
chats: null,
|
||||||
},
|
blacklistChats: null,
|
||||||
|
func: null,
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
this.chats = args.chats
|
this.chats = args.chats;
|
||||||
this.blacklistChats = Boolean(args.blacklistChats)
|
this.blacklistChats = Boolean(args.blacklistChats);
|
||||||
this.resolved = false
|
this.resolved = false;
|
||||||
this.func = args.func
|
this.func = args.func;
|
||||||
}
|
}
|
||||||
|
|
||||||
build(update, others = null) {
|
build(update, others = null) {
|
||||||
@ -19,4 +21,7 @@ class EventCommon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = { EventBuilder, EventCommon }
|
module.exports = {
|
||||||
|
EventBuilder,
|
||||||
|
EventCommon,
|
||||||
|
};
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
const NewMessage = require('./NewMessage')
|
const NewMessage = require('./NewMessage');
|
||||||
const Raw = require('./Raw')
|
const Raw = require('./Raw');
|
||||||
|
|
||||||
class StopPropagation extends Error {
|
class StopPropagation extends Error {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { NewMessage, StopPropagation, Raw }
|
module.exports = {
|
||||||
|
NewMessage,
|
||||||
|
StopPropagation,
|
||||||
|
Raw,
|
||||||
|
};
|
||||||
|
|||||||
@ -1,30 +1,30 @@
|
|||||||
class AsyncQueue {
|
class AsyncQueue {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._queue = []
|
this._queue = [];
|
||||||
this.canGet = new Promise((resolve) => {
|
this.canGet = new Promise((resolve) => {
|
||||||
this.resolveGet = resolve
|
this.resolveGet = resolve;
|
||||||
})
|
});
|
||||||
this.canPush = true
|
this.canPush = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async push(value) {
|
async push(value) {
|
||||||
await this.canPush
|
await this.canPush;
|
||||||
this._queue.push(value)
|
this._queue.push(value);
|
||||||
this.resolveGet(true)
|
this.resolveGet(true);
|
||||||
this.canPush = new Promise((resolve) => {
|
this.canPush = new Promise((resolve) => {
|
||||||
this.resolvePush = resolve
|
this.resolvePush = resolve;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async pop() {
|
async pop() {
|
||||||
await this.canGet
|
await this.canGet;
|
||||||
const returned = this._queue.pop()
|
const returned = this._queue.pop();
|
||||||
this.resolvePush(true)
|
this.resolvePush(true);
|
||||||
this.canGet = new Promise((resolve) => {
|
this.canGet = new Promise((resolve) => {
|
||||||
this.resolveGet = resolve
|
this.resolveGet = resolve;
|
||||||
})
|
});
|
||||||
return returned
|
return returned;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AsyncQueue
|
module.exports = AsyncQueue;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
const { TypeNotFoundError } = require('../errors/Common')
|
const { TypeNotFoundError } = require('../errors/Common');
|
||||||
const { coreObjects } = require('../tl/core')
|
const { coreObjects } = require('../tl/core');
|
||||||
const { tlobjects } = require('../tl/AllTLObjects')
|
const { tlobjects } = require('../tl/AllTLObjects');
|
||||||
const { readBigIntFromBuffer } = require('../Helpers')
|
const { readBigIntFromBuffer } = require('../Helpers');
|
||||||
|
|
||||||
class BinaryReader {
|
class BinaryReader {
|
||||||
/**
|
/**
|
||||||
@ -9,9 +9,9 @@ class BinaryReader {
|
|||||||
* @param data {Buffer}
|
* @param data {Buffer}
|
||||||
*/
|
*/
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.stream = data
|
this.stream = data;
|
||||||
this._last = null
|
this._last = null;
|
||||||
this.offset = 0
|
this.offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// region Reading
|
// region Reading
|
||||||
@ -22,7 +22,7 @@ class BinaryReader {
|
|||||||
* Reads a single byte value.
|
* Reads a single byte value.
|
||||||
*/
|
*/
|
||||||
readByte() {
|
readByte() {
|
||||||
return this.read(1)[0]
|
return this.read(1)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,14 +30,14 @@ class BinaryReader {
|
|||||||
* @param signed {Boolean}
|
* @param signed {Boolean}
|
||||||
*/
|
*/
|
||||||
readInt(signed = true) {
|
readInt(signed = true) {
|
||||||
let res
|
let res;
|
||||||
if (signed) {
|
if (signed) {
|
||||||
res = this.stream.readInt32LE(this.offset)
|
res = this.stream.readInt32LE(this.offset);
|
||||||
} else {
|
} else {
|
||||||
res = this.stream.readUInt32LE(this.offset)
|
res = this.stream.readUInt32LE(this.offset);
|
||||||
}
|
}
|
||||||
this.offset += 4
|
this.offset += 4;
|
||||||
return res
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,7 +46,7 @@ class BinaryReader {
|
|||||||
* @returns {BigInteger}
|
* @returns {BigInteger}
|
||||||
*/
|
*/
|
||||||
readLong(signed = true) {
|
readLong(signed = true) {
|
||||||
return this.readLargeInt(64, signed)
|
return this.readLargeInt(64, signed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,7 +54,8 @@ class BinaryReader {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
readFloat() {
|
readFloat() {
|
||||||
return this.read(4).readFloatLE(0)
|
return this.read(4)
|
||||||
|
.readFloatLE(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +64,8 @@ class BinaryReader {
|
|||||||
*/
|
*/
|
||||||
readDouble() {
|
readDouble() {
|
||||||
// was this a bug ? it should have been <d
|
// was this a bug ? it should have been <d
|
||||||
return this.read(8).readDoubleLE(0)
|
return this.read(8)
|
||||||
|
.readDoubleLE(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,8 +74,8 @@ class BinaryReader {
|
|||||||
* @param signed {Boolean}
|
* @param signed {Boolean}
|
||||||
*/
|
*/
|
||||||
readLargeInt(bits, signed = true) {
|
readLargeInt(bits, signed = true) {
|
||||||
const buffer = this.read(Math.floor(bits / 8))
|
const buffer = this.read(Math.floor(bits / 8));
|
||||||
return readBigIntFromBuffer(buffer, true, signed)
|
return readBigIntFromBuffer(buffer, true, signed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,17 +84,17 @@ class BinaryReader {
|
|||||||
*/
|
*/
|
||||||
read(length = -1) {
|
read(length = -1) {
|
||||||
if (length === -1) {
|
if (length === -1) {
|
||||||
length = this.stream.length - this.offset
|
length = this.stream.length - this.offset;
|
||||||
}
|
}
|
||||||
const result = this.stream.slice(this.offset, this.offset + length)
|
const result = this.stream.slice(this.offset, this.offset + length);
|
||||||
this.offset += length
|
this.offset += length;
|
||||||
if (result.length !== length) {
|
if (result.length !== length) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`No more data left to read (need ${length}, got ${result.length}: ${result}); last read ${this._last}`,
|
`No more data left to read (need ${length}, got ${result.length}: ${result}); last read ${this._last}`,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
this._last = result
|
this._last = result;
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,7 +102,7 @@ class BinaryReader {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
getBuffer() {
|
getBuffer() {
|
||||||
return this.stream
|
return this.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
@ -112,24 +114,24 @@ class BinaryReader {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
tgReadBytes() {
|
tgReadBytes() {
|
||||||
const firstByte = this.readByte()
|
const firstByte = this.readByte();
|
||||||
let padding
|
let padding;
|
||||||
let length
|
let length;
|
||||||
if (firstByte === 254) {
|
if (firstByte === 254) {
|
||||||
length = this.readByte() | (this.readByte() << 8) | (this.readByte() << 16)
|
length = this.readByte() | (this.readByte() << 8) | (this.readByte() << 16);
|
||||||
padding = length % 4
|
padding = length % 4;
|
||||||
} else {
|
} else {
|
||||||
length = firstByte
|
length = firstByte;
|
||||||
padding = (length + 1) % 4
|
padding = (length + 1) % 4;
|
||||||
}
|
}
|
||||||
const data = this.read(length)
|
const data = this.read(length);
|
||||||
|
|
||||||
if (padding > 0) {
|
if (padding > 0) {
|
||||||
padding = 4 - padding
|
padding = 4 - padding;
|
||||||
this.read(padding)
|
this.read(padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,7 +139,8 @@ class BinaryReader {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
tgReadString() {
|
tgReadString() {
|
||||||
return this.tgReadBytes().toString('utf-8')
|
return this.tgReadBytes()
|
||||||
|
.toString('utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,15 +148,15 @@ class BinaryReader {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
tgReadBool() {
|
tgReadBool() {
|
||||||
const value = this.readInt(false)
|
const value = this.readInt(false);
|
||||||
if (value === 0x997275b5) {
|
if (value === 0x997275b5) {
|
||||||
// boolTrue
|
// boolTrue
|
||||||
return true
|
return true;
|
||||||
} else if (value === 0xbc799737) {
|
} else if (value === 0xbc799737) {
|
||||||
// boolFalse
|
// boolFalse
|
||||||
return false
|
return false;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Invalid boolean code ${value.toString('16')}`)
|
throw new Error(`Invalid boolean code ${value.toString('16')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,50 +166,50 @@ class BinaryReader {
|
|||||||
* @returns {Date}
|
* @returns {Date}
|
||||||
*/
|
*/
|
||||||
tgReadDate() {
|
tgReadDate() {
|
||||||
const value = this.readInt()
|
const value = this.readInt();
|
||||||
return new Date(value * 1000)
|
return new Date(value * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a Telegram object.
|
* Reads a Telegram object.
|
||||||
*/
|
*/
|
||||||
tgReadObject() {
|
tgReadObject() {
|
||||||
const constructorId = this.readInt(false)
|
const constructorId = this.readInt(false);
|
||||||
let clazz = tlobjects[constructorId]
|
let clazz = tlobjects[constructorId];
|
||||||
if (clazz === undefined) {
|
if (clazz === undefined) {
|
||||||
/**
|
/**
|
||||||
* The class was None, but there's still a
|
* The class was None, but there's still a
|
||||||
* chance of it being a manually parsed value like bool!
|
* chance of it being a manually parsed value like bool!
|
||||||
*/
|
*/
|
||||||
const value = constructorId
|
const value = constructorId;
|
||||||
if (value === 0x997275b5) {
|
if (value === 0x997275b5) {
|
||||||
// boolTrue
|
// boolTrue
|
||||||
return true
|
return true;
|
||||||
} else if (value === 0xbc799737) {
|
} else if (value === 0xbc799737) {
|
||||||
// boolFalse
|
// boolFalse
|
||||||
return false
|
return false;
|
||||||
} else if (value === 0x1cb5c415) {
|
} else if (value === 0x1cb5c415) {
|
||||||
// Vector
|
// Vector
|
||||||
const temp = []
|
const temp = [];
|
||||||
const length = this.readInt()
|
const length = this.readInt();
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
temp.push(this.tgReadObject())
|
temp.push(this.tgReadObject());
|
||||||
}
|
}
|
||||||
return temp
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
clazz = coreObjects[constructorId]
|
clazz = coreObjects[constructorId];
|
||||||
|
|
||||||
if (clazz === undefined) {
|
if (clazz === undefined) {
|
||||||
// If there was still no luck, give up
|
// If there was still no luck, give up
|
||||||
this.seek(-4) // Go back
|
this.seek(-4); // Go back
|
||||||
const pos = this.tellPosition()
|
const pos = this.tellPosition();
|
||||||
const error = new TypeNotFoundError(constructorId, this.read())
|
const error = new TypeNotFoundError(constructorId, this.read());
|
||||||
this.setPosition(pos)
|
this.setPosition(pos);
|
||||||
throw error
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return clazz.fromReader(this)
|
return clazz.fromReader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -215,14 +218,14 @@ class BinaryReader {
|
|||||||
*/
|
*/
|
||||||
tgReadVector() {
|
tgReadVector() {
|
||||||
if (this.readInt(false) !== 0x1cb5c415) {
|
if (this.readInt(false) !== 0x1cb5c415) {
|
||||||
throw new Error('Invalid constructor code, vector was expected')
|
throw new Error('Invalid constructor code, vector was expected');
|
||||||
}
|
}
|
||||||
const count = this.readInt()
|
const count = this.readInt();
|
||||||
const temp = []
|
const temp = [];
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
temp.push(this.tgReadObject())
|
temp.push(this.tgReadObject());
|
||||||
}
|
}
|
||||||
return temp
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
@ -231,7 +234,7 @@ class BinaryReader {
|
|||||||
* Closes the reader.
|
* Closes the reader.
|
||||||
*/
|
*/
|
||||||
close() {
|
close() {
|
||||||
this.stream = null
|
this.stream = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// region Position related
|
// region Position related
|
||||||
@ -241,7 +244,7 @@ class BinaryReader {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
tellPosition() {
|
tellPosition() {
|
||||||
return this.offset
|
return this.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,7 +252,7 @@ class BinaryReader {
|
|||||||
* @param position
|
* @param position
|
||||||
*/
|
*/
|
||||||
setPosition(position) {
|
setPosition(position) {
|
||||||
this.offset = position
|
this.offset = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -258,10 +261,10 @@ class BinaryReader {
|
|||||||
* @param offset
|
* @param offset
|
||||||
*/
|
*/
|
||||||
seek(offset) {
|
seek(offset) {
|
||||||
this.offset += offset
|
this.offset += offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BinaryReader
|
module.exports = BinaryReader;
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
class BinaryWriter {
|
class BinaryWriter {
|
||||||
constructor(stream) {
|
constructor(stream) {
|
||||||
this._stream = stream
|
this._stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
write(buffer) {
|
write(buffer) {
|
||||||
this._stream = Buffer.concat([this._stream, buffer])
|
this._stream = Buffer.concat([this._stream, buffer]);
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue() {
|
getValue() {
|
||||||
return this._stream
|
return this._stream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BinaryWriter
|
module.exports = BinaryWriter;
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
let _level = null
|
let _level = null;
|
||||||
|
|
||||||
class Logger {
|
class Logger {
|
||||||
static levels = ['error', 'warn', 'info', 'debug']
|
static levels = ['error', 'warn', 'info', 'debug'];
|
||||||
|
|
||||||
constructor(level) {
|
constructor(level) {
|
||||||
if (!_level) {
|
if (!_level) {
|
||||||
_level = level || 'debug'
|
_level = level || 'debug';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isBrowser = typeof process === 'undefined' ||
|
this.isBrowser = typeof process === 'undefined' ||
|
||||||
process.type === 'renderer' ||
|
process.type === 'renderer' ||
|
||||||
process.browser === true ||
|
process.browser === true ||
|
||||||
process.__nwjs
|
process.__nwjs;
|
||||||
if (!this.isBrowser) {
|
if (!this.isBrowser) {
|
||||||
this.colors = {
|
this.colors = {
|
||||||
start: '\x1b[2m',
|
start: '\x1b[2m',
|
||||||
@ -20,7 +20,7 @@ class Logger {
|
|||||||
debug: '\x1b[36m',
|
debug: '\x1b[36m',
|
||||||
error: '\x1b[31m',
|
error: '\x1b[31m',
|
||||||
end: '\x1b[0m',
|
end: '\x1b[0m',
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
this.colors = {
|
this.colors = {
|
||||||
start: '%c',
|
start: '%c',
|
||||||
@ -29,9 +29,13 @@ class Logger {
|
|||||||
debug: 'color : #00ffff',
|
debug: 'color : #00ffff',
|
||||||
error: 'color : #ff0000',
|
error: 'color : #ff0000',
|
||||||
end: '',
|
end: '',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
this.messageFormat = '[%t] [%l] - [%m]'
|
this.messageFormat = '[%t] [%l] - [%m]';
|
||||||
|
}
|
||||||
|
|
||||||
|
static setLevel(level) {
|
||||||
|
_level = level;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,45 +44,41 @@ class Logger {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
canSend(level) {
|
canSend(level) {
|
||||||
return (Logger.levels.indexOf(_level) >= Logger.levels.indexOf(level))
|
return (Logger.levels.indexOf(_level) >= Logger.levels.indexOf(level));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param message {string}
|
* @param message {string}
|
||||||
*/
|
*/
|
||||||
warn(message) {
|
warn(message) {
|
||||||
this._log('warn', message, this.colors.warn)
|
this._log('warn', message, this.colors.warn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param message {string}
|
* @param message {string}
|
||||||
*/
|
*/
|
||||||
info(message) {
|
info(message) {
|
||||||
this._log('info', message, this.colors.info)
|
this._log('info', message, this.colors.info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param message {string}
|
* @param message {string}
|
||||||
*/
|
*/
|
||||||
debug(message) {
|
debug(message) {
|
||||||
this._log('debug', message, this.colors.debug)
|
this._log('debug', message, this.colors.debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param message {string}
|
* @param message {string}
|
||||||
*/
|
*/
|
||||||
error(message) {
|
error(message) {
|
||||||
this._log('error', message, this.colors.error)
|
this._log('error', message, this.colors.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
format(message, level) {
|
format(message, level) {
|
||||||
return this.messageFormat.replace('%t', new Date().toISOString())
|
return this.messageFormat.replace('%t', new Date().toISOString())
|
||||||
.replace('%l', level.toUpperCase())
|
.replace('%l', level.toUpperCase())
|
||||||
.replace('%m', message)
|
.replace('%m', message);
|
||||||
}
|
|
||||||
|
|
||||||
static setLevel(level) {
|
|
||||||
_level = level;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,14 +87,14 @@ class Logger {
|
|||||||
* @param color {string}
|
* @param color {string}
|
||||||
*/
|
*/
|
||||||
_log(level, message, color) {
|
_log(level, message, color) {
|
||||||
if (!_level){
|
if (!_level) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (this.canSend(level)) {
|
if (this.canSend(level)) {
|
||||||
if (!this.isBrowser) {
|
if (!this.isBrowser) {
|
||||||
console.log(color + this.format(message, level) + this.colors.end)
|
console.log(color + this.format(message, level) + this.colors.end);
|
||||||
} else {
|
} else {
|
||||||
console.log(this.colors.start + this.format(message, level), color)
|
console.log(this.colors.start + this.format(message, level), color);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@ -102,4 +102,4 @@ class Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Logger
|
module.exports = Logger;
|
||||||
|
|||||||
@ -1,112 +1,116 @@
|
|||||||
const MessageContainer = require('../tl/core/MessageContainer')
|
const MessageContainer = require('../tl/core/MessageContainer');
|
||||||
const TLMessage = require('../tl/core/TLMessage')
|
const TLMessage = require('../tl/core/TLMessage');
|
||||||
const BinaryWriter = require('../extensions/BinaryWriter')
|
const BinaryWriter = require('../extensions/BinaryWriter');
|
||||||
|
|
||||||
class MessagePacker {
|
class MessagePacker {
|
||||||
constructor(state, logger) {
|
constructor(state, logger) {
|
||||||
this._state = state
|
this._state = state;
|
||||||
this._queue = []
|
this._queue = [];
|
||||||
this._pendingStates = []
|
this._pendingStates = [];
|
||||||
this._ready = new Promise(((resolve) => {
|
this._ready = new Promise(((resolve) => {
|
||||||
this.setReady = resolve
|
this.setReady = resolve;
|
||||||
}))
|
}));
|
||||||
this._log = logger
|
this._log = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
values() {
|
values() {
|
||||||
return this._queue
|
return this._queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
append(state) {
|
append(state) {
|
||||||
this._queue.push(state)
|
this._queue.push(state);
|
||||||
this.setReady(true)
|
this.setReady(true);
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
this._pendingStates.push(state)
|
this._pendingStates.push(state);
|
||||||
state.promise
|
state.promise
|
||||||
// Using finally causes triggering `unhandledrejection` event
|
// Using finally causes triggering `unhandledrejection` event
|
||||||
.catch(() => {})
|
.catch(() => {
|
||||||
.finally(() => {
|
|
||||||
this._pendingStates = this._pendingStates.filter((s) => s !== state)
|
|
||||||
})
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this._pendingStates = this._pendingStates.filter((s) => s !== state);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extend(states) {
|
extend(states) {
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
this._queue.push(state)
|
this._queue.push(state);
|
||||||
}
|
}
|
||||||
this.setReady(true)
|
this.setReady(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async get() {
|
async get() {
|
||||||
if (!this._queue.length) {
|
if (!this._queue.length) {
|
||||||
this._ready = new Promise(((resolve) => {
|
this._ready = new Promise(((resolve) => {
|
||||||
this.setReady = resolve
|
this.setReady = resolve;
|
||||||
}))
|
}));
|
||||||
await this._ready
|
await this._ready;
|
||||||
}
|
}
|
||||||
if (!this._queue[this._queue.length - 1]) {
|
if (!this._queue[this._queue.length - 1]) {
|
||||||
this._queue = []
|
this._queue = [];
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
let data
|
let data;
|
||||||
let buffer = new BinaryWriter(Buffer.alloc(0))
|
let buffer = new BinaryWriter(Buffer.alloc(0));
|
||||||
|
|
||||||
const batch = []
|
const batch = [];
|
||||||
let size = 0
|
let size = 0;
|
||||||
|
|
||||||
while (this._queue.length && batch.length <= MessageContainer.MAXIMUM_LENGTH) {
|
while (this._queue.length && batch.length <= MessageContainer.MAXIMUM_LENGTH) {
|
||||||
const state = this._queue.shift()
|
const state = this._queue.shift();
|
||||||
size += state.data.length + TLMessage.SIZE_OVERHEAD
|
size += state.data.length + TLMessage.SIZE_OVERHEAD;
|
||||||
if (size <= MessageContainer.MAXIMUM_SIZE) {
|
if (size <= MessageContainer.MAXIMUM_SIZE) {
|
||||||
let afterId
|
let afterId;
|
||||||
if (state.after) {
|
if (state.after) {
|
||||||
afterId = state.after.msgId
|
afterId = state.after.msgId;
|
||||||
}
|
}
|
||||||
state.msgId = await this._state.writeDataAsMessage(
|
state.msgId = await this._state.writeDataAsMessage(
|
||||||
buffer, state.data, state.request.classType === 'request',
|
buffer, state.data, state.request.classType === 'request',
|
||||||
afterId,
|
afterId,
|
||||||
)
|
);
|
||||||
this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.className || state.request.constructor.name}`)
|
this._log.debug(`Assigned msgId = ${state.msgId} to ${state.request.className || state.request.constructor.name}`);
|
||||||
batch.push(state)
|
batch.push(state);
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
if (batch.length) {
|
if (batch.length) {
|
||||||
this._queue.unshift(state)
|
this._queue.unshift(state);
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
this._log.warn(`Message payload for ${state.request.className || state.request.constructor.name} is too long ${state.data.length} and cannot be sent`)
|
this._log.warn(`Message payload for ${state.request.className || state.request.constructor.name} is too long ${state.data.length} and cannot be sent`);
|
||||||
state.reject('Request Payload is too big')
|
state.reject('Request Payload is too big');
|
||||||
size = 0
|
size = 0;
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if (!batch.length) {
|
if (!batch.length) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
if (batch.length > 1) {
|
if (batch.length > 1) {
|
||||||
const b = Buffer.alloc(8)
|
const b = Buffer.alloc(8);
|
||||||
b.writeUInt32LE(MessageContainer.CONSTRUCTOR_ID, 0)
|
b.writeUInt32LE(MessageContainer.CONSTRUCTOR_ID, 0);
|
||||||
b.writeInt32LE(batch.length, 4)
|
b.writeInt32LE(batch.length, 4);
|
||||||
data = Buffer.concat([b, buffer.getValue()])
|
data = Buffer.concat([b, buffer.getValue()]);
|
||||||
buffer = new BinaryWriter(Buffer.alloc(0))
|
buffer = new BinaryWriter(Buffer.alloc(0));
|
||||||
const containerId = await this._state.writeDataAsMessage(
|
const containerId = await this._state.writeDataAsMessage(
|
||||||
buffer, data, false,
|
buffer, data, false,
|
||||||
)
|
);
|
||||||
for (const s of batch) {
|
for (const s of batch) {
|
||||||
s.containerId = containerId
|
s.containerId = containerId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data = buffer.getValue()
|
data = buffer.getValue();
|
||||||
return { batch, data }
|
return {
|
||||||
|
batch,
|
||||||
|
data,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
rejectAll() {
|
rejectAll() {
|
||||||
this._pendingStates.forEach((requestState) => {
|
this._pendingStates.forEach((requestState) => {
|
||||||
requestState.reject(new Error('Disconnect'))
|
requestState.reject(new Error('Disconnect'));
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MessagePacker
|
module.exports = MessagePacker;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
const Mutex = require('async-mutex').Mutex
|
const Mutex = require('async-mutex').Mutex;
|
||||||
const mutex = new Mutex()
|
const mutex = new Mutex();
|
||||||
|
|
||||||
const WebSocketClient = require('websocket').w3cwebsocket
|
const WebSocketClient = require('websocket').w3cwebsocket;
|
||||||
|
|
||||||
const closeError = new Error('WebSocket was closed')
|
const closeError = new Error('WebSocket was closed');
|
||||||
|
|
||||||
class PromisedWebSockets {
|
class PromisedWebSockets {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -14,117 +14,117 @@ class PromisedWebSockets {
|
|||||||
process.__nwjs
|
process.__nwjs
|
||||||
|
|
||||||
*/
|
*/
|
||||||
this.client = null
|
this.client = null;
|
||||||
this.closed = true
|
this.closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async readExactly(number) {
|
async readExactly(number) {
|
||||||
let readData = Buffer.alloc(0)
|
let readData = Buffer.alloc(0);
|
||||||
while (true) {
|
while (true) {
|
||||||
const thisTime = await this.read(number)
|
const thisTime = await this.read(number);
|
||||||
readData = Buffer.concat([readData, thisTime])
|
readData = Buffer.concat([readData, thisTime]);
|
||||||
number = number - thisTime.length
|
number = number - thisTime.length;
|
||||||
if (!number) {
|
if (!number) {
|
||||||
return readData
|
return readData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async read(number) {
|
async read(number) {
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
throw closeError
|
throw closeError;
|
||||||
}
|
}
|
||||||
await this.canRead
|
await this.canRead;
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
throw closeError
|
throw closeError;
|
||||||
}
|
}
|
||||||
const toReturn = this.stream.slice(0, number)
|
const toReturn = this.stream.slice(0, number);
|
||||||
this.stream = this.stream.slice(number)
|
this.stream = this.stream.slice(number);
|
||||||
if (this.stream.length === 0) {
|
if (this.stream.length === 0) {
|
||||||
this.canRead = new Promise((resolve) => {
|
this.canRead = new Promise((resolve) => {
|
||||||
this.resolveRead = resolve
|
this.resolveRead = resolve;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return toReturn
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
async readAll() {
|
async readAll() {
|
||||||
if (this.closed || !await this.canRead) {
|
if (this.closed || !await this.canRead) {
|
||||||
throw closeError
|
throw closeError;
|
||||||
}
|
}
|
||||||
const toReturn = this.stream
|
const toReturn = this.stream;
|
||||||
this.stream = Buffer.alloc(0)
|
this.stream = Buffer.alloc(0);
|
||||||
this.canRead = new Promise((resolve) => {
|
this.canRead = new Promise((resolve) => {
|
||||||
this.resolveRead = resolve
|
this.resolveRead = resolve;
|
||||||
})
|
});
|
||||||
|
|
||||||
return toReturn
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
getWebSocketLink(ip, port) {
|
getWebSocketLink(ip, port) {
|
||||||
if (port === 443) {
|
if (port === 443) {
|
||||||
return `wss://${ip}:${port}/apiws`
|
return `wss://${ip}:${port}/apiws`;
|
||||||
} else {
|
} else {
|
||||||
return `ws://${ip}:${port}/apiws`
|
return `ws://${ip}:${port}/apiws`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(port, ip) {
|
async connect(port, ip) {
|
||||||
this.stream = Buffer.alloc(0)
|
this.stream = Buffer.alloc(0);
|
||||||
this.canRead = new Promise((resolve) => {
|
this.canRead = new Promise((resolve) => {
|
||||||
this.resolveRead = resolve
|
this.resolveRead = resolve;
|
||||||
})
|
});
|
||||||
this.closed = false
|
this.closed = false;
|
||||||
this.website = this.getWebSocketLink(ip, port)
|
this.website = this.getWebSocketLink(ip, port);
|
||||||
this.client = new WebSocketClient(this.website, 'binary')
|
this.client = new WebSocketClient(this.website, 'binary');
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.client.onopen = () => {
|
this.client.onopen = () => {
|
||||||
this.receive()
|
this.receive();
|
||||||
resolve(this)
|
resolve(this);
|
||||||
}
|
};
|
||||||
this.client.onerror = (error) => {
|
this.client.onerror = (error) => {
|
||||||
reject(error)
|
reject(error);
|
||||||
}
|
};
|
||||||
this.client.onclose = () => {
|
this.client.onclose = () => {
|
||||||
this.resolveRead(false)
|
this.resolveRead(false);
|
||||||
this.closed = true
|
this.closed = true;
|
||||||
}
|
};
|
||||||
//CONTEST
|
//CONTEST
|
||||||
// Seems to not be working, at least in a web worker
|
// Seems to not be working, at least in a web worker
|
||||||
self.addEventListener('offline', async () => {
|
self.addEventListener('offline', async () => {
|
||||||
await this.close()
|
await this.close();
|
||||||
this.resolveRead(false)
|
this.resolveRead(false);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
write(data) {
|
write(data) {
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
throw closeError
|
throw closeError;
|
||||||
}
|
}
|
||||||
this.client.send(data)
|
this.client.send(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
await this.client.close()
|
await this.client.close();
|
||||||
this.closed = true
|
this.closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async receive() {
|
async receive() {
|
||||||
this.client.onmessage = async (message) => {
|
this.client.onmessage = async (message) => {
|
||||||
const release = await mutex.acquire()
|
const release = await mutex.acquire();
|
||||||
try {
|
try {
|
||||||
const data = message.data instanceof ArrayBuffer
|
const data = message.data instanceof ArrayBuffer
|
||||||
? Buffer.from(message.data)
|
? Buffer.from(message.data)
|
||||||
: Buffer.from(await new Response(message.data).arrayBuffer())
|
: Buffer.from(await new Response(message.data).arrayBuffer());
|
||||||
this.stream = Buffer.concat([this.stream, data])
|
this.stream = Buffer.concat([this.stream, data]);
|
||||||
this.resolveRead(true)
|
this.resolveRead(true);
|
||||||
} finally {
|
} finally {
|
||||||
release()
|
release();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PromisedWebSockets
|
module.exports = PromisedWebSockets;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
const Logger = require('./Logger')
|
const Logger = require('./Logger');
|
||||||
const BinaryWriter = require('./BinaryWriter')
|
const BinaryWriter = require('./BinaryWriter');
|
||||||
const BinaryReader = require('./BinaryReader')
|
const BinaryReader = require('./BinaryReader');
|
||||||
const PromisedWebSockets = require('./PromisedWebSockets')
|
const PromisedWebSockets = require('./PromisedWebSockets');
|
||||||
const MessagePacker = require('./MessagePacker')
|
const MessagePacker = require('./MessagePacker');
|
||||||
const AsyncQueue = require('./AsyncQueue')
|
const AsyncQueue = require('./AsyncQueue');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
BinaryWriter,
|
BinaryWriter,
|
||||||
BinaryReader,
|
BinaryReader,
|
||||||
@ -11,4 +11,4 @@ module.exports = {
|
|||||||
AsyncQueue,
|
AsyncQueue,
|
||||||
Logger,
|
Logger,
|
||||||
PromisedWebSockets,
|
PromisedWebSockets,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,16 +1,25 @@
|
|||||||
const Api = require('./tl/api')
|
const Api = require('./tl/api');
|
||||||
const TelegramClient = require('./client/TelegramClient')
|
const TelegramClient = require('./client/TelegramClient');
|
||||||
const connection = require('./network')
|
const connection = require('./network');
|
||||||
const tl = require('./tl')
|
const tl = require('./tl');
|
||||||
const version = require('./Version')
|
const version = require('./Version');
|
||||||
const events = require('./events')
|
const events = require('./events');
|
||||||
const utils = require('./Utils')
|
const utils = require('./Utils');
|
||||||
const errors = require('./errors')
|
const errors = require('./errors');
|
||||||
const sessions = require('./sessions')
|
const sessions = require('./sessions');
|
||||||
const extensions = require('./extensions')
|
const extensions = require('./extensions');
|
||||||
const helpers = require('./Helpers')
|
const helpers = require('./Helpers');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Api, TelegramClient, sessions, connection, extensions,
|
Api,
|
||||||
tl, version, events, utils, errors, helpers,
|
TelegramClient,
|
||||||
}
|
sessions,
|
||||||
|
connection,
|
||||||
|
extensions,
|
||||||
|
tl,
|
||||||
|
version,
|
||||||
|
events,
|
||||||
|
utils,
|
||||||
|
errors,
|
||||||
|
helpers,
|
||||||
|
};
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
const BigInt = require('big-integer')
|
const BigInt = require('big-integer');
|
||||||
const IGE = require('../crypto/IGE')
|
const IGE = require('../crypto/IGE');
|
||||||
const AuthKey = require('../crypto/AuthKey')
|
const AuthKey = require('../crypto/AuthKey');
|
||||||
const Factorizator = require('../crypto/Factorizator')
|
const Factorizator = require('../crypto/Factorizator');
|
||||||
const RSA = require('../crypto/RSA')
|
const RSA = require('../crypto/RSA');
|
||||||
const Helpers = require('../Helpers')
|
const Helpers = require('../Helpers');
|
||||||
const { constructors, requests } = require('../tl')
|
const {
|
||||||
const BinaryReader = require('../extensions/BinaryReader')
|
constructors,
|
||||||
const { SecurityError } = require("../errors/Common")
|
requests,
|
||||||
|
} = require('../tl');
|
||||||
|
const BinaryReader = require('../extensions/BinaryReader');
|
||||||
|
const { SecurityError } = require('../errors/Common');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the authentication process with the Telegram servers.
|
* Executes the authentication process with the Telegram servers.
|
||||||
@ -16,31 +19,34 @@ const { SecurityError } = require("../errors/Common")
|
|||||||
*/
|
*/
|
||||||
async function doAuthentication(sender, log) {
|
async function doAuthentication(sender, log) {
|
||||||
// Step 1 sending: PQ Request, endianness doesn't matter since it's random
|
// Step 1 sending: PQ Request, endianness doesn't matter since it's random
|
||||||
let bytes = Helpers.generateRandomBytes(16)
|
let bytes = Helpers.generateRandomBytes(16);
|
||||||
|
|
||||||
const nonce = Helpers.readBigIntFromBuffer(bytes, false, true)
|
const nonce = Helpers.readBigIntFromBuffer(bytes, false, true);
|
||||||
|
|
||||||
const resPQ = await sender.send(new requests.ReqPqMulti({ nonce: nonce }))
|
const resPQ = await sender.send(new requests.ReqPqMulti({ nonce: nonce }));
|
||||||
log.debug('Starting authKey generation step 1')
|
log.debug('Starting authKey generation step 1');
|
||||||
|
|
||||||
if (!(resPQ instanceof constructors.ResPQ)) {
|
if (!(resPQ instanceof constructors.ResPQ)) {
|
||||||
throw new Error(`Step 1 answer was ${resPQ}`)
|
throw new Error(`Step 1 answer was ${resPQ}`);
|
||||||
}
|
}
|
||||||
if (resPQ.nonce.neq(nonce)) {
|
if (resPQ.nonce.neq(nonce)) {
|
||||||
throw new SecurityError('Step 1 invalid nonce from server')
|
throw new SecurityError('Step 1 invalid nonce from server');
|
||||||
}
|
}
|
||||||
const pq = Helpers.readBigIntFromBuffer(resPQ.pq, false, true)
|
const pq = Helpers.readBigIntFromBuffer(resPQ.pq, false, true);
|
||||||
log.debug('Finished authKey generation step 1')
|
log.debug('Finished authKey generation step 1');
|
||||||
log.debug('Starting authKey generation step 2')
|
log.debug('Starting authKey generation step 2');
|
||||||
// Step 2 sending: DH Exchange
|
// Step 2 sending: DH Exchange
|
||||||
let { p, q } = Factorizator.factorize(pq)
|
let {
|
||||||
|
p,
|
||||||
|
q,
|
||||||
|
} = Factorizator.factorize(pq);
|
||||||
|
|
||||||
// TODO Bring back after `Factorizator` fix.
|
// TODO Bring back after `Factorizator` fix.
|
||||||
p = Helpers.getByteArray(p)
|
p = Helpers.getByteArray(p);
|
||||||
q = Helpers.getByteArray(q)
|
q = Helpers.getByteArray(q);
|
||||||
|
|
||||||
bytes = Helpers.generateRandomBytes(32)
|
bytes = Helpers.generateRandomBytes(32);
|
||||||
const newNonce = Helpers.readBigIntFromBuffer(bytes, true, true)
|
const newNonce = Helpers.readBigIntFromBuffer(bytes, true, true);
|
||||||
|
|
||||||
const pqInnerData = new constructors.PQInnerData({
|
const pqInnerData = new constructors.PQInnerData({
|
||||||
pq: Helpers.getByteArray(pq), // unsigned
|
pq: Helpers.getByteArray(pq), // unsigned
|
||||||
@ -49,20 +55,20 @@ async function doAuthentication(sender, log) {
|
|||||||
nonce: resPQ.nonce,
|
nonce: resPQ.nonce,
|
||||||
serverNonce: resPQ.serverNonce,
|
serverNonce: resPQ.serverNonce,
|
||||||
newNonce: newNonce,
|
newNonce: newNonce,
|
||||||
})
|
});
|
||||||
|
|
||||||
// sha_digest + data + random_bytes
|
// sha_digest + data + random_bytes
|
||||||
let cipherText = null
|
let cipherText = null;
|
||||||
let targetFingerprint = null
|
let targetFingerprint = null;
|
||||||
for (const fingerprint of resPQ.serverPublicKeyFingerprints) {
|
for (const fingerprint of resPQ.serverPublicKeyFingerprints) {
|
||||||
cipherText = await RSA.encrypt(fingerprint.toString(), pqInnerData.getBytes())
|
cipherText = await RSA.encrypt(fingerprint.toString(), pqInnerData.getBytes());
|
||||||
if (cipherText !== null && cipherText !== undefined) {
|
if (cipherText !== null && cipherText !== undefined) {
|
||||||
targetFingerprint = fingerprint
|
targetFingerprint = fingerprint;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cipherText === null || cipherText === undefined) {
|
if (cipherText === null || cipherText === undefined) {
|
||||||
throw new SecurityError('Step 2 could not find a valid key for fingerprints')
|
throw new SecurityError('Step 2 could not find a valid key for fingerprints');
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverDhParams = await sender.send(
|
const serverDhParams = await sender.send(
|
||||||
@ -74,58 +80,62 @@ async function doAuthentication(sender, log) {
|
|||||||
publicKeyFingerprint: targetFingerprint,
|
publicKeyFingerprint: targetFingerprint,
|
||||||
encryptedData: cipherText,
|
encryptedData: cipherText,
|
||||||
}),
|
}),
|
||||||
)
|
);
|
||||||
if (!(serverDhParams instanceof constructors.ServerDHParamsOk || serverDhParams instanceof constructors.ServerDHParamsFail)) {
|
if (!(serverDhParams instanceof constructors.ServerDHParamsOk || serverDhParams instanceof constructors.ServerDHParamsFail)) {
|
||||||
throw new Error(`Step 2.1 answer was ${serverDhParams}`)
|
throw new Error(`Step 2.1 answer was ${serverDhParams}`);
|
||||||
}
|
}
|
||||||
if (serverDhParams.nonce.neq(resPQ.nonce)) {
|
if (serverDhParams.nonce.neq(resPQ.nonce)) {
|
||||||
throw new SecurityError('Step 2 invalid nonce from server')
|
throw new SecurityError('Step 2 invalid nonce from server');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) {
|
if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) {
|
||||||
throw new SecurityError('Step 2 invalid server nonce from server')
|
throw new SecurityError('Step 2 invalid server nonce from server');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverDhParams instanceof constructors.ServerDHParamsFail) {
|
if (serverDhParams instanceof constructors.ServerDHParamsFail) {
|
||||||
const sh = await Helpers.sha1(Helpers.toSignedLittleBuffer(newNonce, 32).slice(4, 20))
|
const sh = await Helpers.sha1(Helpers.toSignedLittleBuffer(newNonce, 32)
|
||||||
const nnh = Helpers.readBigIntFromBuffer(sh, true, true)
|
.slice(4, 20));
|
||||||
|
const nnh = Helpers.readBigIntFromBuffer(sh, true, true);
|
||||||
if (serverDhParams.newNonceHash.neq(nnh)) {
|
if (serverDhParams.newNonceHash.neq(nnh)) {
|
||||||
throw new SecurityError('Step 2 invalid DH fail nonce from server')
|
throw new SecurityError('Step 2 invalid DH fail nonce from server');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!(serverDhParams instanceof constructors.ServerDHParamsOk)) {
|
if (!(serverDhParams instanceof constructors.ServerDHParamsOk)) {
|
||||||
throw new Error(`Step 2.2 answer was ${serverDhParams}`)
|
throw new Error(`Step 2.2 answer was ${serverDhParams}`);
|
||||||
}
|
}
|
||||||
log.debug('Finished authKey generation step 2')
|
log.debug('Finished authKey generation step 2');
|
||||||
log.debug('Starting authKey generation step 3')
|
log.debug('Starting authKey generation step 3');
|
||||||
|
|
||||||
// Step 3 sending: Complete DH Exchange
|
// Step 3 sending: Complete DH Exchange
|
||||||
const { key, iv } = await Helpers.generateKeyDataFromNonce(resPQ.serverNonce, newNonce)
|
const {
|
||||||
|
key,
|
||||||
|
iv,
|
||||||
|
} = await Helpers.generateKeyDataFromNonce(resPQ.serverNonce, newNonce);
|
||||||
if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
|
if (serverDhParams.encryptedAnswer.length % 16 !== 0) {
|
||||||
// See PR#453
|
// See PR#453
|
||||||
throw new SecurityError('Step 3 AES block size mismatch')
|
throw new SecurityError('Step 3 AES block size mismatch');
|
||||||
}
|
}
|
||||||
const ige = new IGE(key,iv)
|
const ige = new IGE(key, iv);
|
||||||
const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer)
|
const plainTextAnswer = ige.decryptIge(serverDhParams.encryptedAnswer);
|
||||||
const reader = new BinaryReader(plainTextAnswer)
|
const reader = new BinaryReader(plainTextAnswer);
|
||||||
reader.read(20) // hash sum
|
reader.read(20); // hash sum
|
||||||
const serverDhInner = reader.tgReadObject()
|
const serverDhInner = reader.tgReadObject();
|
||||||
if (!(serverDhInner instanceof constructors.ServerDHInnerData)) {
|
if (!(serverDhInner instanceof constructors.ServerDHInnerData)) {
|
||||||
throw new Error(`Step 3 answer was ${serverDhInner}`)
|
throw new Error(`Step 3 answer was ${serverDhInner}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverDhInner.nonce.neq(resPQ.nonce)) {
|
if (serverDhInner.nonce.neq(resPQ.nonce)) {
|
||||||
throw new SecurityError('Step 3 Invalid nonce in encrypted answer')
|
throw new SecurityError('Step 3 Invalid nonce in encrypted answer');
|
||||||
}
|
}
|
||||||
if (serverDhInner.serverNonce.neq(resPQ.serverNonce)) {
|
if (serverDhInner.serverNonce.neq(resPQ.serverNonce)) {
|
||||||
throw new SecurityError('Step 3 Invalid server nonce in encrypted answer')
|
throw new SecurityError('Step 3 Invalid server nonce in encrypted answer');
|
||||||
}
|
}
|
||||||
const dhPrime = Helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false)
|
const dhPrime = Helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false);
|
||||||
const ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false)
|
const ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false);
|
||||||
const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000)
|
const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000);
|
||||||
const b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false)
|
const b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false);
|
||||||
const gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime)
|
const gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime);
|
||||||
const gab = Helpers.modExp(ga, b, dhPrime)
|
const gab = Helpers.modExp(ga, b, dhPrime);
|
||||||
|
|
||||||
// Prepare client DH Inner Data
|
// Prepare client DH Inner Data
|
||||||
const clientDhInner = new constructors.ClientDHInnerData({
|
const clientDhInner = new constructors.ClientDHInnerData({
|
||||||
@ -133,49 +143,52 @@ async function doAuthentication(sender, log) {
|
|||||||
serverNonce: resPQ.serverNonce,
|
serverNonce: resPQ.serverNonce,
|
||||||
retryId: 0, // TODO Actual retry ID
|
retryId: 0, // TODO Actual retry ID
|
||||||
gB: Helpers.getByteArray(gb, false),
|
gB: Helpers.getByteArray(gb, false),
|
||||||
}).getBytes()
|
}).getBytes();
|
||||||
|
|
||||||
const clientDdhInnerHashed = Buffer.concat([await Helpers.sha1(clientDhInner), clientDhInner])
|
const clientDdhInnerHashed = Buffer.concat([await Helpers.sha1(clientDhInner), clientDhInner]);
|
||||||
// Encryption
|
// Encryption
|
||||||
|
|
||||||
const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed)
|
const clientDhEncrypted = ige.encryptIge(clientDdhInnerHashed);
|
||||||
const dhGen = await sender.send(
|
const dhGen = await sender.send(
|
||||||
new requests.SetClientDHParams({
|
new requests.SetClientDHParams({
|
||||||
nonce: resPQ.nonce,
|
nonce: resPQ.nonce,
|
||||||
serverNonce: resPQ.serverNonce,
|
serverNonce: resPQ.serverNonce,
|
||||||
encryptedData: clientDhEncrypted,
|
encryptedData: clientDhEncrypted,
|
||||||
}),
|
}),
|
||||||
)
|
);
|
||||||
const nonceTypes = [constructors.DhGenOk, constructors.DhGenRetry, constructors.DhGenFail]
|
const nonceTypes = [constructors.DhGenOk, constructors.DhGenRetry, constructors.DhGenFail];
|
||||||
if (!(dhGen instanceof nonceTypes[0] || dhGen instanceof nonceTypes[1] || dhGen instanceof nonceTypes[2])) {
|
if (!(dhGen instanceof nonceTypes[0] || dhGen instanceof nonceTypes[1] || dhGen instanceof nonceTypes[2])) {
|
||||||
throw new Error(`Step 3.1 answer was ${dhGen}`)
|
throw new Error(`Step 3.1 answer was ${dhGen}`);
|
||||||
}
|
}
|
||||||
const { name } = dhGen.constructor
|
const { name } = dhGen.constructor;
|
||||||
if (dhGen.nonce.neq(resPQ.nonce)) {
|
if (dhGen.nonce.neq(resPQ.nonce)) {
|
||||||
throw new SecurityError(`Step 3 invalid ${name} nonce from server`)
|
throw new SecurityError(`Step 3 invalid ${name} nonce from server`);
|
||||||
}
|
}
|
||||||
if (dhGen.serverNonce.neq(resPQ.serverNonce)) {
|
if (dhGen.serverNonce.neq(resPQ.serverNonce)) {
|
||||||
throw new SecurityError(`Step 3 invalid ${name} server nonce from server`)
|
throw new SecurityError(`Step 3 invalid ${name} server nonce from server`);
|
||||||
}
|
}
|
||||||
const authKey = new AuthKey()
|
const authKey = new AuthKey();
|
||||||
await authKey.setKey(Helpers.getByteArray(gab))
|
await authKey.setKey(Helpers.getByteArray(gab));
|
||||||
|
|
||||||
const nonceNumber = 1 + nonceTypes.indexOf(dhGen.constructor)
|
const nonceNumber = 1 + nonceTypes.indexOf(dhGen.constructor);
|
||||||
|
|
||||||
const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber)
|
const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber);
|
||||||
const dhHash = dhGen[`newNonceHash${nonceNumber}`]
|
const dhHash = dhGen[`newNonceHash${nonceNumber}`];
|
||||||
|
|
||||||
if (dhHash.neq(newNonceHash)) {
|
if (dhHash.neq(newNonceHash)) {
|
||||||
throw new SecurityError('Step 3 invalid new nonce hash')
|
throw new SecurityError('Step 3 invalid new nonce hash');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(dhGen instanceof constructors.DhGenOk)) {
|
if (!(dhGen instanceof constructors.DhGenOk)) {
|
||||||
throw new Error(`Step 3.2 answer was ${dhGen}`)
|
throw new Error(`Step 3.2 answer was ${dhGen}`);
|
||||||
}
|
}
|
||||||
log.debug('Finished authKey generation step 3')
|
log.debug('Finished authKey generation step 3');
|
||||||
|
|
||||||
return { authKey, timeOffset }
|
return {
|
||||||
|
authKey,
|
||||||
|
timeOffset,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = doAuthentication
|
module.exports = doAuthentication;
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
* This module contains the class used to communicate with Telegram's servers
|
* This module contains the class used to communicate with Telegram's servers
|
||||||
* in plain text, when no authorization key has been created yet.
|
* in plain text, when no authorization key has been created yet.
|
||||||
*/
|
*/
|
||||||
const Helpers = require('../Helpers')
|
const Helpers = require('../Helpers');
|
||||||
const MTProtoState = require('./MTProtoState')
|
const MTProtoState = require('./MTProtoState');
|
||||||
const BinaryReader = require('../extensions/BinaryReader')
|
const BinaryReader = require('../extensions/BinaryReader');
|
||||||
const { InvalidBufferError } = require('../errors/Common')
|
const { InvalidBufferError } = require('../errors/Common');
|
||||||
const BigInt = require('big-integer')
|
const BigInt = require('big-integer');
|
||||||
const { toSignedLittleBuffer } = require("../Helpers")
|
const { toSignedLittleBuffer } = require('../Helpers');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)
|
* MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)
|
||||||
@ -20,8 +20,8 @@ class MTProtoPlainSender {
|
|||||||
* @param loggers
|
* @param loggers
|
||||||
*/
|
*/
|
||||||
constructor(connection, loggers) {
|
constructor(connection, loggers) {
|
||||||
this._state = new MTProtoState(connection, loggers)
|
this._state = new MTProtoState(connection, loggers);
|
||||||
this._connection = connection
|
this._connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,27 +30,27 @@ class MTProtoPlainSender {
|
|||||||
*/
|
*/
|
||||||
async send(request) {
|
async send(request) {
|
||||||
|
|
||||||
let body = request.getBytes()
|
let body = request.getBytes();
|
||||||
let msgId = this._state._getNewMsgId()
|
let msgId = this._state._getNewMsgId();
|
||||||
const m = toSignedLittleBuffer(msgId, 8)
|
const m = toSignedLittleBuffer(msgId, 8);
|
||||||
const b = Buffer.alloc(4)
|
const b = Buffer.alloc(4);
|
||||||
b.writeInt32LE(body.length, 0)
|
b.writeInt32LE(body.length, 0);
|
||||||
|
|
||||||
const res = Buffer.concat([Buffer.concat([Buffer.alloc(8), m, b]), body])
|
const res = Buffer.concat([Buffer.concat([Buffer.alloc(8), m, b]), body]);
|
||||||
|
|
||||||
await this._connection.send(res)
|
await this._connection.send(res);
|
||||||
body = await this._connection.recv()
|
body = await this._connection.recv();
|
||||||
if (body.length < 8) {
|
if (body.length < 8) {
|
||||||
throw new InvalidBufferError(body)
|
throw new InvalidBufferError(body);
|
||||||
}
|
}
|
||||||
const reader = new BinaryReader(body)
|
const reader = new BinaryReader(body);
|
||||||
const authKeyId = reader.readLong()
|
const authKeyId = reader.readLong();
|
||||||
if (authKeyId.neq(BigInt(0))) {
|
if (authKeyId.neq(BigInt(0))) {
|
||||||
throw new Error('Bad authKeyId')
|
throw new Error('Bad authKeyId');
|
||||||
}
|
}
|
||||||
msgId = reader.readLong()
|
msgId = reader.readLong();
|
||||||
if (msgId.eq(BigInt(0))) {
|
if (msgId.eq(BigInt(0))) {
|
||||||
throw new Error('Bad msgId')
|
throw new Error('Bad msgId');
|
||||||
}
|
}
|
||||||
/** ^ We should make sure that the read ``msg_id`` is greater
|
/** ^ We should make sure that the read ``msg_id`` is greater
|
||||||
* than our own ``msg_id``. However, under some circumstances
|
* than our own ``msg_id``. However, under some circumstances
|
||||||
@ -58,18 +58,18 @@ class MTProtoPlainSender {
|
|||||||
* be the case, which would cause endless assertion errors.
|
* be the case, which would cause endless assertion errors.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const length = reader.readInt()
|
const length = reader.readInt();
|
||||||
if (length <= 0) {
|
if (length <= 0) {
|
||||||
throw new Error('Bad length')
|
throw new Error('Bad length');
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* We could read length bytes and use those in a new reader to read
|
* We could read length bytes and use those in a new reader to read
|
||||||
* the next TLObject without including the padding, but since the
|
* the next TLObject without including the padding, but since the
|
||||||
* reader isn't used for anything else after this, it's unnecessary.
|
* reader isn't used for anything else after this, it's unnecessary.
|
||||||
*/
|
*/
|
||||||
return reader.tgReadObject()
|
return reader.tgReadObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MTProtoPlainSender
|
module.exports = MTProtoPlainSender;
|
||||||
|
|||||||
@ -1,17 +1,22 @@
|
|||||||
const MtProtoPlainSender = require('./MTProtoPlainSender')
|
const MtProtoPlainSender = require('./MTProtoPlainSender');
|
||||||
const MTProtoState = require('./MTProtoState')
|
const MTProtoState = require('./MTProtoState');
|
||||||
const Helpers = require('../Helpers')
|
const Helpers = require('../Helpers');
|
||||||
const AuthKey = require('../crypto/AuthKey')
|
const AuthKey = require('../crypto/AuthKey');
|
||||||
const doAuthentication = require('./Authenticator')
|
const doAuthentication = require('./Authenticator');
|
||||||
const RPCResult = require('../tl/core/RPCResult')
|
const RPCResult = require('../tl/core/RPCResult');
|
||||||
const MessageContainer = require('../tl/core/MessageContainer')
|
const MessageContainer = require('../tl/core/MessageContainer');
|
||||||
const GZIPPacked = require('../tl/core/GZIPPacked')
|
const GZIPPacked = require('../tl/core/GZIPPacked');
|
||||||
const RequestState = require('./RequestState')
|
const RequestState = require('./RequestState');
|
||||||
const { MsgsAck, upload, MsgsStateInfo, Pong } = require('../tl').constructors
|
const {
|
||||||
const MessagePacker = require('../extensions/MessagePacker')
|
MsgsAck,
|
||||||
const BinaryReader = require('../extensions/BinaryReader')
|
upload,
|
||||||
const { UpdateConnectionState } = require("./index");
|
MsgsStateInfo,
|
||||||
const { BadMessageError } = require("../errors/Common")
|
Pong,
|
||||||
|
} = require('../tl').constructors;
|
||||||
|
const MessagePacker = require('../extensions/MessagePacker');
|
||||||
|
const BinaryReader = require('../extensions/BinaryReader');
|
||||||
|
const { UpdateConnectionState } = require('./index');
|
||||||
|
const { BadMessageError } = require('../errors/Common');
|
||||||
const {
|
const {
|
||||||
BadServerSalt,
|
BadServerSalt,
|
||||||
BadMsgNotification,
|
BadMsgNotification,
|
||||||
@ -22,12 +27,12 @@ const {
|
|||||||
MsgsStateReq,
|
MsgsStateReq,
|
||||||
MsgResendReq,
|
MsgResendReq,
|
||||||
MsgsAllInfo,
|
MsgsAllInfo,
|
||||||
} = require('../tl').constructors
|
} = require('../tl').constructors;
|
||||||
const { SecurityError } = require('../errors/Common')
|
const { SecurityError } = require('../errors/Common');
|
||||||
const { InvalidBufferError } = require('../errors/Common')
|
const { InvalidBufferError } = require('../errors/Common');
|
||||||
const { LogOut } = require('../tl').requests.auth
|
const { LogOut } = require('../tl').requests.auth;
|
||||||
const { RPCMessageToError } = require('../errors')
|
const { RPCMessageToError } = require('../errors');
|
||||||
const { TypeNotFoundError } = require('../errors/Common')
|
const { TypeNotFoundError } = require('../errors/Common');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,24 +60,24 @@ class MTProtoSender {
|
|||||||
autoReconnectCallback: null,
|
autoReconnectCallback: null,
|
||||||
isMainSender: null,
|
isMainSender: null,
|
||||||
onConnectionBreak: null,
|
onConnectionBreak: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param authKey
|
* @param authKey
|
||||||
* @param opts
|
* @param opts
|
||||||
*/
|
*/
|
||||||
constructor(authKey, opts) {
|
constructor(authKey, opts) {
|
||||||
const args = { ...MTProtoSender.DEFAULT_OPTIONS, ...opts }
|
const args = { ...MTProtoSender.DEFAULT_OPTIONS, ...opts };
|
||||||
this._connection = null
|
this._connection = null;
|
||||||
this._log = args.logger
|
this._log = args.logger;
|
||||||
this._dcId = args.dcId
|
this._dcId = args.dcId;
|
||||||
this._retries = args.retries
|
this._retries = args.retries;
|
||||||
this._delay = args.delay
|
this._delay = args.delay;
|
||||||
this._autoReconnect = args.autoReconnect
|
this._autoReconnect = args.autoReconnect;
|
||||||
this._connectTimeout = args.connectTimeout
|
this._connectTimeout = args.connectTimeout;
|
||||||
this._authKeyCallback = args.authKeyCallback
|
this._authKeyCallback = args.authKeyCallback;
|
||||||
this._updateCallback = args.updateCallback
|
this._updateCallback = args.updateCallback;
|
||||||
this._autoReconnectCallback = args.autoReconnectCallback
|
this._autoReconnectCallback = args.autoReconnectCallback;
|
||||||
this._isMainSender = args.isMainSender;
|
this._isMainSender = args.isMainSender;
|
||||||
this._onConnectionBreak = args.onConnectionBreak;
|
this._onConnectionBreak = args.onConnectionBreak;
|
||||||
|
|
||||||
@ -84,44 +89,44 @@ class MTProtoSender {
|
|||||||
* be cleared but on explicit user disconnection all the
|
* be cleared but on explicit user disconnection all the
|
||||||
* pending futures should be cancelled.
|
* pending futures should be cancelled.
|
||||||
*/
|
*/
|
||||||
this._user_connected = false
|
this._user_connected = false;
|
||||||
this._reconnecting = false
|
this._reconnecting = false;
|
||||||
this._disconnected = true
|
this._disconnected = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We need to join the loops upon disconnection
|
* We need to join the loops upon disconnection
|
||||||
*/
|
*/
|
||||||
this._send_loop_handle = null
|
this._send_loop_handle = null;
|
||||||
this._recv_loop_handle = null
|
this._recv_loop_handle = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preserving the references of the AuthKey and state is important
|
* Preserving the references of the AuthKey and state is important
|
||||||
*/
|
*/
|
||||||
this.authKey = authKey || new AuthKey()
|
this.authKey = authKey || new AuthKey();
|
||||||
this._state = new MTProtoState(this.authKey, this._log)
|
this._state = new MTProtoState(this.authKey, this._log);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outgoing messages are put in a queue and sent in a batch.
|
* Outgoing messages are put in a queue and sent in a batch.
|
||||||
* Note that here we're also storing their ``_RequestState``.
|
* Note that here we're also storing their ``_RequestState``.
|
||||||
*/
|
*/
|
||||||
this._send_queue = new MessagePacker(this._state, this._log)
|
this._send_queue = new MessagePacker(this._state, this._log);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent states are remembered until a response is received.
|
* Sent states are remembered until a response is received.
|
||||||
*/
|
*/
|
||||||
this._pending_state = {}
|
this._pending_state = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responses must be acknowledged, and we can also batch these.
|
* Responses must be acknowledged, and we can also batch these.
|
||||||
*/
|
*/
|
||||||
this._pending_ack = new Set()
|
this._pending_ack = new Set();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to pending_messages but only for the last acknowledges.
|
* Similar to pending_messages but only for the last acknowledges.
|
||||||
* These can't go in pending_messages because no acknowledge for them
|
* These can't go in pending_messages because no acknowledge for them
|
||||||
* is received, but we may still need to resend their state on bad salts.
|
* is received, but we may still need to resend their state on bad salts.
|
||||||
*/
|
*/
|
||||||
this._last_acks = []
|
this._last_acks = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jump table from response ID to method that handles it
|
* Jump table from response ID to method that handles it
|
||||||
@ -142,7 +147,7 @@ class MTProtoSender {
|
|||||||
[MsgsStateReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this),
|
[MsgsStateReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this),
|
||||||
[MsgResendReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this),
|
[MsgResendReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this),
|
||||||
[MsgsAllInfo.CONSTRUCTOR_ID]: this._handleMsgAll.bind(this),
|
[MsgsAllInfo.CONSTRUCTOR_ID]: this._handleMsgAll.bind(this),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public API
|
// Public API
|
||||||
@ -155,30 +160,30 @@ class MTProtoSender {
|
|||||||
*/
|
*/
|
||||||
async connect(connection, force) {
|
async connect(connection, force) {
|
||||||
if (this._user_connected && !force) {
|
if (this._user_connected && !force) {
|
||||||
this._log.info('User is already connected!')
|
this._log.info('User is already connected!');
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
this._connection = connection
|
this._connection = connection;
|
||||||
|
|
||||||
for (let attempt = 0; attempt < this._retries; attempt++) {
|
for (let attempt = 0; attempt < this._retries; attempt++) {
|
||||||
try {
|
try {
|
||||||
await this._connect()
|
await this._connect();
|
||||||
if (this._updateCallback) {
|
if (this._updateCallback) {
|
||||||
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.connected))
|
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.connected));
|
||||||
}
|
}
|
||||||
break
|
break;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (this._updateCallback && attempt===0){
|
if (this._updateCallback && attempt === 0) {
|
||||||
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected))
|
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected));
|
||||||
}
|
}
|
||||||
this._log.error('WebSocket connection failed attempt: ' + (attempt + 1))
|
this._log.error('WebSocket connection failed attempt: ' + (attempt + 1));
|
||||||
await Helpers.sleep(this._delay)
|
await Helpers.sleep(this._delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isConnected() {
|
isConnected() {
|
||||||
return this._user_connected
|
return this._user_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,7 +191,7 @@ class MTProtoSender {
|
|||||||
* all pending requests, and closes the send and receive loops.
|
* all pending requests, and closes the send and receive loops.
|
||||||
*/
|
*/
|
||||||
async disconnect() {
|
async disconnect() {
|
||||||
await this._disconnect()
|
await this._disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,12 +221,12 @@ class MTProtoSender {
|
|||||||
*/
|
*/
|
||||||
send(request) {
|
send(request) {
|
||||||
if (!this._user_connected) {
|
if (!this._user_connected) {
|
||||||
throw new Error('Cannot send requests while disconnected')
|
throw new Error('Cannot send requests while disconnected');
|
||||||
}
|
}
|
||||||
//CONTEST
|
//CONTEST
|
||||||
const state = new RequestState(request)
|
const state = new RequestState(request);
|
||||||
this._send_queue.append(state)
|
this._send_queue.append(state);
|
||||||
return state.promise
|
return state.promise;
|
||||||
/*
|
/*
|
||||||
if (!Helpers.isArrayLike(request)) {
|
if (!Helpers.isArrayLike(request)) {
|
||||||
const state = new RequestState(request)
|
const state = new RequestState(request)
|
||||||
@ -240,18 +245,18 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _connect() {
|
async _connect() {
|
||||||
this._log.info('Connecting to {0}...'.replace('{0}', this._connection))
|
this._log.info('Connecting to {0}...'.replace('{0}', this._connection));
|
||||||
await this._connection.connect()
|
await this._connection.connect();
|
||||||
this._log.debug('Connection success!')
|
this._log.debug('Connection success!');
|
||||||
//process.exit(0)
|
//process.exit(0)
|
||||||
if (!this.authKey.getKey()) {
|
if (!this.authKey.getKey()) {
|
||||||
const plain = new MtProtoPlainSender(this._connection, this._log)
|
const plain = new MtProtoPlainSender(this._connection, this._log);
|
||||||
this._log.debug('New auth_key attempt ...')
|
this._log.debug('New auth_key attempt ...');
|
||||||
const res = await doAuthentication(plain, this._log)
|
const res = await doAuthentication(plain, this._log);
|
||||||
this._log.debug('Generated new auth_key successfully')
|
this._log.debug('Generated new auth_key successfully');
|
||||||
await this.authKey.setKey(res.authKey)
|
await this.authKey.setKey(res.authKey);
|
||||||
|
|
||||||
this._state.time_offset = res.timeOffset
|
this._state.time_offset = res.timeOffset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is *EXTREMELY* important since we don't control
|
* This is *EXTREMELY* important since we don't control
|
||||||
@ -260,41 +265,41 @@ class MTProtoSender {
|
|||||||
* switch to different data centers.
|
* switch to different data centers.
|
||||||
*/
|
*/
|
||||||
if (this._authKeyCallback) {
|
if (this._authKeyCallback) {
|
||||||
await this._authKeyCallback(this.authKey, this._dcId)
|
await this._authKeyCallback(this.authKey, this._dcId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._log.debug('Already have an auth key ...')
|
this._log.debug('Already have an auth key ...');
|
||||||
}
|
}
|
||||||
this._user_connected = true
|
this._user_connected = true;
|
||||||
this._reconnecting = false
|
this._reconnecting = false;
|
||||||
|
|
||||||
this._log.debug('Starting send loop')
|
this._log.debug('Starting send loop');
|
||||||
this._send_loop_handle = this._sendLoop()
|
this._send_loop_handle = this._sendLoop();
|
||||||
|
|
||||||
this._log.debug('Starting receive loop')
|
this._log.debug('Starting receive loop');
|
||||||
this._recv_loop_handle = this._recvLoop()
|
this._recv_loop_handle = this._recvLoop();
|
||||||
|
|
||||||
// _disconnected only completes after manual disconnection
|
// _disconnected only completes after manual disconnection
|
||||||
// or errors after which the sender cannot continue such
|
// or errors after which the sender cannot continue such
|
||||||
// as failing to reconnect or any unexpected error.
|
// as failing to reconnect or any unexpected error.
|
||||||
|
|
||||||
this._log.info('Connection to %s complete!'.replace('%s', this._connection.toString()))
|
this._log.info('Connection to %s complete!'.replace('%s', this._connection.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
async _disconnect(error = null) {
|
async _disconnect(error = null) {
|
||||||
this._send_queue.rejectAll()
|
this._send_queue.rejectAll();
|
||||||
|
|
||||||
if (this._connection === null) {
|
if (this._connection === null) {
|
||||||
this._log.info('Not disconnecting (already have no connection)')
|
this._log.info('Not disconnecting (already have no connection)');
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (this._updateCallback){
|
if (this._updateCallback) {
|
||||||
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected))
|
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.disconnected));
|
||||||
}
|
}
|
||||||
this._log.info('Disconnecting from %s...'.replace('%s', this._connection.toString()))
|
this._log.info('Disconnecting from %s...'.replace('%s', this._connection.toString()));
|
||||||
this._user_connected = false
|
this._user_connected = false;
|
||||||
this._log.debug('Closing current connection...')
|
this._log.debug('Closing current connection...');
|
||||||
await this._connection.disconnect()
|
await this._connection.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -305,92 +310,92 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _sendLoop() {
|
async _sendLoop() {
|
||||||
this._send_queue = new MessagePacker(this._state, this._log)
|
this._send_queue = new MessagePacker(this._state, this._log);
|
||||||
|
|
||||||
while (this._user_connected && !this._reconnecting) {
|
while (this._user_connected && !this._reconnecting) {
|
||||||
if (this._pending_ack.size) {
|
if (this._pending_ack.size) {
|
||||||
const ack = new RequestState(new MsgsAck({ msgIds: Array(...this._pending_ack) }))
|
const ack = new RequestState(new MsgsAck({ msgIds: Array(...this._pending_ack) }));
|
||||||
this._send_queue.append(ack)
|
this._send_queue.append(ack);
|
||||||
this._last_acks.push(ack)
|
this._last_acks.push(ack);
|
||||||
this._pending_ack.clear()
|
this._pending_ack.clear();
|
||||||
}
|
}
|
||||||
this._log.debug('Waiting for messages to send...'+this._reconnecting)
|
this._log.debug('Waiting for messages to send...' + this._reconnecting);
|
||||||
// TODO Wait for the connection send queue to be empty?
|
// TODO Wait for the connection send queue to be empty?
|
||||||
// This means that while it's not empty we can wait for
|
// This means that while it's not empty we can wait for
|
||||||
// more messages to be added to the send queue.
|
// more messages to be added to the send queue.
|
||||||
const res = await this._send_queue.get()
|
const res = await this._send_queue.get();
|
||||||
|
|
||||||
if (this._reconnecting) {
|
if (this._reconnecting) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res) {
|
if (!res) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
let data = res.data
|
let data = res.data;
|
||||||
const batch = res.batch
|
const batch = res.batch;
|
||||||
this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`)
|
this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`);
|
||||||
|
|
||||||
data = await this._state.encryptMessageData(data)
|
data = await this._state.encryptMessageData(data);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this._connection.send(data)
|
await this._connection.send(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._log.error(e)
|
this._log.error(e);
|
||||||
this._log.info('Connection closed while sending data')
|
this._log.info('Connection closed while sending data');
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
for (const state of batch) {
|
for (const state of batch) {
|
||||||
if (!Array.isArray(state)) {
|
if (!Array.isArray(state)) {
|
||||||
if (state.request.classType === 'request') {
|
if (state.request.classType === 'request') {
|
||||||
this._pending_state[state.msgId] = state
|
this._pending_state[state.msgId] = state;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const s of state) {
|
for (const s of state) {
|
||||||
if (s.request.classType === 'request') {
|
if (s.request.classType === 'request') {
|
||||||
this._pending_state[s.msgId] = s
|
this._pending_state[s.msgId] = s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._log.debug('Encrypted messages put in a queue to be sent')
|
this._log.debug('Encrypted messages put in a queue to be sent');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _recvLoop() {
|
async _recvLoop() {
|
||||||
let body
|
let body;
|
||||||
let message
|
let message;
|
||||||
|
|
||||||
while (this._user_connected && !this._reconnecting) {
|
while (this._user_connected && !this._reconnecting) {
|
||||||
// this._log.debug('Receiving items from the network...');
|
// this._log.debug('Receiving items from the network...');
|
||||||
this._log.debug('Receiving items from the network...')
|
this._log.debug('Receiving items from the network...');
|
||||||
try {
|
try {
|
||||||
body = await this._connection.recv()
|
body = await this._connection.recv();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// this._log.info('Connection closed while receiving data');
|
// this._log.info('Connection closed while receiving data');
|
||||||
this._log.warn('Connection closed while receiving data')
|
this._log.warn('Connection closed while receiving data');
|
||||||
this.reconnect()
|
this.reconnect();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
message = await this._state.decryptMessageData(body)
|
message = await this._state.decryptMessageData(body);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TypeNotFoundError) {
|
if (e instanceof TypeNotFoundError) {
|
||||||
// Received object which we don't know how to deserialize
|
// Received object which we don't know how to deserialize
|
||||||
this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`)
|
this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`);
|
||||||
continue
|
continue;
|
||||||
} else if (e instanceof SecurityError) {
|
} else if (e instanceof SecurityError) {
|
||||||
// A step while decoding had the incorrect data. This message
|
// A step while decoding had the incorrect data. This message
|
||||||
// should not be considered safe and it should be ignored.
|
// should not be considered safe and it should be ignored.
|
||||||
this._log.warn(`Security error while unpacking a received message: ${e}`)
|
this._log.warn(`Security error while unpacking a received message: ${e}`);
|
||||||
continue
|
continue;
|
||||||
} else if (e instanceof InvalidBufferError) {
|
} else if (e instanceof InvalidBufferError) {
|
||||||
this._log.info('Broken authorization key; resetting')
|
this._log.info('Broken authorization key; resetting');
|
||||||
if (this._updateCallback && this._isMainSender){
|
if (this._updateCallback && this._isMainSender) {
|
||||||
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.broken))
|
this._updateCallback(new UpdateConnectionState(UpdateConnectionState.broken));
|
||||||
} else if (this._onConnectionBreak && !this._isMainSender){
|
} else if (this._onConnectionBreak && !this._isMainSender) {
|
||||||
// Deletes the current sender from the object
|
// Deletes the current sender from the object
|
||||||
this._onConnectionBreak(this._dcId)
|
this._onConnectionBreak(this._dcId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't really need to do this if we're going to sign in again
|
// We don't really need to do this if we're going to sign in again
|
||||||
@ -402,19 +407,19 @@ class MTProtoSender {
|
|||||||
// We can disconnect at sign in
|
// We can disconnect at sign in
|
||||||
/* await this.disconnect()
|
/* await this.disconnect()
|
||||||
*/
|
*/
|
||||||
return
|
return;
|
||||||
} else {
|
} else {
|
||||||
this._log.error('Unhandled error while receiving data')
|
this._log.error('Unhandled error while receiving data');
|
||||||
this._log.error(e)
|
this._log.error(e);
|
||||||
this.reconnect()
|
this.reconnect();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this._processMessage(message)
|
await this._processMessage(message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._log.error('Unhandled error while receiving data')
|
this._log.error('Unhandled error while receiving data');
|
||||||
this._log.error(e)
|
this._log.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -430,15 +435,15 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _processMessage(message) {
|
async _processMessage(message) {
|
||||||
this._pending_ack.add(message.msgId)
|
this._pending_ack.add(message.msgId);
|
||||||
// eslint-disable-next-line require-atomic-updates
|
// eslint-disable-next-line require-atomic-updates
|
||||||
message.obj = await message.obj
|
message.obj = await message.obj;
|
||||||
let handler = this._handlers[message.obj.CONSTRUCTOR_ID]
|
let handler = this._handlers[message.obj.CONSTRUCTOR_ID];
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
handler = this._handleUpdate.bind(this)
|
handler = this._handleUpdate.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
await handler(message)
|
await handler(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -449,36 +454,36 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_popStates(msgId) {
|
_popStates(msgId) {
|
||||||
let state = this._pending_state[msgId]
|
let state = this._pending_state[msgId];
|
||||||
if (state) {
|
if (state) {
|
||||||
delete this._pending_state[msgId]
|
delete this._pending_state[msgId];
|
||||||
return [state]
|
return [state];
|
||||||
}
|
}
|
||||||
|
|
||||||
const toPop = []
|
const toPop = [];
|
||||||
|
|
||||||
for (state of Object.values(this._pending_state)) {
|
for (state of Object.values(this._pending_state)) {
|
||||||
if (state.containerId && state.containerId.equals(msgId)) {
|
if (state.containerId && state.containerId.equals(msgId)) {
|
||||||
toPop.push(state.msgId)
|
toPop.push(state.msgId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toPop.length) {
|
if (toPop.length) {
|
||||||
const temp = []
|
const temp = [];
|
||||||
for (const x of toPop) {
|
for (const x of toPop) {
|
||||||
temp.push(this._pending_state[x])
|
temp.push(this._pending_state[x]);
|
||||||
delete this._pending_state[x]
|
delete this._pending_state[x];
|
||||||
}
|
}
|
||||||
return temp
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const ack of this._last_acks) {
|
for (const ack of this._last_acks) {
|
||||||
if (ack.msgId === msgId) {
|
if (ack.msgId === msgId) {
|
||||||
return [ack]
|
return [ack];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return []
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -490,12 +495,12 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_handleRPCResult(message) {
|
_handleRPCResult(message) {
|
||||||
const RPCResult = message.obj
|
const RPCResult = message.obj;
|
||||||
const state = this._pending_state[RPCResult.reqMsgId]
|
const state = this._pending_state[RPCResult.reqMsgId];
|
||||||
if (state) {
|
if (state) {
|
||||||
delete this._pending_state[RPCResult.reqMsgId]
|
delete this._pending_state[RPCResult.reqMsgId];
|
||||||
}
|
}
|
||||||
this._log.debug(`Handling RPC result for message ${RPCResult.reqMsgId}`)
|
this._log.debug(`Handling RPC result for message ${RPCResult.reqMsgId}`);
|
||||||
|
|
||||||
if (!state) {
|
if (!state) {
|
||||||
// TODO We should not get responses to things we never sent
|
// TODO We should not get responses to things we never sent
|
||||||
@ -503,29 +508,29 @@ class MTProtoSender {
|
|||||||
// See #658, #759 and #958. They seem to happen in a container
|
// See #658, #759 and #958. They seem to happen in a container
|
||||||
// which contain the real response right after.
|
// which contain the real response right after.
|
||||||
try {
|
try {
|
||||||
const reader = new BinaryReader(RPCResult.body)
|
const reader = new BinaryReader(RPCResult.body);
|
||||||
if (!(reader.tgReadObject() instanceof upload.File)) {
|
if (!(reader.tgReadObject() instanceof upload.File)) {
|
||||||
throw new TypeNotFoundError('Not an upload.File')
|
throw new TypeNotFoundError('Not an upload.File');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._log.error(e)
|
this._log.error(e);
|
||||||
if (e instanceof TypeNotFoundError) {
|
if (e instanceof TypeNotFoundError) {
|
||||||
this._log.info(`Received response without parent request: ${RPCResult.body}`)
|
this._log.info(`Received response without parent request: ${RPCResult.body}`);
|
||||||
return
|
return;
|
||||||
} else {
|
} else {
|
||||||
throw e
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (RPCResult.error) {
|
if (RPCResult.error) {
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
const error = RPCMessageToError(RPCResult.error, state.request)
|
const error = RPCMessageToError(RPCResult.error, state.request);
|
||||||
this._send_queue.append(new RequestState(new MsgsAck({ msgIds: [state.msgId] })))
|
this._send_queue.append(new RequestState(new MsgsAck({ msgIds: [state.msgId] })));
|
||||||
state.reject(error)
|
state.reject(error);
|
||||||
} else {
|
} else {
|
||||||
const reader = new BinaryReader(RPCResult.body)
|
const reader = new BinaryReader(RPCResult.body);
|
||||||
const read = state.request.readResult(reader)
|
const read = state.request.readResult(reader);
|
||||||
state.resolve(read)
|
state.resolve(read);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,9 +542,9 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _handleContainer(message) {
|
async _handleContainer(message) {
|
||||||
this._log.debug('Handling container')
|
this._log.debug('Handling container');
|
||||||
for (const innerMessage of message.obj.messages) {
|
for (const innerMessage of message.obj.messages) {
|
||||||
await this._processMessage(innerMessage)
|
await this._processMessage(innerMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,21 +556,21 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _handleGzipPacked(message) {
|
async _handleGzipPacked(message) {
|
||||||
this._log.debug('Handling gzipped data')
|
this._log.debug('Handling gzipped data');
|
||||||
const reader = new BinaryReader(message.obj.data)
|
const reader = new BinaryReader(message.obj.data);
|
||||||
message.obj = reader.tgReadObject()
|
message.obj = reader.tgReadObject();
|
||||||
await this._processMessage(message)
|
await this._processMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _handleUpdate(message) {
|
async _handleUpdate(message) {
|
||||||
if (message.obj.SUBCLASS_OF_ID !== 0x8af52aac) {
|
if (message.obj.SUBCLASS_OF_ID !== 0x8af52aac) {
|
||||||
// crc32(b'Updates')
|
// crc32(b'Updates')
|
||||||
this._log.warn(`Note: ${message.obj.className} is not an update, not dispatching it`)
|
this._log.warn(`Note: ${message.obj.className} is not an update, not dispatching it`);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
this._log.debug('Handling update ' + message.obj.className)
|
this._log.debug('Handling update ' + message.obj.className);
|
||||||
if (this._updateCallback) {
|
if (this._updateCallback) {
|
||||||
this._updateCallback(message.obj)
|
this._updateCallback(message.obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,14 +583,14 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _handlePong(message) {
|
async _handlePong(message) {
|
||||||
const pong = message.obj
|
const pong = message.obj;
|
||||||
this._log.debug(`Handling pong for message ${pong.msgId}`)
|
this._log.debug(`Handling pong for message ${pong.msgId}`);
|
||||||
const state = this._pending_state[pong.msgId]
|
const state = this._pending_state[pong.msgId];
|
||||||
delete this._pending_state[pong.msgId]
|
delete this._pending_state[pong.msgId];
|
||||||
|
|
||||||
// Todo Check result
|
// Todo Check result
|
||||||
if (state) {
|
if (state) {
|
||||||
state.resolve(pong)
|
state.resolve(pong);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,12 +604,12 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _handleBadServerSalt(message) {
|
async _handleBadServerSalt(message) {
|
||||||
const badSalt = message.obj
|
const badSalt = message.obj;
|
||||||
this._log.debug(`Handling bad salt for message ${badSalt.badMsgId}`)
|
this._log.debug(`Handling bad salt for message ${badSalt.badMsgId}`);
|
||||||
this._state.salt = badSalt.newServerSalt
|
this._state.salt = badSalt.newServerSalt;
|
||||||
const states = this._popStates(badSalt.badMsgId)
|
const states = this._popStates(badSalt.badMsgId);
|
||||||
this._send_queue.extend(states)
|
this._send_queue.extend(states);
|
||||||
this._log.debug(`${states.length} message(s) will be resent`)
|
this._log.debug(`${states.length} message(s) will be resent`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -617,32 +622,32 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _handleBadNotification(message) {
|
async _handleBadNotification(message) {
|
||||||
const badMsg = message.obj
|
const badMsg = message.obj;
|
||||||
const states = this._popStates(badMsg.badMsgId)
|
const states = this._popStates(badMsg.badMsgId);
|
||||||
this._log.debug(`Handling bad msg ${JSON.stringify(badMsg)}`)
|
this._log.debug(`Handling bad msg ${JSON.stringify(badMsg)}`);
|
||||||
if ([16, 17].includes(badMsg.errorCode)) {
|
if ([16, 17].includes(badMsg.errorCode)) {
|
||||||
// Sent msg_id too low or too high (respectively).
|
// Sent msg_id too low or too high (respectively).
|
||||||
// Use the current msg_id to determine the right time offset.
|
// Use the current msg_id to determine the right time offset.
|
||||||
const to = this._state.updateTimeOffset(message.msgId)
|
const to = this._state.updateTimeOffset(message.msgId);
|
||||||
this._log.info(`System clock is wrong, set time offset to ${to}s`)
|
this._log.info(`System clock is wrong, set time offset to ${to}s`);
|
||||||
} else if (badMsg.errorCode === 32) {
|
} else if (badMsg.errorCode === 32) {
|
||||||
// msg_seqno too low, so just pump it up by some "large" amount
|
// msg_seqno too low, so just pump it up by some "large" amount
|
||||||
// TODO A better fix would be to start with a new fresh session ID
|
// TODO A better fix would be to start with a new fresh session ID
|
||||||
this._state._sequence += 64
|
this._state._sequence += 64;
|
||||||
} else if (badMsg.errorCode === 33) {
|
} else if (badMsg.errorCode === 33) {
|
||||||
// msg_seqno too high never seems to happen but just in case
|
// msg_seqno too high never seems to happen but just in case
|
||||||
this._state._sequence -= 16
|
this._state._sequence -= 16;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
state.reject(new BadMessageError(state.request, badMsg.errorCode))
|
state.reject(new BadMessageError(state.request, badMsg.errorCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
// Messages are to be re-sent once we've corrected the issue
|
// Messages are to be re-sent once we've corrected the issue
|
||||||
this._send_queue.extend(states)
|
this._send_queue.extend(states);
|
||||||
this._log.debug(`${states.length} messages will be resent due to bad msg`)
|
this._log.debug(`${states.length} messages will be resent due to bad msg`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -655,9 +660,9 @@ class MTProtoSender {
|
|||||||
*/
|
*/
|
||||||
async _handleDetailedInfo(message) {
|
async _handleDetailedInfo(message) {
|
||||||
// TODO https://goo.gl/VvpCC6
|
// TODO https://goo.gl/VvpCC6
|
||||||
const msgId = message.obj.answerMsgId
|
const msgId = message.obj.answerMsgId;
|
||||||
this._log.debug(`Handling detailed info for message ${msgId}`)
|
this._log.debug(`Handling detailed info for message ${msgId}`);
|
||||||
this._pending_ack.add(msgId)
|
this._pending_ack.add(msgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -670,9 +675,9 @@ class MTProtoSender {
|
|||||||
*/
|
*/
|
||||||
async _handleNewDetailedInfo(message) {
|
async _handleNewDetailedInfo(message) {
|
||||||
// TODO https://goo.gl/VvpCC6
|
// TODO https://goo.gl/VvpCC6
|
||||||
const msgId = message.obj.answerMsgId
|
const msgId = message.obj.answerMsgId;
|
||||||
this._log.debug(`Handling new detailed info for message ${msgId}`)
|
this._log.debug(`Handling new detailed info for message ${msgId}`);
|
||||||
this._pending_ack.add(msgId)
|
this._pending_ack.add(msgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -685,8 +690,8 @@ class MTProtoSender {
|
|||||||
*/
|
*/
|
||||||
async _handleNewSessionCreated(message) {
|
async _handleNewSessionCreated(message) {
|
||||||
// TODO https://goo.gl/LMyN7A
|
// TODO https://goo.gl/LMyN7A
|
||||||
this._log.debug('Handling new session created')
|
this._log.debug('Handling new session created');
|
||||||
this._state.salt = message.obj.serverSalt
|
this._state.salt = message.obj.serverSalt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -708,13 +713,13 @@ class MTProtoSender {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _handleAck(message) {
|
async _handleAck(message) {
|
||||||
const ack = message.obj
|
const ack = message.obj;
|
||||||
this._log.debug(`Handling acknowledge for ${ack.msgIds}`)
|
this._log.debug(`Handling acknowledge for ${ack.msgIds}`);
|
||||||
for (const msgId of ack.msgIds) {
|
for (const msgId of ack.msgIds) {
|
||||||
const state = this._pending_state[msgId]
|
const state = this._pending_state[msgId];
|
||||||
if (state && state.request instanceof LogOut) {
|
if (state && state.request instanceof LogOut) {
|
||||||
delete this._pending_state[msgId]
|
delete this._pending_state[msgId];
|
||||||
state.resolve(true)
|
state.resolve(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -731,12 +736,12 @@ class MTProtoSender {
|
|||||||
async _handleFutureSalts(message) {
|
async _handleFutureSalts(message) {
|
||||||
// TODO save these salts and automatically adjust to the
|
// TODO save these salts and automatically adjust to the
|
||||||
// correct one whenever the salt in use expires.
|
// correct one whenever the salt in use expires.
|
||||||
this._log.debug(`Handling future salts for message ${message.msgId}`)
|
this._log.debug(`Handling future salts for message ${message.msgId}`);
|
||||||
const state = this._pending_state[message.msgId]
|
const state = this._pending_state[message.msgId];
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
delete this._pending_state[message]
|
delete this._pending_state[message];
|
||||||
state.resolve(message.obj)
|
state.resolve(message.obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -749,8 +754,9 @@ class MTProtoSender {
|
|||||||
*/
|
*/
|
||||||
async _handleStateForgotten(message) {
|
async _handleStateForgotten(message) {
|
||||||
this._send_queue.append(
|
this._send_queue.append(
|
||||||
new RequestState(new MsgsStateInfo(message.msgId, String.fromCharCode(1).repeat(message.obj.msgIds))),
|
new RequestState(new MsgsStateInfo(message.msgId, String.fromCharCode(1)
|
||||||
)
|
.repeat(message.obj.msgIds))),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -764,35 +770,35 @@ class MTProtoSender {
|
|||||||
|
|
||||||
async reconnect() {
|
async reconnect() {
|
||||||
if (this._user_connected && !this._reconnecting) {
|
if (this._user_connected && !this._reconnecting) {
|
||||||
this._reconnecting = true
|
this._reconnecting = true;
|
||||||
// TODO Should we set this?
|
// TODO Should we set this?
|
||||||
// this._user_connected = false
|
// this._user_connected = false
|
||||||
this._log.info("Started reconnecting")
|
this._log.info('Started reconnecting');
|
||||||
this._reconnect()
|
this._reconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _reconnect() {
|
async _reconnect() {
|
||||||
this._log.debug('Closing current connection...')
|
this._log.debug('Closing current connection...');
|
||||||
try {
|
try {
|
||||||
await this.disconnect()
|
await this.disconnect();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._log.warn(err)
|
this._log.warn(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._send_queue.append(null)
|
this._send_queue.append(null);
|
||||||
this._state.reset()
|
this._state.reset();
|
||||||
|
|
||||||
await this.connect(this._connection, true)
|
await this.connect(this._connection, true);
|
||||||
|
|
||||||
this._reconnecting = false
|
this._reconnecting = false;
|
||||||
// uncomment this if you want to resend
|
// uncomment this if you want to resend
|
||||||
//this._send_queue.extend(Object.values(this._pending_state))
|
//this._send_queue.extend(Object.values(this._pending_state))
|
||||||
this._pending_state = {}
|
this._pending_state = {};
|
||||||
if (this._autoReconnectCallback) {
|
if (this._autoReconnectCallback) {
|
||||||
await this._autoReconnectCallback()
|
await this._autoReconnectCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MTProtoSender
|
module.exports = MTProtoSender;
|
||||||
|
|||||||
@ -1,13 +1,19 @@
|
|||||||
const Helpers = require('../Helpers')
|
const Helpers = require('../Helpers');
|
||||||
const IGE = require('../crypto/IGE')
|
const IGE = require('../crypto/IGE');
|
||||||
const BinaryReader = require('../extensions/BinaryReader')
|
const BinaryReader = require('../extensions/BinaryReader');
|
||||||
const GZIPPacked = require('../tl/core/GZIPPacked')
|
const GZIPPacked = require('../tl/core/GZIPPacked');
|
||||||
const { TLMessage } = require('../tl/core')
|
const { TLMessage } = require('../tl/core');
|
||||||
const { SecurityError, InvalidBufferError } = require('../errors/Common')
|
const {
|
||||||
const { InvokeAfterMsg } = require('../tl').requests
|
SecurityError,
|
||||||
const BigInt = require('big-integer')
|
InvalidBufferError,
|
||||||
const { toSignedLittleBuffer,readBufferFromBigInt } = require("../Helpers")
|
} = require('../errors/Common');
|
||||||
const { readBigIntFromBuffer } = require("../Helpers")
|
const { InvokeAfterMsg } = require('../tl').requests;
|
||||||
|
const BigInt = require('big-integer');
|
||||||
|
const {
|
||||||
|
toSignedLittleBuffer,
|
||||||
|
readBufferFromBigInt,
|
||||||
|
} = require('../Helpers');
|
||||||
|
const { readBigIntFromBuffer } = require('../Helpers');
|
||||||
|
|
||||||
class MTProtoState {
|
class MTProtoState {
|
||||||
/**
|
/**
|
||||||
@ -36,13 +42,13 @@ class MTProtoState {
|
|||||||
* @param loggers
|
* @param loggers
|
||||||
*/
|
*/
|
||||||
constructor(authKey, loggers) {
|
constructor(authKey, loggers) {
|
||||||
this.authKey = authKey
|
this.authKey = authKey;
|
||||||
this._log = loggers
|
this._log = loggers;
|
||||||
this.timeOffset = 0
|
this.timeOffset = 0;
|
||||||
this.salt = 0
|
this.salt = 0;
|
||||||
|
|
||||||
this.id = this._sequence = this._lastMsgId = null
|
this.id = this._sequence = this._lastMsgId = null;
|
||||||
this.reset()
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,9 +56,9 @@ class MTProtoState {
|
|||||||
*/
|
*/
|
||||||
reset() {
|
reset() {
|
||||||
// Session IDs can be random on every connection
|
// Session IDs can be random on every connection
|
||||||
this.id = Helpers.generateRandomLong(true)
|
this.id = Helpers.generateRandomLong(true);
|
||||||
this._sequence = 0
|
this._sequence = 0;
|
||||||
this._lastMsgId = BigInt(0)
|
this._lastMsgId = BigInt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,7 +67,7 @@ class MTProtoState {
|
|||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
updateMessageId(message) {
|
updateMessageId(message) {
|
||||||
message.msgId = this._getNewMsgId()
|
message.msgId = this._getNewMsgId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,14 +78,17 @@ class MTProtoState {
|
|||||||
* @returns {{iv: Buffer, key: Buffer}}
|
* @returns {{iv: Buffer, key: Buffer}}
|
||||||
*/
|
*/
|
||||||
async _calcKey(authKey, msgKey, client) {
|
async _calcKey(authKey, msgKey, client) {
|
||||||
const x = client === true ? 0 : 8
|
const x = client === true ? 0 : 8;
|
||||||
const [sha256a , sha256b] = await Promise.all([
|
const [sha256a, sha256b] = await Promise.all([
|
||||||
Helpers.sha256(Buffer.concat([msgKey, authKey.slice(x, x + 36)])),
|
Helpers.sha256(Buffer.concat([msgKey, authKey.slice(x, x + 36)])),
|
||||||
Helpers.sha256(Buffer.concat([authKey.slice(x + 40, x + 76), msgKey]))
|
Helpers.sha256(Buffer.concat([authKey.slice(x + 40, x + 76), msgKey])),
|
||||||
])
|
]);
|
||||||
const key = Buffer.concat([sha256a.slice(0, 8), sha256b.slice(8, 24), sha256a.slice(24, 32)])
|
const key = Buffer.concat([sha256a.slice(0, 8), sha256b.slice(8, 24), sha256a.slice(24, 32)]);
|
||||||
const iv = Buffer.concat([sha256b.slice(0, 8), sha256a.slice(8, 24), sha256b.slice(24, 32)])
|
const iv = Buffer.concat([sha256b.slice(0, 8), sha256a.slice(8, 24), sha256b.slice(24, 32)]);
|
||||||
return { key, iv }
|
return {
|
||||||
|
key,
|
||||||
|
iv,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,22 +100,22 @@ class MTProtoState {
|
|||||||
* @param afterId
|
* @param afterId
|
||||||
*/
|
*/
|
||||||
async writeDataAsMessage(buffer, data, contentRelated, afterId) {
|
async writeDataAsMessage(buffer, data, contentRelated, afterId) {
|
||||||
const msgId = this._getNewMsgId()
|
const msgId = this._getNewMsgId();
|
||||||
const seqNo = this._getSeqNo(contentRelated)
|
const seqNo = this._getSeqNo(contentRelated);
|
||||||
let body
|
let body;
|
||||||
if (!afterId) {
|
if (!afterId) {
|
||||||
body = await GZIPPacked.gzipIfSmaller(contentRelated, data)
|
body = await GZIPPacked.gzipIfSmaller(contentRelated, data);
|
||||||
} else {
|
} else {
|
||||||
body = await GZIPPacked.gzipIfSmaller(contentRelated, new InvokeAfterMsg(afterId, data).getBytes())
|
body = await GZIPPacked.gzipIfSmaller(contentRelated, new InvokeAfterMsg(afterId, data).getBytes());
|
||||||
}
|
}
|
||||||
const s = Buffer.alloc(4)
|
const s = Buffer.alloc(4);
|
||||||
s.writeInt32LE(seqNo, 0)
|
s.writeInt32LE(seqNo, 0);
|
||||||
const b = Buffer.alloc(4)
|
const b = Buffer.alloc(4);
|
||||||
b.writeInt32LE(body.length, 0)
|
b.writeInt32LE(body.length, 0);
|
||||||
const m = toSignedLittleBuffer(msgId, 8)
|
const m = toSignedLittleBuffer(msgId, 8);
|
||||||
buffer.write(Buffer.concat([m, s, b]))
|
buffer.write(Buffer.concat([m, s, b]));
|
||||||
buffer.write(body)
|
buffer.write(body);
|
||||||
return msgId
|
return msgId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,21 +124,25 @@ class MTProtoState {
|
|||||||
* @param data
|
* @param data
|
||||||
*/
|
*/
|
||||||
async encryptMessageData(data) {
|
async encryptMessageData(data) {
|
||||||
await this.authKey.waitForKey()
|
await this.authKey.waitForKey();
|
||||||
const s = toSignedLittleBuffer(this.salt,8)
|
const s = toSignedLittleBuffer(this.salt, 8);
|
||||||
const i = toSignedLittleBuffer(this.id,8)
|
const i = toSignedLittleBuffer(this.id, 8);
|
||||||
data = Buffer.concat([Buffer.concat([s,i]), data])
|
data = Buffer.concat([Buffer.concat([s, i]), data]);
|
||||||
const padding = Helpers.generateRandomBytes(Helpers.mod(-(data.length + 12), 16) + 12)
|
const padding = Helpers.generateRandomBytes(Helpers.mod(-(data.length + 12), 16) + 12);
|
||||||
// Being substr(what, offset, length); x = 0 for client
|
// Being substr(what, offset, length); x = 0 for client
|
||||||
// "msg_key_large = SHA256(substr(auth_key, 88+x, 32) + pt + padding)"
|
// "msg_key_large = SHA256(substr(auth_key, 88+x, 32) + pt + padding)"
|
||||||
const msgKeyLarge = await Helpers.sha256(Buffer.concat([this.authKey.getKey().slice(88, 88 + 32), data, padding]))
|
const msgKeyLarge = await Helpers.sha256(Buffer.concat([this.authKey.getKey()
|
||||||
|
.slice(88, 88 + 32), data, padding]));
|
||||||
// "msg_key = substr (msg_key_large, 8, 16)"
|
// "msg_key = substr (msg_key_large, 8, 16)"
|
||||||
const msgKey = msgKeyLarge.slice(8, 24)
|
const msgKey = msgKeyLarge.slice(8, 24);
|
||||||
|
|
||||||
const { iv, key } = await this._calcKey(this.authKey.getKey(), msgKey, true)
|
const {
|
||||||
|
iv,
|
||||||
|
key,
|
||||||
|
} = await this._calcKey(this.authKey.getKey(), msgKey, true);
|
||||||
|
|
||||||
const keyId = Helpers.readBufferFromBigInt(this.authKey.keyId, 8)
|
const keyId = Helpers.readBufferFromBigInt(this.authKey.keyId, 8);
|
||||||
return Buffer.concat([keyId, msgKey, new IGE(key,iv).encryptIge(Buffer.concat([data, padding]))])
|
return Buffer.concat([keyId, msgKey, new IGE(key, iv).encryptIge(Buffer.concat([data, padding]))]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -138,45 +151,49 @@ class MTProtoState {
|
|||||||
*/
|
*/
|
||||||
async decryptMessageData(body) {
|
async decryptMessageData(body) {
|
||||||
if (body.length < 8) {
|
if (body.length < 8) {
|
||||||
throw new InvalidBufferError(body)
|
throw new InvalidBufferError(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Check salt,sessionId, and sequenceNumber
|
// TODO Check salt,sessionId, and sequenceNumber
|
||||||
const keyId = Helpers.readBigIntFromBuffer(body.slice(0, 8))
|
const keyId = Helpers.readBigIntFromBuffer(body.slice(0, 8));
|
||||||
if (keyId.neq(this.authKey.keyId)) {
|
if (keyId.neq(this.authKey.keyId)) {
|
||||||
throw new SecurityError('Server replied with an invalid auth key')
|
throw new SecurityError('Server replied with an invalid auth key');
|
||||||
}
|
}
|
||||||
|
|
||||||
const msgKey = body.slice(8, 24)
|
const msgKey = body.slice(8, 24);
|
||||||
const { iv, key } = await this._calcKey(this.authKey.getKey(), msgKey, false)
|
const {
|
||||||
body = new IGE(key,iv).decryptIge(body.slice(24))
|
iv,
|
||||||
|
key,
|
||||||
|
} = await this._calcKey(this.authKey.getKey(), msgKey, false);
|
||||||
|
body = new IGE(key, iv).decryptIge(body.slice(24));
|
||||||
|
|
||||||
// https://core.telegram.org/mtproto/security_guidelines
|
// https://core.telegram.org/mtproto/security_guidelines
|
||||||
// Sections "checking sha256 hash" and "message length"
|
// Sections "checking sha256 hash" and "message length"
|
||||||
|
|
||||||
const ourKey = await Helpers.sha256(Buffer.concat([this.authKey.getKey().slice(96, 96 + 32), body]))
|
const ourKey = await Helpers.sha256(Buffer.concat([this.authKey.getKey()
|
||||||
|
.slice(96, 96 + 32), body]));
|
||||||
|
|
||||||
if (!msgKey.equals(ourKey.slice(8, 24))) {
|
if (!msgKey.equals(ourKey.slice(8, 24))) {
|
||||||
throw new SecurityError('Received msg_key doesn\'t match with expected one')
|
throw new SecurityError('Received msg_key doesn\'t match with expected one');
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = new BinaryReader(body)
|
const reader = new BinaryReader(body);
|
||||||
reader.readLong() // removeSalt
|
reader.readLong(); // removeSalt
|
||||||
const serverId = reader.readLong()
|
const serverId = reader.readLong();
|
||||||
if (serverId !== this.id) {
|
if (serverId !== this.id) {
|
||||||
// throw new SecurityError('Server replied with a wrong session ID');
|
// throw new SecurityError('Server replied with a wrong session ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
const remoteMsgId = reader.readLong()
|
const remoteMsgId = reader.readLong();
|
||||||
const remoteSequence = reader.readInt()
|
const remoteSequence = reader.readInt();
|
||||||
reader.readInt() // msgLen for the inner object, padding ignored
|
reader.readInt(); // msgLen for the inner object, padding ignored
|
||||||
|
|
||||||
// We could read msg_len bytes and use those in a new reader to read
|
// We could read msg_len bytes and use those in a new reader to read
|
||||||
// the next TLObject without including the padding, but since the
|
// the next TLObject without including the padding, but since the
|
||||||
// reader isn't used for anything else after this, it's unnecessary.
|
// reader isn't used for anything else after this, it's unnecessary.
|
||||||
const obj = reader.tgReadObject()
|
const obj = reader.tgReadObject();
|
||||||
|
|
||||||
return new TLMessage(remoteMsgId, remoteSequence, obj)
|
return new TLMessage(remoteMsgId, remoteSequence, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,14 +202,16 @@ class MTProtoState {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_getNewMsgId() {
|
_getNewMsgId() {
|
||||||
const now = new Date().getTime() / 1000 + this.timeOffset
|
const now = new Date().getTime() / 1000 + this.timeOffset;
|
||||||
const nanoseconds = Math.floor((now - Math.floor(now)) * 1e9)
|
const nanoseconds = Math.floor((now - Math.floor(now)) * 1e9);
|
||||||
let newMsgId = (BigInt(Math.floor(now)).shiftLeft(BigInt(32))).or(BigInt(nanoseconds).shiftLeft(BigInt(2)))
|
let newMsgId = (BigInt(Math.floor(now))
|
||||||
|
.shiftLeft(BigInt(32))).or(BigInt(nanoseconds)
|
||||||
|
.shiftLeft(BigInt(2)));
|
||||||
if (this._lastMsgId.greaterOrEquals(newMsgId)) {
|
if (this._lastMsgId.greaterOrEquals(newMsgId)) {
|
||||||
newMsgId = this._lastMsgId.add(BigInt(4))
|
newMsgId = this._lastMsgId.add(BigInt(4));
|
||||||
}
|
}
|
||||||
this._lastMsgId = newMsgId
|
this._lastMsgId = newMsgId;
|
||||||
return newMsgId
|
return newMsgId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -201,20 +220,20 @@ class MTProtoState {
|
|||||||
* @param correctMsgId {BigInteger}
|
* @param correctMsgId {BigInteger}
|
||||||
*/
|
*/
|
||||||
updateTimeOffset(correctMsgId) {
|
updateTimeOffset(correctMsgId) {
|
||||||
const bad = this._getNewMsgId()
|
const bad = this._getNewMsgId();
|
||||||
const old = this.timeOffset
|
const old = this.timeOffset;
|
||||||
const now = Math.floor(new Date().getTime() / 1000)
|
const now = Math.floor(new Date().getTime() / 1000);
|
||||||
const correct = correctMsgId.shiftRight(BigInt(32))
|
const correct = correctMsgId.shiftRight(BigInt(32));
|
||||||
this.timeOffset = correct - now
|
this.timeOffset = correct - now;
|
||||||
|
|
||||||
if (this.timeOffset !== old) {
|
if (this.timeOffset !== old) {
|
||||||
this._lastMsgId = BigInt(0)
|
this._lastMsgId = BigInt(0);
|
||||||
this._log.debug(
|
this._log.debug(
|
||||||
`Updated time offset (old offset ${old}, bad ${bad}, good ${correctMsgId}, new ${this.timeOffset})`,
|
`Updated time offset (old offset ${old}, bad ${bad}, good ${correctMsgId}, new ${this.timeOffset})`,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.timeOffset
|
return this.timeOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -225,13 +244,13 @@ class MTProtoState {
|
|||||||
*/
|
*/
|
||||||
_getSeqNo(contentRelated) {
|
_getSeqNo(contentRelated) {
|
||||||
if (contentRelated) {
|
if (contentRelated) {
|
||||||
const result = this._sequence * 2 + 1
|
const result = this._sequence * 2 + 1;
|
||||||
this._sequence += 1
|
this._sequence += 1;
|
||||||
return result
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return this._sequence * 2
|
return this._sequence * 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MTProtoState
|
module.exports = MTProtoState;
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
class RequestState {
|
class RequestState {
|
||||||
constructor(request, after = null) {
|
constructor(request, after = null) {
|
||||||
this.containerId = null
|
this.containerId = null;
|
||||||
this.msgId = null
|
this.msgId = null;
|
||||||
this.request = request
|
this.request = request;
|
||||||
this.data = request.getBytes()
|
this.data = request.getBytes();
|
||||||
this.after = after
|
this.after = after;
|
||||||
this.result = null
|
this.result = null;
|
||||||
this.promise = new Promise((resolve, reject) => {
|
this.promise = new Promise((resolve, reject) => {
|
||||||
this.resolve = resolve
|
this.resolve = resolve;
|
||||||
this.reject = reject
|
this.reject = reject;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = RequestState
|
module.exports = RequestState;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const PromisedWebSockets = require('../../extensions/PromisedWebSockets')
|
const PromisedWebSockets = require('../../extensions/PromisedWebSockets');
|
||||||
const AsyncQueue = require('../../extensions/AsyncQueue')
|
const AsyncQueue = require('../../extensions/AsyncQueue');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Connection` class is a wrapper around ``asyncio.open_connection``.
|
* The `Connection` class is a wrapper around ``asyncio.open_connection``.
|
||||||
@ -13,155 +13,155 @@ const AsyncQueue = require('../../extensions/AsyncQueue')
|
|||||||
* the client is disconnected (includes remote disconnections).
|
* the client is disconnected (includes remote disconnections).
|
||||||
*/
|
*/
|
||||||
class Connection {
|
class Connection {
|
||||||
PacketCodecClass = null
|
PacketCodecClass = null;
|
||||||
|
|
||||||
constructor(ip, port, dcId, loggers) {
|
constructor(ip, port, dcId, loggers) {
|
||||||
this._ip = ip
|
this._ip = ip;
|
||||||
this._port = port
|
this._port = port;
|
||||||
this._dcId = dcId
|
this._dcId = dcId;
|
||||||
this._log = loggers
|
this._log = loggers;
|
||||||
this._connected = false
|
this._connected = false;
|
||||||
this._sendTask = null
|
this._sendTask = null;
|
||||||
this._recvTask = null
|
this._recvTask = null;
|
||||||
this._codec = null
|
this._codec = null;
|
||||||
this._obfuscation = null // TcpObfuscated and MTProxy
|
this._obfuscation = null; // TcpObfuscated and MTProxy
|
||||||
this._sendArray = new AsyncQueue()
|
this._sendArray = new AsyncQueue();
|
||||||
this._recvArray = new AsyncQueue()
|
this._recvArray = new AsyncQueue();
|
||||||
//this.socket = new PromiseSocket(new Socket())
|
//this.socket = new PromiseSocket(new Socket())
|
||||||
|
|
||||||
this.socket = new PromisedWebSockets()
|
this.socket = new PromisedWebSockets();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _connect() {
|
async _connect() {
|
||||||
this._log.debug('Connecting')
|
this._log.debug('Connecting');
|
||||||
this._codec = new this.PacketCodecClass(this)
|
this._codec = new this.PacketCodecClass(this);
|
||||||
await this.socket.connect(this._port, this._ip, this)
|
await this.socket.connect(this._port, this._ip, this);
|
||||||
this._log.debug('Finished connecting')
|
this._log.debug('Finished connecting');
|
||||||
// await this.socket.connect({host: this._ip, port: this._port});
|
// await this.socket.connect({host: this._ip, port: this._port});
|
||||||
await this._initConn()
|
await this._initConn();
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
await this._connect()
|
await this._connect();
|
||||||
this._connected = true
|
this._connected = true;
|
||||||
|
|
||||||
if (!this._sendTask) {
|
if (!this._sendTask) {
|
||||||
this._sendTask = this._sendLoop()
|
this._sendTask = this._sendLoop();
|
||||||
}
|
}
|
||||||
this._recvTask = this._recvLoop()
|
this._recvTask = this._recvLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnect() {
|
async disconnect() {
|
||||||
this._connected = false
|
this._connected = false;
|
||||||
await this._recvArray.push(null)
|
await this._recvArray.push(null);
|
||||||
await this.socket.close()
|
await this.socket.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(data) {
|
async send(data) {
|
||||||
if (!this._connected) {
|
if (!this._connected) {
|
||||||
throw new Error('Not connected')
|
throw new Error('Not connected');
|
||||||
}
|
}
|
||||||
await this._sendArray.push(data)
|
await this._sendArray.push(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async recv() {
|
async recv() {
|
||||||
while (this._connected) {
|
while (this._connected) {
|
||||||
const result = await this._recvArray.pop()
|
const result = await this._recvArray.pop();
|
||||||
// null = sentinel value = keep trying
|
// null = sentinel value = keep trying
|
||||||
if (result) {
|
if (result) {
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error('Not connected')
|
throw new Error('Not connected');
|
||||||
}
|
}
|
||||||
|
|
||||||
async _sendLoop() {
|
async _sendLoop() {
|
||||||
// TODO handle errors
|
// TODO handle errors
|
||||||
try {
|
try {
|
||||||
while (this._connected) {
|
while (this._connected) {
|
||||||
const data = await this._sendArray.pop()
|
const data = await this._sendArray.pop();
|
||||||
if (!data) {
|
if (!data) {
|
||||||
this._sendTask = null
|
this._sendTask = null;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
await this._send(data)
|
await this._send(data);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._log.info('The server closed the connection while sending')
|
this._log.info('The server closed the connection while sending');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _recvLoop() {
|
async _recvLoop() {
|
||||||
let data
|
let data;
|
||||||
while (this._connected) {
|
while (this._connected) {
|
||||||
try {
|
try {
|
||||||
data = await this._recv()
|
data = await this._recv();
|
||||||
if (!data) {
|
if (!data) {
|
||||||
throw new Error("no data received")
|
throw new Error('no data received');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._log.info('connection closed')
|
this._log.info('connection closed');
|
||||||
//await this._recvArray.push()
|
//await this._recvArray.push()
|
||||||
|
|
||||||
this.disconnect()
|
this.disconnect();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
await this._recvArray.push(data)
|
await this._recvArray.push(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _initConn() {
|
async _initConn() {
|
||||||
if (this._codec.tag) {
|
if (this._codec.tag) {
|
||||||
await this.socket.write(this._codec.tag)
|
await this.socket.write(this._codec.tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _send(data) {
|
async _send(data) {
|
||||||
const encodedPacket = this._codec.encodePacket(data)
|
const encodedPacket = this._codec.encodePacket(data);
|
||||||
this.socket.write(encodedPacket)
|
this.socket.write(encodedPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _recv() {
|
async _recv() {
|
||||||
return await this._codec.readPacket(this.socket)
|
return await this._codec.readPacket(this.socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `${this._ip}:${this._port}/${this.constructor.name.replace('Connection', '')}`
|
return `${this._ip}:${this._port}/${this.constructor.name.replace('Connection', '')}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObfuscatedConnection extends Connection {
|
class ObfuscatedConnection extends Connection {
|
||||||
ObfuscatedIO = null
|
ObfuscatedIO = null;
|
||||||
|
|
||||||
async _initConn() {
|
async _initConn() {
|
||||||
this._obfuscation = new this.ObfuscatedIO(this)
|
this._obfuscation = new this.ObfuscatedIO(this);
|
||||||
this.socket.write(this._obfuscation.header)
|
this.socket.write(this._obfuscation.header);
|
||||||
}
|
}
|
||||||
|
|
||||||
_send(data) {
|
_send(data) {
|
||||||
this._obfuscation.write(this._codec.encodePacket(data))
|
this._obfuscation.write(this._codec.encodePacket(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async _recv() {
|
async _recv() {
|
||||||
return await this._codec.readPacket(this._obfuscation)
|
return await this._codec.readPacket(this._obfuscation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PacketCodec {
|
class PacketCodec {
|
||||||
constructor(connection) {
|
constructor(connection) {
|
||||||
this._conn = connection
|
this._conn = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
encodePacket(data) {
|
encodePacket(data) {
|
||||||
throw new Error('Not Implemented')
|
throw new Error('Not Implemented');
|
||||||
|
|
||||||
// Override
|
// Override
|
||||||
}
|
}
|
||||||
|
|
||||||
async readPacket(reader) {
|
async readPacket(reader) {
|
||||||
// override
|
// override
|
||||||
throw new Error('Not Implemented')
|
throw new Error('Not Implemented');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,4 +169,4 @@ module.exports = {
|
|||||||
Connection,
|
Connection,
|
||||||
PacketCodec,
|
PacketCodec,
|
||||||
ObfuscatedConnection,
|
ObfuscatedConnection,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,38 +1,41 @@
|
|||||||
const { readBufferFromBigInt } = require('../../Helpers')
|
const { readBufferFromBigInt } = require('../../Helpers');
|
||||||
const { Connection, PacketCodec } = require('./Connection')
|
const {
|
||||||
const BigInt = require('big-integer')
|
Connection,
|
||||||
|
PacketCodec,
|
||||||
|
} = require('./Connection');
|
||||||
|
const BigInt = require('big-integer');
|
||||||
|
|
||||||
class AbridgedPacketCodec extends PacketCodec {
|
class AbridgedPacketCodec extends PacketCodec {
|
||||||
static tag = Buffer.from('ef', 'hex')
|
static tag = Buffer.from('ef', 'hex');
|
||||||
static obfuscateTag = Buffer.from('efefefef', 'hex')
|
static obfuscateTag = Buffer.from('efefefef', 'hex');
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props);
|
||||||
this.tag = AbridgedPacketCodec.tag
|
this.tag = AbridgedPacketCodec.tag;
|
||||||
this.obfuscateTag = AbridgedPacketCodec.obfuscateTag
|
this.obfuscateTag = AbridgedPacketCodec.obfuscateTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
encodePacket(data) {
|
encodePacket(data) {
|
||||||
let length = data.length >> 2
|
let length = data.length >> 2;
|
||||||
if (length < 127) {
|
if (length < 127) {
|
||||||
const b = Buffer.alloc(1)
|
const b = Buffer.alloc(1);
|
||||||
b.writeUInt8(length, 0)
|
b.writeUInt8(length, 0);
|
||||||
length = b
|
length = b;
|
||||||
} else {
|
} else {
|
||||||
length = Buffer.concat([Buffer.from('7f', 'hex'), readBufferFromBigInt(BigInt(length), 3)])
|
length = Buffer.concat([Buffer.from('7f', 'hex'), readBufferFromBigInt(BigInt(length), 3)]);
|
||||||
}
|
}
|
||||||
return Buffer.concat([length, data])
|
return Buffer.concat([length, data]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async readPacket(reader) {
|
async readPacket(reader) {
|
||||||
const readData = await reader.read(1)
|
const readData = await reader.read(1);
|
||||||
let length = readData[0]
|
let length = readData[0];
|
||||||
if (length >= 127) {
|
if (length >= 127) {
|
||||||
length = Buffer.concat([await reader.read(3), Buffer.alloc(1)])
|
length = Buffer.concat([await reader.read(3), Buffer.alloc(1)])
|
||||||
.readInt32LE(0)
|
.readInt32LE(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await reader.read(length << 2)
|
return await reader.read(length << 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,10 +45,10 @@ class AbridgedPacketCodec extends PacketCodec {
|
|||||||
* 508 bytes (127 << 2, which is very common).
|
* 508 bytes (127 << 2, which is very common).
|
||||||
*/
|
*/
|
||||||
class ConnectionTCPAbridged extends Connection {
|
class ConnectionTCPAbridged extends Connection {
|
||||||
PacketCodecClass = AbridgedPacketCodec
|
PacketCodecClass = AbridgedPacketCodec;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ConnectionTCPAbridged,
|
ConnectionTCPAbridged,
|
||||||
AbridgedPacketCodec,
|
AbridgedPacketCodec,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,78 +1,85 @@
|
|||||||
const { generateRandomBytes } = require('../../Helpers')
|
const { generateRandomBytes } = require('../../Helpers');
|
||||||
const { ObfuscatedConnection } = require('./Connection')
|
const { ObfuscatedConnection } = require('./Connection');
|
||||||
const { AbridgedPacketCodec } = require('./TCPAbridged')
|
const { AbridgedPacketCodec } = require('./TCPAbridged');
|
||||||
const CTR = require('../../crypto/CTR')
|
const CTR = require('../../crypto/CTR');
|
||||||
|
|
||||||
class ObfuscatedIO {
|
class ObfuscatedIO {
|
||||||
header = null
|
header = null;
|
||||||
|
|
||||||
constructor(connection) {
|
constructor(connection) {
|
||||||
this.connection = connection.socket
|
this.connection = connection.socket;
|
||||||
const res = this.initHeader(connection.PacketCodecClass)
|
const res = this.initHeader(connection.PacketCodecClass);
|
||||||
this.header = res.random
|
this.header = res.random;
|
||||||
|
|
||||||
this._encrypt = res.encryptor
|
this._encrypt = res.encryptor;
|
||||||
this._decrypt = res.decryptor
|
this._decrypt = res.decryptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
initHeader(packetCodec) {
|
initHeader(packetCodec) {
|
||||||
// Obfuscated messages secrets cannot start with any of these
|
// Obfuscated messages secrets cannot start with any of these
|
||||||
const keywords = [Buffer.from('50567247', 'hex'), Buffer.from('474554', 'hex'),
|
const keywords = [Buffer.from('50567247', 'hex'), Buffer.from('474554', 'hex'),
|
||||||
Buffer.from('504f5354', 'hex'), Buffer.from('eeeeeeee', 'hex')]
|
Buffer.from('504f5354', 'hex'), Buffer.from('eeeeeeee', 'hex')];
|
||||||
let random
|
let random;
|
||||||
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
// eslint-disable-next-line no-constant-condition
|
||||||
while (true) {
|
while (true) {
|
||||||
random = generateRandomBytes(64)
|
random = generateRandomBytes(64);
|
||||||
if (random[0] !== 0xef && !(random.slice(4, 8).equals(Buffer.alloc(4)))) {
|
if (random[0] !== 0xef && !(random.slice(4, 8)
|
||||||
let ok = true
|
.equals(Buffer.alloc(4)))) {
|
||||||
|
let ok = true;
|
||||||
for (const key of keywords) {
|
for (const key of keywords) {
|
||||||
if (key.equals(random.slice(0, 4))) {
|
if (key.equals(random.slice(0, 4))) {
|
||||||
ok = false
|
ok = false;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ok) {
|
if (ok) {
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
random = random.toJSON().data
|
random = random.toJSON().data;
|
||||||
|
|
||||||
const randomReversed = Buffer.from(random.slice(8, 56)).reverse()
|
const randomReversed = Buffer.from(random.slice(8, 56))
|
||||||
|
.reverse();
|
||||||
// Encryption has "continuous buffer" enabled
|
// Encryption has "continuous buffer" enabled
|
||||||
const encryptKey = Buffer.from(random.slice(8, 40))
|
const encryptKey = Buffer.from(random.slice(8, 40));
|
||||||
const encryptIv = Buffer.from(random.slice(40, 56))
|
const encryptIv = Buffer.from(random.slice(40, 56));
|
||||||
const decryptKey = Buffer.from(randomReversed.slice(0, 32))
|
const decryptKey = Buffer.from(randomReversed.slice(0, 32));
|
||||||
const decryptIv = Buffer.from(randomReversed.slice(32, 48))
|
const decryptIv = Buffer.from(randomReversed.slice(32, 48));
|
||||||
const encryptor = new CTR(encryptKey, encryptIv)
|
const encryptor = new CTR(encryptKey, encryptIv);
|
||||||
const decryptor = new CTR(decryptKey, decryptIv)
|
const decryptor = new CTR(decryptKey, decryptIv);
|
||||||
|
|
||||||
random = Buffer.concat([
|
random = Buffer.concat([
|
||||||
Buffer.from(random.slice(0, 56)), packetCodec.obfuscateTag, Buffer.from(random.slice(60)),
|
Buffer.from(random.slice(0, 56)), packetCodec.obfuscateTag, Buffer.from(random.slice(60)),
|
||||||
])
|
]);
|
||||||
random = Buffer.concat([
|
random = Buffer.concat([
|
||||||
Buffer.from(random.slice(0, 56)), Buffer.from(encryptor.encrypt(random).slice(56, 64)),Buffer.from(random.slice(64)) ,
|
Buffer.from(random.slice(0, 56)), Buffer.from(encryptor.encrypt(random)
|
||||||
])
|
.slice(56, 64)), Buffer.from(random.slice(64)),
|
||||||
return { random, encryptor, decryptor }
|
]);
|
||||||
|
return {
|
||||||
|
random,
|
||||||
|
encryptor,
|
||||||
|
decryptor,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async read(n) {
|
async read(n) {
|
||||||
const data = await this.connection.readExactly(n)
|
const data = await this.connection.readExactly(n);
|
||||||
return this._decrypt.encrypt(data)
|
return this._decrypt.encrypt(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
write(data) {
|
write(data) {
|
||||||
this.connection.write(this._encrypt.encrypt(data))
|
this.connection.write(this._encrypt.encrypt(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConnectionTCPObfuscated extends ObfuscatedConnection {
|
class ConnectionTCPObfuscated extends ObfuscatedConnection {
|
||||||
ObfuscatedIO = ObfuscatedIO
|
ObfuscatedIO = ObfuscatedIO;
|
||||||
PacketCodecClass = AbridgedPacketCodec
|
PacketCodecClass = AbridgedPacketCodec;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ConnectionTCPObfuscated,
|
ConnectionTCPObfuscated,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
const { Connection } = require('./Connection')
|
const { Connection } = require('./Connection');
|
||||||
const { ConnectionTCPFull } = require('./TCPFull')
|
const { ConnectionTCPFull } = require('./TCPFull');
|
||||||
const { ConnectionTCPAbridged } = require('./TCPAbridged')
|
const { ConnectionTCPAbridged } = require('./TCPAbridged');
|
||||||
const { ConnectionTCPObfuscated } = require('./TCPObfuscated')
|
const { ConnectionTCPObfuscated } = require('./TCPObfuscated');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Connection,
|
Connection,
|
||||||
ConnectionTCPFull,
|
ConnectionTCPFull,
|
||||||
ConnectionTCPAbridged,
|
ConnectionTCPAbridged,
|
||||||
ConnectionTCPObfuscated,
|
ConnectionTCPObfuscated,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
const MTProtoPlainSender = require('./MTProtoPlainSender')
|
const MTProtoPlainSender = require('./MTProtoPlainSender');
|
||||||
const doAuthentication = require('./Authenticator')
|
const doAuthentication = require('./Authenticator');
|
||||||
const MTProtoSender = require('./MTProtoSender')
|
const MTProtoSender = require('./MTProtoSender');
|
||||||
|
|
||||||
class UpdateConnectionState {
|
class UpdateConnectionState {
|
||||||
static disconnected = -1
|
static disconnected = -1;
|
||||||
static connected = 1
|
static connected = 1;
|
||||||
static broken = 0
|
static broken = 0;
|
||||||
|
|
||||||
constructor(state) {
|
constructor(state) {
|
||||||
this.state = state
|
this.state = state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ const {
|
|||||||
ConnectionTCPFull,
|
ConnectionTCPFull,
|
||||||
ConnectionTCPAbridged,
|
ConnectionTCPAbridged,
|
||||||
ConnectionTCPObfuscated,
|
ConnectionTCPObfuscated,
|
||||||
} = require('./connection')
|
} = require('./connection');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Connection,
|
Connection,
|
||||||
ConnectionTCPFull,
|
ConnectionTCPFull,
|
||||||
@ -27,4 +27,4 @@ module.exports = {
|
|||||||
doAuthentication,
|
doAuthentication,
|
||||||
MTProtoSender,
|
MTProtoSender,
|
||||||
UpdateConnectionState,
|
UpdateConnectionState,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -8,11 +8,49 @@ class Session {
|
|||||||
* @param toInstance {Session|null}
|
* @param toInstance {Session|null}
|
||||||
* @returns {Session}
|
* @returns {Session}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* CONTEST
|
/* CONTEST
|
||||||
clone(toInstance = null) {
|
clone(toInstance = null) {
|
||||||
return toInstance || new this.constructor()
|
return toInstance || new this.constructor()
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the currently-used data center ID.
|
||||||
|
*/
|
||||||
|
get dcId() {
|
||||||
|
throw new Error('Not Implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the server address where the library should connect to.
|
||||||
|
*/
|
||||||
|
get serverAddress() {
|
||||||
|
throw new Error('Not Implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the port to which the library should connect to.
|
||||||
|
*/
|
||||||
|
get port() {
|
||||||
|
throw new Error('Not Implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an ``AuthKey`` instance associated with the saved
|
||||||
|
* data center, or `None` if a new one should be generated.
|
||||||
|
*/
|
||||||
|
get authKey() {
|
||||||
|
throw new Error('Not Implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the ``AuthKey`` to be used for the saved data center.
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
set authKey(value) {
|
||||||
|
throw new Error('Not Implemented');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the information of the data center address and port that
|
* Sets the information of the data center address and port that
|
||||||
* the library should connect to, as well as the data center ID,
|
* the library should connect to, as well as the data center ID,
|
||||||
@ -22,44 +60,7 @@ class Session {
|
|||||||
* @param port {number}
|
* @param port {number}
|
||||||
*/
|
*/
|
||||||
setDC(dcId, serverAddress, port) {
|
setDC(dcId, serverAddress, port) {
|
||||||
throw new Error('Not implemented')
|
throw new Error('Not implemented');
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the currently-used data center ID.
|
|
||||||
*/
|
|
||||||
get dcId() {
|
|
||||||
throw new Error('Not Implemented')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the server address where the library should connect to.
|
|
||||||
*/
|
|
||||||
get serverAddress() {
|
|
||||||
throw new Error('Not Implemented')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the port to which the library should connect to.
|
|
||||||
*/
|
|
||||||
get port() {
|
|
||||||
throw new Error('Not Implemented')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an ``AuthKey`` instance associated with the saved
|
|
||||||
* data center, or `None` if a new one should be generated.
|
|
||||||
*/
|
|
||||||
get authKey() {
|
|
||||||
throw new Error('Not Implemented')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the ``AuthKey`` to be used for the saved data center.
|
|
||||||
* @param value
|
|
||||||
*/
|
|
||||||
set authKey(value) {
|
|
||||||
throw new Error('Not Implemented')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,6 +113,7 @@ class Session {
|
|||||||
* Called on client disconnection. Should be used to
|
* Called on client disconnection. Should be used to
|
||||||
* free any used resources. Can be left empty if none.
|
* free any used resources. Can be left empty if none.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*CONTEST
|
/*CONTEST
|
||||||
close() {
|
close() {
|
||||||
|
|
||||||
@ -124,7 +126,7 @@ class Session {
|
|||||||
* make persist the relevant session information to disk.
|
* make persist the relevant session information to disk.
|
||||||
*/
|
*/
|
||||||
save() {
|
save() {
|
||||||
throw new Error('Not Implemented')
|
throw new Error('Not Implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,7 +135,7 @@ class Session {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
throw new Error('Not Implemented')
|
throw new Error('Not Implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -172,4 +174,4 @@ class Session {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Session
|
module.exports = Session;
|
||||||
|
|||||||
@ -1,27 +1,27 @@
|
|||||||
const StorageSession = require('./StorageSession')
|
const StorageSession = require('./StorageSession');
|
||||||
|
|
||||||
const CACHE_NAME = 'GramJs'
|
const CACHE_NAME = 'GramJs';
|
||||||
|
|
||||||
class CacheApiSession extends StorageSession {
|
class CacheApiSession extends StorageSession {
|
||||||
async _delete() {
|
async _delete() {
|
||||||
const request = new Request(this._storageKey)
|
const request = new Request(this._storageKey);
|
||||||
const cache = await self.caches.open(CACHE_NAME)
|
const cache = await self.caches.open(CACHE_NAME);
|
||||||
return cache.delete(request)
|
return cache.delete(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchFromCache() {
|
async _fetchFromCache() {
|
||||||
const request = new Request(this._storageKey)
|
const request = new Request(this._storageKey);
|
||||||
const cache = await self.caches.open(CACHE_NAME)
|
const cache = await self.caches.open(CACHE_NAME);
|
||||||
const cached = await cache.match(request)
|
const cached = await cache.match(request);
|
||||||
return cached ? cached.text() : null
|
return cached ? cached.text() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _saveToCache(data) {
|
async _saveToCache(data) {
|
||||||
const request = new Request(this._storageKey)
|
const request = new Request(this._storageKey);
|
||||||
const response = new Response(data)
|
const response = new Response(data);
|
||||||
const cache = await self.caches.open(CACHE_NAME)
|
const cache = await self.caches.open(CACHE_NAME);
|
||||||
return cache.put(request, response)
|
return cache.put(request, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = CacheApiSession
|
module.exports = CacheApiSession;
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
const StorageSession = require('./StorageSession')
|
const StorageSession = require('./StorageSession');
|
||||||
const idb = require('idb-keyval')
|
const idb = require('idb-keyval');
|
||||||
|
|
||||||
const CACHE_NAME = 'GramJs'
|
const CACHE_NAME = 'GramJs';
|
||||||
|
|
||||||
class IdbSession extends StorageSession {
|
class IdbSession extends StorageSession {
|
||||||
async _delete() {
|
async _delete() {
|
||||||
return idb.del(`${CACHE_NAME}:${this._storageKey}`)
|
return idb.del(`${CACHE_NAME}:${this._storageKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchFromCache() {
|
async _fetchFromCache() {
|
||||||
return idb.get(`${CACHE_NAME}:${this._storageKey}`)
|
return idb.get(`${CACHE_NAME}:${this._storageKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _saveToCache(data) {
|
async _saveToCache(data) {
|
||||||
return idb.set(`${CACHE_NAME}:${this._storageKey}`, data)
|
return idb.set(`${CACHE_NAME}:${this._storageKey}`, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = IdbSession
|
module.exports = IdbSession;
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
const StorageSession = require('./StorageSession')
|
const StorageSession = require('./StorageSession');
|
||||||
|
|
||||||
class LocalStorageSession extends StorageSession {
|
class LocalStorageSession extends StorageSession {
|
||||||
async _delete() {
|
async _delete() {
|
||||||
return localStorage.removeItem(this._storageKey)
|
return localStorage.removeItem(this._storageKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchFromCache(key) {
|
async _fetchFromCache(key) {
|
||||||
return localStorage.getItem(this._storageKey)
|
return localStorage.getItem(this._storageKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _saveToCache(key, data) {
|
async _saveToCache(key, data) {
|
||||||
return localStorage.setItem(this._storageKey, data)
|
return localStorage.setItem(this._storageKey, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = LocalStorageSession
|
module.exports = LocalStorageSession;
|
||||||
|
|||||||
@ -1,45 +1,46 @@
|
|||||||
const utils = require('../Utils')
|
const utils = require('../Utils');
|
||||||
const types = require('../tl').constructors
|
const types = require('../tl').constructors;
|
||||||
const Session = require('./Abstract')
|
const Session = require('./Abstract');
|
||||||
|
|
||||||
class MemorySession extends Session {
|
class MemorySession extends Session {
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super();
|
||||||
|
|
||||||
this._serverAddress = null
|
this._serverAddress = null;
|
||||||
this._dcId = 0
|
this._dcId = 0;
|
||||||
this._port = null
|
this._port = null;
|
||||||
this._takeoutId = null
|
this._takeoutId = null;
|
||||||
|
|
||||||
this._entities = new Set()
|
this._entities = new Set();
|
||||||
this._updateStates = {}
|
this._updateStates = {};
|
||||||
}
|
|
||||||
|
|
||||||
setDC(dcId, serverAddress, port) {
|
|
||||||
this._dcId = dcId | 0
|
|
||||||
this._serverAddress = serverAddress
|
|
||||||
this._port = port
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get dcId() {
|
get dcId() {
|
||||||
return this._dcId
|
return this._dcId;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serverAddress() {
|
get serverAddress() {
|
||||||
return this._serverAddress
|
return this._serverAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
get port() {
|
get port() {
|
||||||
return this._port
|
return this._port;
|
||||||
}
|
}
|
||||||
|
|
||||||
get authKey() {
|
get authKey() {
|
||||||
return this._authKey
|
return this._authKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
set authKey(value) {
|
set authKey(value) {
|
||||||
this._authKey = value
|
this._authKey = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDC(dcId, serverAddress, port) {
|
||||||
|
this._dcId = dcId | 0;
|
||||||
|
this._serverAddress = serverAddress;
|
||||||
|
this._port = port;
|
||||||
|
}
|
||||||
|
|
||||||
/* CONTEST
|
/* CONTEST
|
||||||
get takeoutId() {
|
get takeoutId() {
|
||||||
return this._takeoutId
|
return this._takeoutId
|
||||||
@ -253,4 +254,4 @@ class MemorySession extends Session {
|
|||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MemorySession
|
module.exports = MemorySession;
|
||||||
|
|||||||
@ -1,135 +1,144 @@
|
|||||||
const MemorySession = require('./Memory')
|
const MemorySession = require('./Memory');
|
||||||
const AuthKey = require('../crypto/AuthKey')
|
const AuthKey = require('../crypto/AuthKey');
|
||||||
const utils = require('../Utils')
|
const utils = require('../Utils');
|
||||||
|
|
||||||
const STORAGE_KEY_BASE = 'GramJs-session-'
|
const STORAGE_KEY_BASE = 'GramJs-session-';
|
||||||
|
|
||||||
class StorageSession extends MemorySession {
|
class StorageSession extends MemorySession {
|
||||||
constructor(sessionId) {
|
constructor(sessionId) {
|
||||||
super()
|
super();
|
||||||
this._storageKey = sessionId
|
this._storageKey = sessionId;
|
||||||
this._authKeys = {}
|
this._authKeys = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
get authKey() {
|
||||||
|
throw new Error('Not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
set authKey(value) {
|
||||||
|
throw new Error('Not supported');
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
if (!this._storageKey) {
|
if (!this._storageKey) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const json = await this._fetchFromCache()
|
const json = await this._fetchFromCache();
|
||||||
const { mainDcId, keys, hashes } = JSON.parse(json)
|
const {
|
||||||
const { ipAddress, port } = utils.getDC(mainDcId)
|
mainDcId,
|
||||||
|
keys,
|
||||||
|
hashes,
|
||||||
|
} = JSON.parse(json);
|
||||||
|
const {
|
||||||
|
ipAddress,
|
||||||
|
port,
|
||||||
|
} = utils.getDC(mainDcId);
|
||||||
|
|
||||||
this.setDC(mainDcId, ipAddress, port, true)
|
this.setDC(mainDcId, ipAddress, port, true);
|
||||||
|
|
||||||
Object.keys(keys).forEach((dcId) => {
|
Object.keys(keys)
|
||||||
if (keys[dcId] && hashes[dcId]){
|
.forEach((dcId) => {
|
||||||
this._authKeys[dcId] = new AuthKey(
|
if (keys[dcId] && hashes[dcId]) {
|
||||||
Buffer.from(keys[dcId].data),
|
this._authKeys[dcId] = new AuthKey(
|
||||||
Buffer.from(hashes[dcId].data)
|
Buffer.from(keys[dcId].data),
|
||||||
)
|
Buffer.from(hashes[dcId].data),
|
||||||
}
|
);
|
||||||
})
|
}
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to retrieve or parse session from storage')
|
console.warn('Failed to retrieve or parse session from storage');
|
||||||
console.warn(err)
|
console.warn(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setDC(dcId, serverAddress, port, skipUpdateStorage = false) {
|
setDC(dcId, serverAddress, port, skipUpdateStorage = false) {
|
||||||
this._dcId = dcId
|
this._dcId = dcId;
|
||||||
this._serverAddress = serverAddress
|
this._serverAddress = serverAddress;
|
||||||
this._port = port
|
this._port = port;
|
||||||
|
|
||||||
delete this._authKeys[dcId]
|
delete this._authKeys[dcId];
|
||||||
|
|
||||||
if (!skipUpdateStorage) {
|
if (!skipUpdateStorage) {
|
||||||
void this._updateStorage()
|
void this._updateStorage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
if (!this._storageKey) {
|
if (!this._storageKey) {
|
||||||
this._storageKey = generateStorageKey()
|
this._storageKey = generateStorageKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._updateStorage()
|
await this._updateStorage();
|
||||||
|
|
||||||
return this._storageKey
|
return this._storageKey;
|
||||||
}
|
|
||||||
|
|
||||||
get authKey() {
|
|
||||||
throw new Error('Not supported')
|
|
||||||
}
|
|
||||||
|
|
||||||
set authKey(value) {
|
|
||||||
throw new Error('Not supported')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthKey(dcId = this._dcId) {
|
getAuthKey(dcId = this._dcId) {
|
||||||
return this._authKeys[dcId]
|
return this._authKeys[dcId];
|
||||||
}
|
}
|
||||||
|
|
||||||
setAuthKey(authKey, dcId = this._dcId) {
|
setAuthKey(authKey, dcId = this._dcId) {
|
||||||
this._authKeys[dcId] = authKey
|
this._authKeys[dcId] = authKey;
|
||||||
|
|
||||||
void this._updateStorage()
|
void this._updateStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateStorage() {
|
async _updateStorage() {
|
||||||
if (!this._storageKey) {
|
if (!this._storageKey) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionData = {
|
const sessionData = {
|
||||||
mainDcId: this._dcId,
|
mainDcId: this._dcId,
|
||||||
keys: {},
|
keys: {},
|
||||||
hashes: {}
|
hashes: {},
|
||||||
}
|
};
|
||||||
|
|
||||||
Object.keys(this._authKeys).map((dcId) => {
|
Object.keys(this._authKeys)
|
||||||
const authKey = this._authKeys[dcId]
|
.map((dcId) => {
|
||||||
sessionData.keys[dcId] = authKey._key
|
const authKey = this._authKeys[dcId];
|
||||||
sessionData.hashes[dcId] = authKey._hash
|
sessionData.keys[dcId] = authKey._key;
|
||||||
})
|
sessionData.hashes[dcId] = authKey._hash;
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this._saveToCache(JSON.stringify(sessionData))
|
await this._saveToCache(JSON.stringify(sessionData));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to update session in storage')
|
console.warn('Failed to update session in storage');
|
||||||
console.warn(err)
|
console.warn(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete() {
|
async delete() {
|
||||||
try {
|
try {
|
||||||
return await this._delete()
|
return await this._delete();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to delete session from storage')
|
console.warn('Failed to delete session from storage');
|
||||||
console.warn(err)
|
console.warn(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @abstract
|
// @abstract
|
||||||
async _delete() {
|
async _delete() {
|
||||||
throw new Error('Not Implemented')
|
throw new Error('Not Implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
// @abstract
|
// @abstract
|
||||||
async _fetchFromCache() {
|
async _fetchFromCache() {
|
||||||
throw new Error('Not Implemented')
|
throw new Error('Not Implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
// @abstract
|
// @abstract
|
||||||
async _saveToCache(data) {
|
async _saveToCache(data) {
|
||||||
throw new Error('Not Implemented')
|
throw new Error('Not Implemented');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateStorageKey() {
|
function generateStorageKey() {
|
||||||
// Creating two sessions at the same moment is not expected nor supported.
|
// Creating two sessions at the same moment is not expected nor supported.
|
||||||
return `${STORAGE_KEY_BASE}${Date.now()}`
|
return `${STORAGE_KEY_BASE}${Date.now()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = StorageSession
|
module.exports = StorageSession;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
const MemorySession = require('./Memory')
|
const MemorySession = require('./Memory');
|
||||||
const AuthKey = require('../crypto/AuthKey')
|
const AuthKey = require('../crypto/AuthKey');
|
||||||
const BinaryReader = require('../extensions/BinaryReader')
|
const BinaryReader = require('../extensions/BinaryReader');
|
||||||
const CURRENT_VERSION = '1'
|
const CURRENT_VERSION = '1';
|
||||||
|
|
||||||
|
|
||||||
class StringSession extends MemorySession {
|
class StringSession extends MemorySession {
|
||||||
@ -21,22 +21,22 @@ class StringSession extends MemorySession {
|
|||||||
* @param session {string|null}
|
* @param session {string|null}
|
||||||
*/
|
*/
|
||||||
constructor(session = null) {
|
constructor(session = null) {
|
||||||
super()
|
super();
|
||||||
if (session) {
|
if (session) {
|
||||||
if (session[0] !== CURRENT_VERSION) {
|
if (session[0] !== CURRENT_VERSION) {
|
||||||
throw new Error('Not a valid string')
|
throw new Error('Not a valid string');
|
||||||
}
|
}
|
||||||
session = session.slice(1)
|
session = session.slice(1);
|
||||||
const r = StringSession.decode(session)
|
const r = StringSession.decode(session);
|
||||||
const reader = new BinaryReader(r)
|
const reader = new BinaryReader(r);
|
||||||
this._dcId = reader.read(1)
|
this._dcId = reader.read(1)
|
||||||
.readUInt8(0)
|
.readUInt8(0);
|
||||||
const serverAddressLen = reader.read(2)
|
const serverAddressLen = reader.read(2)
|
||||||
.readInt16BE(0)
|
.readInt16BE(0);
|
||||||
this._serverAddress = String(reader.read(serverAddressLen))
|
this._serverAddress = String(reader.read(serverAddressLen));
|
||||||
this._port = reader.read(2)
|
this._port = reader.read(2)
|
||||||
.readInt16BE(0)
|
.readInt16BE(0);
|
||||||
this._key = reader.read(-1)
|
this._key = reader.read(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ class StringSession extends MemorySession {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
static encode(x) {
|
static encode(x) {
|
||||||
return x.toString('base64')
|
return x.toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,26 +53,26 @@ class StringSession extends MemorySession {
|
|||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
*/
|
*/
|
||||||
static decode(x) {
|
static decode(x) {
|
||||||
return Buffer.from(x, 'base64')
|
return Buffer.from(x, 'base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
if (this._key) {
|
if (this._key) {
|
||||||
this._authKey = new AuthKey()
|
this._authKey = new AuthKey();
|
||||||
await this._authKey.setKey(this._key)
|
await this._authKey.setKey(this._key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
if (!this.authKey) {
|
if (!this.authKey) {
|
||||||
return ''
|
return '';
|
||||||
}
|
}
|
||||||
const dcBuffer = Buffer.from([this.dcId])
|
const dcBuffer = Buffer.from([this.dcId]);
|
||||||
const addressBuffer = Buffer.from(this.serverAddress)
|
const addressBuffer = Buffer.from(this.serverAddress);
|
||||||
const addressLengthBuffer = Buffer.alloc(2)
|
const addressLengthBuffer = Buffer.alloc(2);
|
||||||
addressLengthBuffer.writeInt16BE(addressBuffer.length, 0)
|
addressLengthBuffer.writeInt16BE(addressBuffer.length, 0);
|
||||||
const portBuffer = Buffer.alloc(2)
|
const portBuffer = Buffer.alloc(2);
|
||||||
portBuffer.writeInt16BE(this.port, 0)
|
portBuffer.writeInt16BE(this.port, 0);
|
||||||
|
|
||||||
return CURRENT_VERSION + StringSession.encode(Buffer.concat([
|
return CURRENT_VERSION + StringSession.encode(Buffer.concat([
|
||||||
dcBuffer,
|
dcBuffer,
|
||||||
@ -80,26 +80,26 @@ class StringSession extends MemorySession {
|
|||||||
addressBuffer,
|
addressBuffer,
|
||||||
portBuffer,
|
portBuffer,
|
||||||
this.authKey.getKey(),
|
this.authKey.getKey(),
|
||||||
]))
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthKey(dcId) {
|
getAuthKey(dcId) {
|
||||||
if (dcId && dcId !== this.dcId) {
|
if (dcId && dcId !== this.dcId) {
|
||||||
// Not supported.
|
// Not supported.
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.authKey
|
return this.authKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAuthKey(authKey, dcId) {
|
setAuthKey(authKey, dcId) {
|
||||||
if (dcId && dcId !== this.dcId) {
|
if (dcId && dcId !== this.dcId) {
|
||||||
// Not supported.
|
// Not supported.
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.authKey = authKey
|
this.authKey = authKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = StringSession
|
module.exports = StringSession;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
const Memory = require('./Memory')
|
const Memory = require('./Memory');
|
||||||
const StringSession = require('./StringSession')
|
const StringSession = require('./StringSession');
|
||||||
const CacheApiSession = require('./CacheApiSession')
|
const CacheApiSession = require('./CacheApiSession');
|
||||||
const LocalStorageSession = require('./LocalStorageSession')
|
const LocalStorageSession = require('./LocalStorageSession');
|
||||||
const IdbSession = require('./IdbSession')
|
const IdbSession = require('./IdbSession');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Memory,
|
Memory,
|
||||||
@ -10,4 +10,4 @@ module.exports = {
|
|||||||
CacheApiSession,
|
CacheApiSession,
|
||||||
LocalStorageSession,
|
LocalStorageSession,
|
||||||
IdbSession,
|
IdbSession,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
const api = require('./api')
|
const api = require('./api');
|
||||||
const LAYER = 121
|
const LAYER = 121;
|
||||||
const tlobjects = {}
|
const tlobjects = {};
|
||||||
|
|
||||||
|
|
||||||
for (const tl of Object.values(api)) {
|
for (const tl of Object.values(api)) {
|
||||||
if (tl.CONSTRUCTOR_ID) {
|
if (tl.CONSTRUCTOR_ID) {
|
||||||
tlobjects[tl.CONSTRUCTOR_ID] = tl
|
tlobjects[tl.CONSTRUCTOR_ID] = tl;
|
||||||
} else {
|
} else {
|
||||||
for (const sub of Object.values(tl)) {
|
for (const sub of Object.values(tl)) {
|
||||||
tlobjects[sub.CONSTRUCTOR_ID] = sub
|
tlobjects[sub.CONSTRUCTOR_ID] = sub;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
LAYER,
|
LAYER,
|
||||||
tlobjects
|
tlobjects,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,42 +1,44 @@
|
|||||||
class MTProtoRequest {
|
class MTProtoRequest {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.sent = false
|
this.sent = false;
|
||||||
this.msgId = 0 // long
|
this.msgId = 0; // long
|
||||||
this.sequence = 0
|
this.sequence = 0;
|
||||||
|
|
||||||
this.dirty = false
|
this.dirty = false;
|
||||||
this.sendTime = 0
|
this.sendTime = 0;
|
||||||
this.confirmReceived = false
|
this.confirmReceived = false;
|
||||||
|
|
||||||
// These should be overrode
|
// These should be overrode
|
||||||
|
|
||||||
this.constructorId = 0
|
this.constructorId = 0;
|
||||||
this.confirmed = false
|
this.confirmed = false;
|
||||||
this.responded = false
|
this.responded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// these should not be overrode
|
// these should not be overrode
|
||||||
onSendSuccess() {
|
onSendSuccess() {
|
||||||
this.sendTime = new Date().getTime()
|
this.sendTime = new Date().getTime();
|
||||||
this.sent = true
|
this.sent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onConfirm() {
|
onConfirm() {
|
||||||
this.confirmReceived = true
|
this.confirmReceived = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
needResend() {
|
needResend() {
|
||||||
return this.dirty || (this.confirmed && !this.confirmReceived && new Date().getTime() - this.sendTime > 3000)
|
return this.dirty || (this.confirmed && !this.confirmReceived && new Date().getTime() - this.sendTime > 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// These should be overrode
|
// These should be overrode
|
||||||
onSend() {
|
onSend() {
|
||||||
throw Error('Not overload ' + this.constructor.name)
|
throw Error('Not overload ' + this.constructor.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
onResponse(buffer) {}
|
onResponse(buffer) {
|
||||||
|
}
|
||||||
|
|
||||||
onException(exception) {}
|
onException(exception) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MTProtoRequest
|
module.exports = MTProtoRequest;
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
const {
|
const {
|
||||||
parseTl,
|
parseTl,
|
||||||
serializeBytes,
|
serializeBytes,
|
||||||
serializeDate
|
serializeDate,
|
||||||
} = require('./generationHelpers')
|
} = require('./generationHelpers');
|
||||||
const { readBufferFromBigInt,toSignedLittleBuffer } = require('../Helpers')
|
const {
|
||||||
|
readBufferFromBigInt,
|
||||||
|
toSignedLittleBuffer,
|
||||||
|
} = require('../Helpers');
|
||||||
|
|
||||||
const tlContent = require('./apiTl.js')
|
const tlContent = require('./apiTl.js');
|
||||||
const schemeContent = require('./schemaTl.js')
|
const schemeContent = require('./schemaTl.js');
|
||||||
|
|
||||||
/*CONTEST
|
/*CONTEST
|
||||||
const NAMED_AUTO_CASTS = new Set([
|
const NAMED_AUTO_CASTS = new Set([
|
||||||
@ -29,98 +32,103 @@ const AUTO_CASTS = new Set([
|
|||||||
])
|
])
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const CACHING_SUPPORTED = typeof self !== 'undefined' && self.localStorage !== undefined
|
const CACHING_SUPPORTED = typeof self !== 'undefined' && self.localStorage !== undefined;
|
||||||
|
|
||||||
const CACHE_KEY = 'GramJs:apiCache'
|
const CACHE_KEY = 'GramJs:apiCache';
|
||||||
|
|
||||||
function buildApiFromTlSchema() {
|
function buildApiFromTlSchema() {
|
||||||
let definitions;
|
let definitions;
|
||||||
const fromCache = CACHING_SUPPORTED && loadFromCache()
|
const fromCache = CACHING_SUPPORTED && loadFromCache();
|
||||||
|
|
||||||
if (fromCache) {
|
if (fromCache) {
|
||||||
definitions = fromCache
|
definitions = fromCache;
|
||||||
} else {
|
} else {
|
||||||
definitions = loadFromTlSchemas()
|
definitions = loadFromTlSchemas();
|
||||||
|
|
||||||
if (CACHING_SUPPORTED) {
|
if (CACHING_SUPPORTED) {
|
||||||
localStorage.setItem(CACHE_KEY, JSON.stringify(definitions))
|
localStorage.setItem(CACHE_KEY, JSON.stringify(definitions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mergeWithNamespaces(
|
return mergeWithNamespaces(
|
||||||
createClasses('constructor', definitions.constructors),
|
createClasses('constructor', definitions.constructors),
|
||||||
createClasses('request', definitions.requests)
|
createClasses('request', definitions.requests),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFromCache() {
|
function loadFromCache() {
|
||||||
const jsonCache = localStorage.getItem(CACHE_KEY)
|
const jsonCache = localStorage.getItem(CACHE_KEY);
|
||||||
return jsonCache && JSON.parse(jsonCache)
|
return jsonCache && JSON.parse(jsonCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFromTlSchemas() {
|
function loadFromTlSchemas() {
|
||||||
const [constructorParamsApi, functionParamsApi] = extractParams(tlContent)
|
const [constructorParamsApi, functionParamsApi] = extractParams(tlContent);
|
||||||
const [constructorParamsSchema, functionParamsSchema] = extractParams(schemeContent)
|
const [constructorParamsSchema, functionParamsSchema] = extractParams(schemeContent);
|
||||||
const constructors = [].concat(constructorParamsApi, constructorParamsSchema)
|
const constructors = [].concat(constructorParamsApi, constructorParamsSchema);
|
||||||
const requests = [].concat(functionParamsApi, functionParamsSchema)
|
const requests = [].concat(functionParamsApi, functionParamsSchema);
|
||||||
|
|
||||||
return { constructors, requests }
|
return {
|
||||||
|
constructors,
|
||||||
|
requests,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeWithNamespaces(obj1, obj2) {
|
function mergeWithNamespaces(obj1, obj2) {
|
||||||
const result = { ...obj1 }
|
const result = { ...obj1 };
|
||||||
|
|
||||||
Object.keys(obj2).forEach((key) => {
|
Object.keys(obj2)
|
||||||
if (typeof obj2[key] === 'function' || !result[key]) {
|
.forEach((key) => {
|
||||||
result[key] = obj2[key]
|
if (typeof obj2[key] === 'function' || !result[key]) {
|
||||||
} else {
|
result[key] = obj2[key];
|
||||||
Object.assign(result[key], obj2[key])
|
} else {
|
||||||
}
|
Object.assign(result[key], obj2[key]);
|
||||||
})
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractParams(fileContent) {
|
function extractParams(fileContent) {
|
||||||
const f = parseTl(fileContent, 109)
|
const f = parseTl(fileContent, 109);
|
||||||
const constructors = []
|
const constructors = [];
|
||||||
const functions = []
|
const functions = [];
|
||||||
for (const d of f) {
|
for (const d of f) {
|
||||||
d.isFunction ? functions.push(d) : constructors.push(d)
|
d.isFunction ? functions.push(d) : constructors.push(d);
|
||||||
}
|
}
|
||||||
return [constructors, functions]
|
return [constructors, functions];
|
||||||
}
|
}
|
||||||
|
|
||||||
function argToBytes(x, type) {
|
function argToBytes(x, type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'int':
|
case 'int':
|
||||||
const i = Buffer.alloc(4)
|
const i = Buffer.alloc(4);
|
||||||
i.writeInt32LE(x, 0)
|
i.writeInt32LE(x, 0);
|
||||||
return i
|
return i;
|
||||||
case 'long':
|
case 'long':
|
||||||
return toSignedLittleBuffer(x, 8)
|
return toSignedLittleBuffer(x, 8);
|
||||||
case 'int128':
|
case 'int128':
|
||||||
return toSignedLittleBuffer(x, 16)
|
return toSignedLittleBuffer(x, 16);
|
||||||
case 'int256':
|
case 'int256':
|
||||||
return toSignedLittleBuffer(x, 32)
|
return toSignedLittleBuffer(x, 32);
|
||||||
case 'double':
|
case 'double':
|
||||||
const d = Buffer.alloc(8)
|
const d = Buffer.alloc(8);
|
||||||
d.writeDoubleLE(x, 0)
|
d.writeDoubleLE(x, 0);
|
||||||
return d
|
return d;
|
||||||
case 'string':
|
case 'string':
|
||||||
return serializeBytes(x)
|
return serializeBytes(x);
|
||||||
case 'Bool':
|
case 'Bool':
|
||||||
return x ? Buffer.from('b5757299', 'hex') : Buffer.from('379779bc', 'hex')
|
return x ? Buffer.from('b5757299', 'hex') : Buffer.from('379779bc', 'hex');
|
||||||
case 'true':
|
case 'true':
|
||||||
return Buffer.alloc(0)
|
return Buffer.alloc(0);
|
||||||
case 'bytes':
|
case 'bytes':
|
||||||
return serializeBytes(x)
|
return serializeBytes(x);
|
||||||
case 'date':
|
case 'date':
|
||||||
return serializeDate(x)
|
return serializeDate(x);
|
||||||
default:
|
default:
|
||||||
return x.getBytes()
|
return x.getBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
CONTEST
|
CONTEST
|
||||||
async function getInputFromResolve(utils, client, peer, peerType) {
|
async function getInputFromResolve(utils, client, peer, peerType) {
|
||||||
@ -155,181 +163,189 @@ async function getInputFromResolve(utils, client, peer, peerType) {
|
|||||||
function getArgFromReader(reader, arg) {
|
function getArgFromReader(reader, arg) {
|
||||||
if (arg.isVector) {
|
if (arg.isVector) {
|
||||||
if (arg.useVectorId) {
|
if (arg.useVectorId) {
|
||||||
reader.readInt()
|
reader.readInt();
|
||||||
}
|
}
|
||||||
const temp = []
|
const temp = [];
|
||||||
const len = reader.readInt()
|
const len = reader.readInt();
|
||||||
arg.isVector = false
|
arg.isVector = false;
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
temp.push(getArgFromReader(reader, arg))
|
temp.push(getArgFromReader(reader, arg));
|
||||||
}
|
}
|
||||||
arg.isVector = true
|
arg.isVector = true;
|
||||||
return temp
|
return temp;
|
||||||
} else if (arg.flagIndicator) {
|
} else if (arg.flagIndicator) {
|
||||||
return reader.readInt()
|
return reader.readInt();
|
||||||
} else {
|
} else {
|
||||||
switch (arg.type) {
|
switch (arg.type) {
|
||||||
case 'int':
|
case 'int':
|
||||||
return reader.readInt()
|
return reader.readInt();
|
||||||
case 'long':
|
case 'long':
|
||||||
return reader.readLong()
|
return reader.readLong();
|
||||||
case 'int128':
|
case 'int128':
|
||||||
return reader.readLargeInt(128)
|
return reader.readLargeInt(128);
|
||||||
case 'int256':
|
case 'int256':
|
||||||
return reader.readLargeInt(256)
|
return reader.readLargeInt(256);
|
||||||
case 'double':
|
case 'double':
|
||||||
return reader.readDouble()
|
return reader.readDouble();
|
||||||
case 'string':
|
case 'string':
|
||||||
return reader.tgReadString()
|
return reader.tgReadString();
|
||||||
case 'Bool':
|
case 'Bool':
|
||||||
return reader.tgReadBool()
|
return reader.tgReadBool();
|
||||||
case 'true':
|
case 'true':
|
||||||
return true
|
return true;
|
||||||
case 'bytes':
|
case 'bytes':
|
||||||
return reader.tgReadBytes()
|
return reader.tgReadBytes();
|
||||||
case 'date':
|
case 'date':
|
||||||
return reader.tgReadDate()
|
return reader.tgReadDate();
|
||||||
default:
|
default:
|
||||||
if (!arg.skipConstructorId) {
|
if (!arg.skipConstructorId) {
|
||||||
return reader.tgReadObject()
|
return reader.tgReadObject();
|
||||||
} else {
|
} else {
|
||||||
return api.constructors[arg.type].fromReader(reader)
|
return api.constructors[arg.type].fromReader(reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createClasses(classesType, params) {
|
function createClasses(classesType, params) {
|
||||||
const classes = {}
|
const classes = {};
|
||||||
for (const classParams of params) {
|
for (const classParams of params) {
|
||||||
const { name, constructorId, subclassOfId, argsConfig, namespace, result } = classParams
|
const {
|
||||||
const fullName = [namespace, name].join('.').replace(/^\./, '')
|
name,
|
||||||
|
constructorId,
|
||||||
|
subclassOfId,
|
||||||
|
argsConfig,
|
||||||
|
namespace,
|
||||||
|
result,
|
||||||
|
} = classParams;
|
||||||
|
const fullName = [namespace, name].join('.')
|
||||||
|
.replace(/^\./, '');
|
||||||
|
|
||||||
class VirtualClass {
|
class VirtualClass {
|
||||||
static CONSTRUCTOR_ID = constructorId
|
static CONSTRUCTOR_ID = constructorId;
|
||||||
static SUBCLASS_OF_ID = subclassOfId
|
static SUBCLASS_OF_ID = subclassOfId;
|
||||||
static className = fullName
|
static className = fullName;
|
||||||
static classType = classesType
|
static classType = classesType;
|
||||||
|
|
||||||
CONSTRUCTOR_ID = constructorId
|
CONSTRUCTOR_ID = constructorId;
|
||||||
SUBCLASS_OF_ID = subclassOfId
|
SUBCLASS_OF_ID = subclassOfId;
|
||||||
className = fullName
|
className = fullName;
|
||||||
classType = classesType
|
classType = classesType;
|
||||||
|
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
args = args || {}
|
args = args || {};
|
||||||
Object.keys(args)
|
Object.keys(args)
|
||||||
.forEach((argName) => {
|
.forEach((argName) => {
|
||||||
this[argName] = args[argName]
|
this[argName] = args[argName];
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromReader(reader) {
|
static fromReader(reader) {
|
||||||
|
|
||||||
const args = {}
|
const args = {};
|
||||||
|
|
||||||
for (const argName in argsConfig) {
|
for (const argName in argsConfig) {
|
||||||
if (argsConfig.hasOwnProperty(argName)) {
|
if (argsConfig.hasOwnProperty(argName)) {
|
||||||
const arg = argsConfig[argName]
|
const arg = argsConfig[argName];
|
||||||
if (arg.isFlag) {
|
if (arg.isFlag) {
|
||||||
if (arg.type === 'true') {
|
if (arg.type === 'true') {
|
||||||
args[argName] = Boolean(args['flags'] & 1 << arg.flagIndex)
|
args[argName] = Boolean(args['flags'] & 1 << arg.flagIndex);
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
if (args['flags'] & 1 << arg.flagIndex) {
|
if (args['flags'] & 1 << arg.flagIndex) {
|
||||||
args[argName] = getArgFromReader(reader, arg)
|
args[argName] = getArgFromReader(reader, arg);
|
||||||
} else {
|
} else {
|
||||||
args[argName] = null
|
args[argName] = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (arg.flagIndicator) {
|
if (arg.flagIndicator) {
|
||||||
arg.name = 'flags'
|
arg.name = 'flags';
|
||||||
}
|
}
|
||||||
args[argName] = getArgFromReader(reader, arg)
|
args[argName] = getArgFromReader(reader, arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new VirtualClass(args)
|
return new VirtualClass(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBytes() {
|
getBytes() {
|
||||||
// The next is pseudo-code:
|
// The next is pseudo-code:
|
||||||
const idForBytes = this.CONSTRUCTOR_ID
|
const idForBytes = this.CONSTRUCTOR_ID;
|
||||||
const c = Buffer.alloc(4)
|
const c = Buffer.alloc(4);
|
||||||
c.writeUInt32LE(idForBytes, 0)
|
c.writeUInt32LE(idForBytes, 0);
|
||||||
const buffers = [c]
|
const buffers = [c];
|
||||||
for (const arg in argsConfig) {
|
for (const arg in argsConfig) {
|
||||||
if (argsConfig.hasOwnProperty(arg)) {
|
if (argsConfig.hasOwnProperty(arg)) {
|
||||||
if (argsConfig[arg].isFlag) {
|
if (argsConfig[arg].isFlag) {
|
||||||
if ((this[arg]===false&&argsConfig[arg].type==="true") || this[arg]===null || this[arg]===undefined){
|
if ((this[arg] === false && argsConfig[arg].type === 'true') || this[arg] === null || this[arg] === undefined) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (argsConfig[arg].isVector) {
|
if (argsConfig[arg].isVector) {
|
||||||
if (argsConfig[arg].useVectorId) {
|
if (argsConfig[arg].useVectorId) {
|
||||||
buffers.push(Buffer.from('15c4b51c', 'hex'))
|
buffers.push(Buffer.from('15c4b51c', 'hex'));
|
||||||
}
|
}
|
||||||
const l = Buffer.alloc(4)
|
const l = Buffer.alloc(4);
|
||||||
l.writeInt32LE(this[arg].length, 0)
|
l.writeInt32LE(this[arg].length, 0);
|
||||||
buffers.push(l, Buffer.concat(this[arg].map(x => argToBytes(x, argsConfig[arg].type))))
|
buffers.push(l, Buffer.concat(this[arg].map(x => argToBytes(x, argsConfig[arg].type))));
|
||||||
} else if (argsConfig[arg].flagIndicator) {
|
} else if (argsConfig[arg].flagIndicator) {
|
||||||
if (!Object.values(argsConfig)
|
if (!Object.values(argsConfig)
|
||||||
.some((f) => f.isFlag)) {
|
.some((f) => f.isFlag)) {
|
||||||
buffers.push(Buffer.alloc(4))
|
buffers.push(Buffer.alloc(4));
|
||||||
} else {
|
} else {
|
||||||
let flagCalculate = 0
|
let flagCalculate = 0;
|
||||||
for (const f in argsConfig) {
|
for (const f in argsConfig) {
|
||||||
if (argsConfig[f].isFlag) {
|
if (argsConfig[f].isFlag) {
|
||||||
if ((this[f]===false&&argsConfig[f].type==="true") || this[f]===undefined || this[f]===null) {
|
if ((this[f] === false && argsConfig[f].type === 'true') || this[f] === undefined || this[f] === null) {
|
||||||
flagCalculate |= 0
|
flagCalculate |= 0;
|
||||||
} else {
|
} else {
|
||||||
flagCalculate |= 1 << argsConfig[f].flagIndex
|
flagCalculate |= 1 << argsConfig[f].flagIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const f = Buffer.alloc(4)
|
const f = Buffer.alloc(4);
|
||||||
f.writeUInt32LE(flagCalculate, 0)
|
f.writeUInt32LE(flagCalculate, 0);
|
||||||
buffers.push(f)
|
buffers.push(f);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
buffers.push(argToBytes(this[arg], argsConfig[arg].type))
|
buffers.push(argToBytes(this[arg], argsConfig[arg].type));
|
||||||
|
|
||||||
if (this[arg] && typeof this[arg].getBytes === 'function') {
|
if (this[arg] && typeof this[arg].getBytes === 'function') {
|
||||||
let boxed = (argsConfig[arg].type.charAt(argsConfig[arg].type.indexOf('.') + 1))
|
let boxed = (argsConfig[arg].type.charAt(argsConfig[arg].type.indexOf('.') + 1));
|
||||||
boxed = boxed === boxed.toUpperCase()
|
boxed = boxed === boxed.toUpperCase();
|
||||||
if (!boxed) {
|
if (!boxed) {
|
||||||
buffers.shift()
|
buffers.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return Buffer.concat(buffers)
|
return Buffer.concat(buffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
readResult(reader) {
|
readResult(reader) {
|
||||||
if (classesType !== 'request') {
|
if (classesType !== 'request') {
|
||||||
throw new Error('`readResult()` called for non-request instance')
|
throw new Error('`readResult()` called for non-request instance');
|
||||||
}
|
}
|
||||||
|
|
||||||
const m = result.match(/Vector<(int|long)>/)
|
const m = result.match(/Vector<(int|long)>/);
|
||||||
if (m) {
|
if (m) {
|
||||||
reader.readInt()
|
reader.readInt();
|
||||||
let temp = []
|
let temp = [];
|
||||||
let len = reader.readInt()
|
let len = reader.readInt();
|
||||||
if (m[1] === 'int') {
|
if (m[1] === 'int') {
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
temp.push(reader.readInt())
|
temp.push(reader.readInt());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
temp.push(reader.readLong())
|
temp.push(reader.readLong());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return temp
|
return temp;
|
||||||
} else {
|
} else {
|
||||||
return reader.tgReadObject()
|
return reader.tgReadObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,16 +384,16 @@ function createClasses(classesType, params) {
|
|||||||
|
|
||||||
if (namespace) {
|
if (namespace) {
|
||||||
if (!classes[namespace]) {
|
if (!classes[namespace]) {
|
||||||
classes[namespace] = {}
|
classes[namespace] = {};
|
||||||
}
|
}
|
||||||
classes[namespace][name] = VirtualClass
|
classes[namespace][name] = VirtualClass;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
classes[name] = VirtualClass
|
classes[name] = VirtualClass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return classes
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = buildApiFromTlSchema()
|
module.exports = buildApiFromTlSchema();
|
||||||
|
|||||||
@ -1,57 +1,58 @@
|
|||||||
const { serializeBytes } = require('../index')
|
const { serializeBytes } = require('../index');
|
||||||
const { inflate } = require('pako/dist/pako_inflate')
|
const { inflate } = require('pako/dist/pako_inflate');
|
||||||
|
|
||||||
//CONTEST const { deflate } = require('pako/dist/pako_deflate')
|
//CONTEST const { deflate } = require('pako/dist/pako_deflate')
|
||||||
|
|
||||||
class GZIPPacked {
|
class GZIPPacked {
|
||||||
static CONSTRUCTOR_ID = 0x3072cfa1
|
static CONSTRUCTOR_ID = 0x3072cfa1;
|
||||||
static classType = 'constructor'
|
static classType = 'constructor';
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.data = data
|
this.data = data;
|
||||||
this.CONSTRUCTOR_ID = 0x3072cfa1
|
this.CONSTRUCTOR_ID = 0x3072cfa1;
|
||||||
this.classType = 'constructor'
|
this.classType = 'constructor';
|
||||||
}
|
}
|
||||||
|
|
||||||
static async gzipIfSmaller(contentRelated, data) {
|
static async gzipIfSmaller(contentRelated, data) {
|
||||||
if (contentRelated && data.length > 512) {
|
if (contentRelated && data.length > 512) {
|
||||||
const gzipped = await (new GZIPPacked(data)).toBytes()
|
const gzipped = await (new GZIPPacked(data)).toBytes();
|
||||||
if (gzipped.length < data.length) {
|
if (gzipped.length < data.length) {
|
||||||
return gzipped
|
return gzipped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gzip(input) {
|
static gzip(input) {
|
||||||
return Buffer.from(input)
|
return Buffer.from(input);
|
||||||
// TODO this usually makes it faster for large requests
|
// TODO this usually makes it faster for large requests
|
||||||
//return Buffer.from(deflate(input, { level: 9, gzip: true }))
|
//return Buffer.from(deflate(input, { level: 9, gzip: true }))
|
||||||
}
|
}
|
||||||
|
|
||||||
static ungzip(input) {
|
static ungzip(input) {
|
||||||
return Buffer.from(inflate(input))
|
return Buffer.from(inflate(input));
|
||||||
}
|
|
||||||
|
|
||||||
async toBytes() {
|
|
||||||
const g = Buffer.alloc(4)
|
|
||||||
g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0)
|
|
||||||
return Buffer.concat([
|
|
||||||
g,
|
|
||||||
serializeBytes(await GZIPPacked.gzip(this.data)),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async read(reader) {
|
static async read(reader) {
|
||||||
const constructor = reader.readInt(false)
|
const constructor = reader.readInt(false);
|
||||||
if (constructor !== GZIPPacked.CONSTRUCTOR_ID) {
|
if (constructor !== GZIPPacked.CONSTRUCTOR_ID) {
|
||||||
throw new Error('not equal')
|
throw new Error('not equal');
|
||||||
}
|
}
|
||||||
return await GZIPPacked.gzip(reader.tgReadBytes())
|
return await GZIPPacked.gzip(reader.tgReadBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromReader(reader) {
|
static async fromReader(reader) {
|
||||||
return new GZIPPacked(await GZIPPacked.ungzip(reader.tgReadBytes()))
|
return new GZIPPacked(await GZIPPacked.ungzip(reader.tgReadBytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
async toBytes() {
|
||||||
|
const g = Buffer.alloc(4);
|
||||||
|
g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0);
|
||||||
|
return Buffer.concat([
|
||||||
|
g,
|
||||||
|
serializeBytes(await GZIPPacked.gzip(this.data)),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = GZIPPacked
|
module.exports = GZIPPacked;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
const TLMessage = require('./TLMessage')
|
const TLMessage = require('./TLMessage');
|
||||||
|
|
||||||
class MessageContainer {
|
class MessageContainer {
|
||||||
static CONSTRUCTOR_ID = 0x73f1f8dc;
|
static CONSTRUCTOR_ID = 0x73f1f8dc;
|
||||||
static classType = "constructor"
|
static classType = 'constructor';
|
||||||
// Maximum size in bytes for the inner payload of the container.
|
// Maximum size in bytes for the inner payload of the container.
|
||||||
// Telegram will close the connection if the payload is bigger.
|
// Telegram will close the connection if the payload is bigger.
|
||||||
// The overhead of the container itself is subtracted.
|
// The overhead of the container itself is subtracted.
|
||||||
@ -20,26 +20,26 @@ class MessageContainer {
|
|||||||
|
|
||||||
constructor(messages) {
|
constructor(messages) {
|
||||||
|
|
||||||
this.CONSTRUCTOR_ID = 0x73f1f8dc
|
this.CONSTRUCTOR_ID = 0x73f1f8dc;
|
||||||
this.messages = messages
|
this.messages = messages;
|
||||||
this.classType = "constructor"
|
this.classType = 'constructor';
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromReader(reader) {
|
static async fromReader(reader) {
|
||||||
const messages = []
|
const messages = [];
|
||||||
const length = reader.readInt()
|
const length = reader.readInt();
|
||||||
for (let x = 0; x < length; x++) {
|
for (let x = 0; x < length; x++) {
|
||||||
const msgId = reader.readLong()
|
const msgId = reader.readLong();
|
||||||
const seqNo = reader.readInt()
|
const seqNo = reader.readInt();
|
||||||
const length = reader.readInt()
|
const length = reader.readInt();
|
||||||
const before = reader.tellPosition()
|
const before = reader.tellPosition();
|
||||||
const obj = reader.tgReadObject()
|
const obj = reader.tgReadObject();
|
||||||
reader.setPosition(before + length)
|
reader.setPosition(before + length);
|
||||||
const tlMessage = new TLMessage(msgId, seqNo, obj)
|
const tlMessage = new TLMessage(msgId, seqNo, obj);
|
||||||
messages.push(tlMessage)
|
messages.push(tlMessage);
|
||||||
}
|
}
|
||||||
return new MessageContainer(messages)
|
return new MessageContainer(messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MessageContainer
|
module.exports = MessageContainer;
|
||||||
|
|||||||
@ -1,33 +1,33 @@
|
|||||||
const { RpcError } = require('../index').constructors
|
const { RpcError } = require('../index').constructors;
|
||||||
const GZIPPacked = require('./GZIPPacked')
|
const GZIPPacked = require('./GZIPPacked');
|
||||||
|
|
||||||
class RPCResult {
|
class RPCResult {
|
||||||
static CONSTRUCTOR_ID = 0xf35c6d01;
|
static CONSTRUCTOR_ID = 0xf35c6d01;
|
||||||
static classType = "constructor"
|
static classType = 'constructor';
|
||||||
|
|
||||||
constructor(reqMsgId, body, error) {
|
constructor(reqMsgId, body, error) {
|
||||||
this.CONSTRUCTOR_ID = 0xf35c6d01
|
this.CONSTRUCTOR_ID = 0xf35c6d01;
|
||||||
this.reqMsgId = reqMsgId
|
this.reqMsgId = reqMsgId;
|
||||||
this.body = body
|
this.body = body;
|
||||||
this.error = error
|
this.error = error;
|
||||||
this.classType = "constructor"
|
this.classType = 'constructor';
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromReader(reader) {
|
static async fromReader(reader) {
|
||||||
const msgId = reader.readLong()
|
const msgId = reader.readLong();
|
||||||
const innerCode = reader.readInt(false)
|
const innerCode = reader.readInt(false);
|
||||||
if (innerCode === RpcError.CONSTRUCTOR_ID) {
|
if (innerCode === RpcError.CONSTRUCTOR_ID) {
|
||||||
return new RPCResult(msgId, null, RpcError.fromReader(reader))
|
return new RPCResult(msgId, null, RpcError.fromReader(reader));
|
||||||
}
|
}
|
||||||
if (innerCode === GZIPPacked.CONSTRUCTOR_ID) {
|
if (innerCode === GZIPPacked.CONSTRUCTOR_ID) {
|
||||||
return new RPCResult(msgId, (await GZIPPacked.fromReader(reader)).data)
|
return new RPCResult(msgId, (await GZIPPacked.fromReader(reader)).data);
|
||||||
}
|
}
|
||||||
reader.seek(-4)
|
reader.seek(-4);
|
||||||
// This reader.read() will read more than necessary, but it's okay.
|
// This reader.read() will read more than necessary, but it's okay.
|
||||||
// We could make use of MessageContainer's length here, but since
|
// We could make use of MessageContainer's length here, but since
|
||||||
// it's not necessary we don't need to care about it.
|
// it's not necessary we don't need to care about it.
|
||||||
return new RPCResult(msgId, reader.read(), null)
|
return new RPCResult(msgId, reader.read(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = RPCResult
|
module.exports = RPCResult;
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
|
|
||||||
class TLMessage {
|
class TLMessage {
|
||||||
static SIZE_OVERHEAD = 12;
|
static SIZE_OVERHEAD = 12;
|
||||||
static classType = "constructor"
|
static classType = 'constructor';
|
||||||
|
|
||||||
constructor(msgId, seqNo, obj) {
|
constructor(msgId, seqNo, obj) {
|
||||||
this.msgId = msgId
|
this.msgId = msgId;
|
||||||
this.seqNo = seqNo
|
this.seqNo = seqNo;
|
||||||
this.obj = obj
|
this.obj = obj;
|
||||||
this.classType = "constructor"
|
this.classType = 'constructor';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TLMessage
|
module.exports = TLMessage;
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
const TLMessage = require('./TLMessage')
|
const TLMessage = require('./TLMessage');
|
||||||
const RPCResult = require('./RPCResult')
|
const RPCResult = require('./RPCResult');
|
||||||
const MessageContainer = require('./MessageContainer')
|
const MessageContainer = require('./MessageContainer');
|
||||||
const GZIPPacked = require('./GZIPPacked')
|
const GZIPPacked = require('./GZIPPacked');
|
||||||
const coreObjects = {
|
const coreObjects = {
|
||||||
[RPCResult.CONSTRUCTOR_ID]: RPCResult,
|
[RPCResult.CONSTRUCTOR_ID]: RPCResult,
|
||||||
[GZIPPacked.CONSTRUCTOR_ID]: GZIPPacked,
|
[GZIPPacked.CONSTRUCTOR_ID]: GZIPPacked,
|
||||||
[MessageContainer.CONSTRUCTOR_ID]: MessageContainer,
|
[MessageContainer.CONSTRUCTOR_ID]: MessageContainer,
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
TLMessage,
|
TLMessage,
|
||||||
@ -14,4 +14,4 @@ module.exports = {
|
|||||||
MessageContainer,
|
MessageContainer,
|
||||||
GZIPPacked,
|
GZIPPacked,
|
||||||
coreObjects,
|
coreObjects,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
const snakeToCamelCase = (name) => {
|
const snakeToCamelCase = (name) => {
|
||||||
const result = name.replace(/(?:^|_)([a-z])/g, (_, g) => g.toUpperCase())
|
const result = name.replace(/(?:^|_)([a-z])/g, (_, g) => g.toUpperCase());
|
||||||
return result.replace(/_/g, '')
|
return result.replace(/_/g, '');
|
||||||
}
|
};
|
||||||
const variableSnakeToCamelCase = (str) => str.replace(
|
const variableSnakeToCamelCase = (str) => str.replace(
|
||||||
/([-_][a-z])/g,
|
/([-_][a-z])/g,
|
||||||
(group) => group.toUpperCase()
|
(group) => group.toUpperCase()
|
||||||
.replace('-', '')
|
.replace('-', '')
|
||||||
.replace('_', '')
|
.replace('_', ''),
|
||||||
)
|
);
|
||||||
|
|
||||||
const CORE_TYPES = new Set([
|
const CORE_TYPES = new Set([
|
||||||
0xbc799737, // boolFalse#bc799737 = Bool;
|
0xbc799737, // boolFalse#bc799737 = Bool;
|
||||||
0x997275b5, // boolTrue#997275b5 = Bool;
|
0x997275b5, // boolTrue#997275b5 = Bool;
|
||||||
0x3fedd339, // true#3fedd339 = True;
|
0x3fedd339, // true#3fedd339 = True;
|
||||||
0xc4b9f9bb, // error#c4b9f9bb code:int text:string = Error;
|
0xc4b9f9bb, // error#c4b9f9bb code:int text:string = Error;
|
||||||
0x56730bcc // null#56730bcc = Null;
|
0x56730bcc, // null#56730bcc = Null;
|
||||||
])
|
]);
|
||||||
const AUTH_KEY_TYPES = new Set([
|
const AUTH_KEY_TYPES = new Set([
|
||||||
0x05162463, // resPQ,
|
0x05162463, // resPQ,
|
||||||
0x83c95aec, // p_q_inner_data
|
0x83c95aec, // p_q_inner_data
|
||||||
@ -27,49 +27,49 @@ const AUTH_KEY_TYPES = new Set([
|
|||||||
0x6643b654, // client_DH_inner_data
|
0x6643b654, // client_DH_inner_data
|
||||||
0xd712e4be, // req_DH_params
|
0xd712e4be, // req_DH_params
|
||||||
0xf5045f1f, // set_client_DH_params
|
0xf5045f1f, // set_client_DH_params
|
||||||
0x3072cfa1 // gzip_packed
|
0x3072cfa1, // gzip_packed
|
||||||
])
|
]);
|
||||||
|
|
||||||
// This is copy-pasted from `gramjs/Helpers.js` to not depend on TypeScript modules
|
// This is copy-pasted from `gramjs/Helpers.js` to not depend on TypeScript modules
|
||||||
function makeCRCTable() {
|
function makeCRCTable() {
|
||||||
let c
|
let c;
|
||||||
const crcTable = []
|
const crcTable = [];
|
||||||
for (let n = 0; n < 256; n++) {
|
for (let n = 0; n < 256; n++) {
|
||||||
c = n
|
c = n;
|
||||||
for (let k = 0; k < 8; k++) {
|
for (let k = 0; k < 8; k++) {
|
||||||
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1))
|
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
||||||
}
|
}
|
||||||
crcTable[n] = c
|
crcTable[n] = c;
|
||||||
}
|
}
|
||||||
return crcTable
|
return crcTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
let crcTable = null
|
let crcTable = null;
|
||||||
|
|
||||||
function crc32(buf) {
|
function crc32(buf) {
|
||||||
if (!crcTable) {
|
if (!crcTable) {
|
||||||
crcTable = makeCRCTable()
|
crcTable = makeCRCTable();
|
||||||
}
|
}
|
||||||
if (!Buffer.isBuffer(buf)) {
|
if (!Buffer.isBuffer(buf)) {
|
||||||
buf = Buffer.from(buf)
|
buf = Buffer.from(buf);
|
||||||
}
|
}
|
||||||
let crc = -1
|
let crc = -1;
|
||||||
|
|
||||||
for (let index = 0; index < buf.length; index++) {
|
for (let index = 0; index < buf.length; index++) {
|
||||||
const byte = buf[index]
|
const byte = buf[index];
|
||||||
crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8)
|
crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
||||||
}
|
}
|
||||||
return (crc ^ (-1)) >>> 0
|
return (crc ^ (-1)) >>> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromLine = (line, isFunction) => {
|
const fromLine = (line, isFunction) => {
|
||||||
const match = line.match(/([\w.]+)(?:#([0-9a-fA-F]+))?(?:\s{?\w+:[\w\d<>#.?!]+}?)*\s=\s([\w\d<>#.?]+);$/)
|
const match = line.match(/([\w.]+)(?:#([0-9a-fA-F]+))?(?:\s{?\w+:[\w\d<>#.?!]+}?)*\s=\s([\w\d<>#.?]+);$/);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
// Probably "vector#1cb5c415 {t:Type} # [ t ] = Vector t;"
|
// Probably "vector#1cb5c415 {t:Type} # [ t ] = Vector t;"
|
||||||
throw new Error(`Cannot parse TLObject ${line}`)
|
throw new Error(`Cannot parse TLObject ${line}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const argsMatch = findAll(/({)?(\w+):([\w\d<>#.?!]+)}?/, line)
|
const argsMatch = findAll(/({)?(\w+):([\w\d<>#.?!]+)}?/, line);
|
||||||
const currentConfig = {
|
const currentConfig = {
|
||||||
name: match[1],
|
name: match[1],
|
||||||
constructorId: parseInt(match[2], 16),
|
constructorId: parseInt(match[2], 16),
|
||||||
@ -77,24 +77,26 @@ const fromLine = (line, isFunction) => {
|
|||||||
subclassOfId: crc32(match[3]),
|
subclassOfId: crc32(match[3]),
|
||||||
result: match[3],
|
result: match[3],
|
||||||
isFunction: isFunction,
|
isFunction: isFunction,
|
||||||
namespace: null
|
namespace: null,
|
||||||
}
|
};
|
||||||
if (!currentConfig.constructorId) {
|
if (!currentConfig.constructorId) {
|
||||||
|
|
||||||
let hexId = ''
|
let hexId = '';
|
||||||
let args
|
let args;
|
||||||
|
|
||||||
if (Object.values(currentConfig.argsConfig).length) {
|
if (Object.values(currentConfig.argsConfig).length) {
|
||||||
args = ` ${Object.keys(currentConfig.argsConfig).map((arg) => arg.toString()).join(' ')}`
|
args = ` ${Object.keys(currentConfig.argsConfig)
|
||||||
|
.map((arg) => arg.toString())
|
||||||
|
.join(' ')}`;
|
||||||
} else {
|
} else {
|
||||||
args = ''
|
args = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const representation = `${currentConfig.name}${hexId}${args} = ${currentConfig.result}`
|
const representation = `${currentConfig.name}${hexId}${args} = ${currentConfig.result}`
|
||||||
.replace(/(:|\?)bytes /g, '$1string ')
|
.replace(/(:|\?)bytes /g, '$1string ')
|
||||||
.replace(/</g, ' ')
|
.replace(/</g, ' ')
|
||||||
.replace(/>|{|}/g, '')
|
.replace(/>|{|}/g, '')
|
||||||
.replace(/ \w+:flags\.\d+\?true/g, '')
|
.replace(/ \w+:flags\.\d+\?true/g, '');
|
||||||
|
|
||||||
if (currentConfig.name === 'inputMediaInvoice') {
|
if (currentConfig.name === 'inputMediaInvoice') {
|
||||||
// eslint-disable-next-line no-empty
|
// eslint-disable-next-line no-empty
|
||||||
@ -102,17 +104,17 @@ const fromLine = (line, isFunction) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentConfig.constructorId = crc32(Buffer.from(representation, 'utf8'))
|
currentConfig.constructorId = crc32(Buffer.from(representation, 'utf8'));
|
||||||
}
|
}
|
||||||
for (const [brace, name, argType] of argsMatch) {
|
for (const [brace, name, argType] of argsMatch) {
|
||||||
if (brace === undefined) {
|
if (brace === undefined) {
|
||||||
currentConfig.argsConfig[variableSnakeToCamelCase(name)] = buildArgConfig(name, argType)
|
currentConfig.argsConfig[variableSnakeToCamelCase(name)] = buildArgConfig(name, argType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentConfig.name.includes('.')) {
|
if (currentConfig.name.includes('.')) {
|
||||||
[currentConfig.namespace, currentConfig.name] = currentConfig.name.split(/\.(.+)/)
|
[currentConfig.namespace, currentConfig.name] = currentConfig.name.split(/\.(.+)/);
|
||||||
}
|
}
|
||||||
currentConfig.name = snakeToCamelCase(currentConfig.name)
|
currentConfig.name = snakeToCamelCase(currentConfig.name);
|
||||||
/*
|
/*
|
||||||
for (const arg in currentConfig.argsConfig){
|
for (const arg in currentConfig.argsConfig){
|
||||||
if (currentConfig.argsConfig.hasOwnProperty(arg)){
|
if (currentConfig.argsConfig.hasOwnProperty(arg)){
|
||||||
@ -121,11 +123,11 @@ const fromLine = (line, isFunction) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
return currentConfig
|
return currentConfig;
|
||||||
}
|
};
|
||||||
|
|
||||||
function buildArgConfig(name, argType) {
|
function buildArgConfig(name, argType) {
|
||||||
name = name === 'self' ? 'is_self' : name
|
name = name === 'self' ? 'is_self' : name;
|
||||||
// Default values
|
// Default values
|
||||||
const currentConfig = {
|
const currentConfig = {
|
||||||
isVector: false,
|
isVector: false,
|
||||||
@ -134,41 +136,41 @@ function buildArgConfig(name, argType) {
|
|||||||
flagIndex: -1,
|
flagIndex: -1,
|
||||||
flagIndicator: true,
|
flagIndicator: true,
|
||||||
type: null,
|
type: null,
|
||||||
useVectorId: null
|
useVectorId: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
// Special case: some types can be inferred, which makes it
|
// Special case: some types can be inferred, which makes it
|
||||||
// less annoying to type. Currently the only type that can
|
// less annoying to type. Currently the only type that can
|
||||||
// be inferred is if the name is 'random_id', to which a
|
// be inferred is if the name is 'random_id', to which a
|
||||||
// random ID will be assigned if left as None (the default)
|
// random ID will be assigned if left as None (the default)
|
||||||
let canBeInferred = name === 'random_id'
|
let canBeInferred = name === 'random_id';
|
||||||
|
|
||||||
// The type can be an indicator that other arguments will be flags
|
// The type can be an indicator that other arguments will be flags
|
||||||
if (argType !== '#') {
|
if (argType !== '#') {
|
||||||
currentConfig.flagIndicator = false
|
currentConfig.flagIndicator = false;
|
||||||
// Strip the exclamation mark always to have only the name
|
// Strip the exclamation mark always to have only the name
|
||||||
currentConfig.type = argType.replace(/^!+/, '')
|
currentConfig.type = argType.replace(/^!+/, '');
|
||||||
|
|
||||||
// The type may be a flag (flags.IDX?REAL_TYPE)
|
// The type may be a flag (flags.IDX?REAL_TYPE)
|
||||||
// Note that 'flags' is NOT the flags name; this
|
// Note that 'flags' is NOT the flags name; this
|
||||||
// is determined by a previous argument
|
// is determined by a previous argument
|
||||||
// However, we assume that the argument will always be called 'flags'
|
// However, we assume that the argument will always be called 'flags'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const flagMatch = currentConfig.type.match(/flags.(\d+)\?([\w<>.]+)/)
|
const flagMatch = currentConfig.type.match(/flags.(\d+)\?([\w<>.]+)/);
|
||||||
|
|
||||||
if (flagMatch) {
|
if (flagMatch) {
|
||||||
currentConfig.isFlag = true
|
currentConfig.isFlag = true;
|
||||||
currentConfig.flagIndex = Number(flagMatch[1]);
|
currentConfig.flagIndex = Number(flagMatch[1]);
|
||||||
// Update the type to match the exact type, not the "flagged" one
|
// Update the type to match the exact type, not the "flagged" one
|
||||||
[, , currentConfig.type] = flagMatch
|
[, , currentConfig.type] = flagMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then check if the type is a Vector<REAL_TYPE>
|
// Then check if the type is a Vector<REAL_TYPE>
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const vectorMatch = currentConfig.type.match(/[Vv]ector<([\w\d.]+)>/)
|
const vectorMatch = currentConfig.type.match(/[Vv]ector<([\w\d.]+)>/);
|
||||||
|
|
||||||
if (vectorMatch) {
|
if (vectorMatch) {
|
||||||
currentConfig.isVector = true
|
currentConfig.isVector = true;
|
||||||
|
|
||||||
// If the type's first letter is not uppercase, then
|
// If the type's first letter is not uppercase, then
|
||||||
// it is a constructor and we use (read/write) its ID.
|
// it is a constructor and we use (read/write) its ID.
|
||||||
@ -176,7 +178,7 @@ function buildArgConfig(name, argType) {
|
|||||||
currentConfig.useVectorId = currentConfig.type.charAt(0) === 'V';
|
currentConfig.useVectorId = currentConfig.type.charAt(0) === 'V';
|
||||||
|
|
||||||
// Update the type to match the one inside the vector
|
// Update the type to match the one inside the vector
|
||||||
[, currentConfig.type] = vectorMatch
|
[, currentConfig.type] = vectorMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// See use_vector_id. An example of such case is ipPort in
|
// See use_vector_id. An example of such case is ipPort in
|
||||||
@ -184,10 +186,10 @@ function buildArgConfig(name, argType) {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (/^[a-z]$/.test(currentConfig.type.split('.')
|
if (/^[a-z]$/.test(currentConfig.type.split('.')
|
||||||
.pop()
|
.pop()
|
||||||
.charAt(0)
|
.charAt(0),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
currentConfig.skipConstructorId = true
|
currentConfig.skipConstructorId = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The name may contain "date" in it, if this is the case and
|
// The name may contain "date" in it, if this is the case and
|
||||||
@ -202,61 +204,65 @@ function buildArgConfig(name, argType) {
|
|||||||
// this.type = 'date';
|
// this.type = 'date';
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
return currentConfig
|
return currentConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const parseTl = function* (content, layer, methods = [], ignoreIds = CORE_TYPES) {
|
const parseTl = function* (content, layer, methods = [], ignoreIds = CORE_TYPES) {
|
||||||
const methodInfo = (methods || []).reduce((o, m) => ({ ...o, [m.name]: m }), {})
|
const methodInfo = (methods || []).reduce((o, m) => ({
|
||||||
const objAll = []
|
...o,
|
||||||
const objByName = {}
|
[m.name]: m,
|
||||||
const objByType = {}
|
}), {});
|
||||||
|
const objAll = [];
|
||||||
|
const objByName = {};
|
||||||
|
const objByType = {};
|
||||||
|
|
||||||
const file = content
|
const file = content;
|
||||||
|
|
||||||
let isFunction = false
|
let isFunction = false;
|
||||||
|
|
||||||
for (let line of file.split('\n')) {
|
for (let line of file.split('\n')) {
|
||||||
const commentIndex = line.indexOf('//')
|
const commentIndex = line.indexOf('//');
|
||||||
|
|
||||||
if (commentIndex !== -1) {
|
if (commentIndex !== -1) {
|
||||||
line = line.slice(0, commentIndex)
|
line = line.slice(0, commentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
line = line.trim()
|
line = line.trim();
|
||||||
|
|
||||||
if (!line) {
|
if (!line) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const match = line.match(/---(\w+)---/)
|
const match = line.match(/---(\w+)---/);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const [, followingTypes] = match
|
const [, followingTypes] = match;
|
||||||
isFunction = followingTypes === 'functions'
|
isFunction = followingTypes === 'functions';
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = fromLine(line, isFunction)
|
const result = fromLine(line, isFunction);
|
||||||
|
|
||||||
if (ignoreIds.has(result.constructorId)) {
|
if (ignoreIds.has(result.constructorId)) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
objAll.push(result)
|
objAll.push(result);
|
||||||
|
|
||||||
if (!result.isFunction) {
|
if (!result.isFunction) {
|
||||||
if (!objByType[result.result]) {
|
if (!objByType[result.result]) {
|
||||||
objByType[result.result] = []
|
objByType[result.result] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
objByName[result.name] = result
|
objByName[result.name] = result;
|
||||||
objByType[result.result].push(result)
|
objByType[result.result].push(result);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!e.toString().includes('vector#1cb5c415')) {
|
if (!e.toString()
|
||||||
throw e
|
.includes('vector#1cb5c415')) {
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,79 +274,79 @@ const parseTl = function* (content, layer, methods = [], ignoreIds = CORE_TYPES)
|
|||||||
if (AUTH_KEY_TYPES.has(obj.constructorId)) {
|
if (AUTH_KEY_TYPES.has(obj.constructorId)) {
|
||||||
for (const arg in obj.argsConfig) {
|
for (const arg in obj.argsConfig) {
|
||||||
if (obj.argsConfig[arg].type === 'string') {
|
if (obj.argsConfig[arg].type === 'string') {
|
||||||
obj.argsConfig[arg].type = 'bytes'
|
obj.argsConfig[arg].type = 'bytes';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const obj of objAll) {
|
for (const obj of objAll) {
|
||||||
yield obj
|
yield obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
const findAll = (regex, str, matches = []) => {
|
const findAll = (regex, str, matches = []) => {
|
||||||
if (!regex.flags.includes(`g`)) {
|
if (!regex.flags.includes(`g`)) {
|
||||||
regex = new RegExp(regex.source, `g`)
|
regex = new RegExp(regex.source, `g`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = regex.exec(str)
|
const res = regex.exec(str);
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
matches.push(res.slice(1))
|
matches.push(res.slice(1));
|
||||||
findAll(regex, str, matches)
|
findAll(regex, str, matches);
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches
|
return matches;
|
||||||
}
|
};
|
||||||
|
|
||||||
function serializeBytes(data) {
|
function serializeBytes(data) {
|
||||||
if (!(data instanceof Buffer)) {
|
if (!(data instanceof Buffer)) {
|
||||||
if (typeof data == 'string') {
|
if (typeof data == 'string') {
|
||||||
data = Buffer.from(data)
|
data = Buffer.from(data);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`Bytes or str expected, not ${data.constructor.name}`)
|
throw Error(`Bytes or str expected, not ${data.constructor.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const r = []
|
const r = [];
|
||||||
let padding
|
let padding;
|
||||||
if (data.length < 254) {
|
if (data.length < 254) {
|
||||||
padding = (data.length + 1) % 4
|
padding = (data.length + 1) % 4;
|
||||||
if (padding !== 0) {
|
if (padding !== 0) {
|
||||||
padding = 4 - padding
|
padding = 4 - padding;
|
||||||
}
|
}
|
||||||
r.push(Buffer.from([data.length]))
|
r.push(Buffer.from([data.length]));
|
||||||
r.push(data)
|
r.push(data);
|
||||||
} else {
|
} else {
|
||||||
padding = data.length % 4
|
padding = data.length % 4;
|
||||||
if (padding !== 0) {
|
if (padding !== 0) {
|
||||||
padding = 4 - padding
|
padding = 4 - padding;
|
||||||
}
|
}
|
||||||
r.push(Buffer.from([254, data.length % 256, (data.length >> 8) % 256, (data.length >> 16) % 256]))
|
r.push(Buffer.from([254, data.length % 256, (data.length >> 8) % 256, (data.length >> 16) % 256]));
|
||||||
r.push(data)
|
r.push(data);
|
||||||
}
|
}
|
||||||
r.push(Buffer.alloc(padding)
|
r.push(Buffer.alloc(padding)
|
||||||
.fill(0))
|
.fill(0));
|
||||||
|
|
||||||
return Buffer.concat(r)
|
return Buffer.concat(r);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function serializeDate(dt) {
|
function serializeDate(dt) {
|
||||||
if (!dt) {
|
if (!dt) {
|
||||||
return Buffer.alloc(4)
|
return Buffer.alloc(4)
|
||||||
.fill(0)
|
.fill(0);
|
||||||
}
|
}
|
||||||
if (dt instanceof Date) {
|
if (dt instanceof Date) {
|
||||||
dt = Math.floor((Date.now() - dt.getTime()) / 1000)
|
dt = Math.floor((Date.now() - dt.getTime()) / 1000);
|
||||||
}
|
}
|
||||||
if (typeof dt == 'number') {
|
if (typeof dt == 'number') {
|
||||||
const t = Buffer.alloc(4)
|
const t = Buffer.alloc(4);
|
||||||
t.writeInt32LE(dt, 0)
|
t.writeInt32LE(dt, 0);
|
||||||
return t
|
return t;
|
||||||
}
|
}
|
||||||
throw Error(`Cannot interpret "${dt}" as a date`)
|
throw Error(`Cannot interpret "${dt}" as a date`);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -352,5 +358,5 @@ module.exports = {
|
|||||||
serializeDate,
|
serializeDate,
|
||||||
serializeBytes,
|
serializeBytes,
|
||||||
snakeToCamelCase,
|
snakeToCamelCase,
|
||||||
variableSnakeToCamelCase
|
variableSnakeToCamelCase,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
const api = require('./api')
|
const api = require('./api');
|
||||||
const { serializeBytes, serializeDate } = require('./generationHelpers')
|
const {
|
||||||
const patched = null
|
serializeBytes,
|
||||||
|
serializeDate,
|
||||||
|
} = require('./generationHelpers');
|
||||||
|
const patched = null;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// TODO Refactor internal usages to always use `api`.
|
// TODO Refactor internal usages to always use `api`.
|
||||||
@ -8,5 +11,5 @@ module.exports = {
|
|||||||
requests: api,
|
requests: api,
|
||||||
patched,
|
patched,
|
||||||
serializeBytes,
|
serializeBytes,
|
||||||
serializeDate
|
serializeDate,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,57 +1,61 @@
|
|||||||
const path = require('path')
|
const path = require('path');
|
||||||
const fs = require('fs')
|
const fs = require('fs');
|
||||||
|
|
||||||
const { parseTl } = require('../generationHelpers')
|
const { parseTl } = require('../generationHelpers');
|
||||||
const templateFn = require('./template')
|
const templateFn = require('./template');
|
||||||
|
|
||||||
const INPUT_FILE = path.resolve(__dirname, '../static/api.tl')
|
const INPUT_FILE = path.resolve(__dirname, '../static/api.tl');
|
||||||
const SCHEMA_FILE = path.resolve(__dirname, '../static/schema.tl')
|
const SCHEMA_FILE = path.resolve(__dirname, '../static/schema.tl');
|
||||||
|
|
||||||
const OUTPUT_FILE = path.resolve(__dirname, '../api.d.ts')
|
const OUTPUT_FILE = path.resolve(__dirname, '../api.d.ts');
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
const tlContent = fs.readFileSync(INPUT_FILE, 'utf-8')
|
const tlContent = fs.readFileSync(INPUT_FILE, 'utf-8');
|
||||||
const apiConfig = extractParams(tlContent)
|
const apiConfig = extractParams(tlContent);
|
||||||
const schemeContent = fs.readFileSync(SCHEMA_FILE, 'utf-8')
|
const schemeContent = fs.readFileSync(SCHEMA_FILE, 'utf-8');
|
||||||
const schemeConfig = extractParams(schemeContent)
|
const schemeConfig = extractParams(schemeContent);
|
||||||
const types = [...apiConfig.types, ...schemeConfig.types]
|
const types = [...apiConfig.types, ...schemeConfig.types];
|
||||||
const functions = [...apiConfig.functions, ...schemeConfig.functions]
|
const functions = [...apiConfig.functions, ...schemeConfig.functions];
|
||||||
const constructors = [...apiConfig.constructors, ...schemeConfig.constructors]
|
const constructors = [...apiConfig.constructors, ...schemeConfig.constructors];
|
||||||
const generated = templateFn({ types: types, functions: functions, constructors: constructors })
|
const generated = templateFn({
|
||||||
|
types: types,
|
||||||
|
functions: functions,
|
||||||
|
constructors: constructors,
|
||||||
|
});
|
||||||
|
|
||||||
fs.writeFileSync(OUTPUT_FILE, generated)
|
fs.writeFileSync(OUTPUT_FILE, generated);
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractParams(fileContent) {
|
function extractParams(fileContent) {
|
||||||
const defInterator = parseTl(fileContent, 109)
|
const defInterator = parseTl(fileContent, 109);
|
||||||
const types = {}
|
const types = {};
|
||||||
const constructors = []
|
const constructors = [];
|
||||||
const functions = []
|
const functions = [];
|
||||||
|
|
||||||
for (const def of defInterator) {
|
for (const def of defInterator) {
|
||||||
if (def.isFunction) {
|
if (def.isFunction) {
|
||||||
functions.push(def)
|
functions.push(def);
|
||||||
} else {
|
} else {
|
||||||
if (!types[def.result]) {
|
if (!types[def.result]) {
|
||||||
let [namespace, name] = def.result.includes('.') ? def.result.split('.') : [undefined, def.result]
|
let [namespace, name] = def.result.includes('.') ? def.result.split('.') : [undefined, def.result];
|
||||||
|
|
||||||
types[def.result] = {
|
types[def.result] = {
|
||||||
namespace,
|
namespace,
|
||||||
name,
|
name,
|
||||||
constructors: []
|
constructors: [],
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
types[def.result].constructors.push(def.namespace ? `${def.namespace}.${def.name}` : def.name)
|
types[def.result].constructors.push(def.namespace ? `${def.namespace}.${def.name}` : def.name);
|
||||||
constructors.push(def)
|
constructors.push(def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
types: Object.values(types),
|
types: Object.values(types),
|
||||||
constructors,
|
constructors,
|
||||||
functions
|
functions,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user