UNPKG

dograma

Version:

NodeJS/Browser MTProto API Telegram client library,

501 lines (498 loc) 16 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports._entityType = exports._EntityType = exports.TotalList = exports.crc32 = exports.bufferXor = exports.sleep = exports.getRandomInt = exports.getMinBigInt = exports.returnBigInt = exports.getByteArray = exports.modExp = exports.sha256 = exports.sha1 = exports.convertToLittle = exports.generateKeyDataFromNonce = exports.stripText = exports.generateRandomBytes = exports.bigIntMod = exports.mod = exports.generateRandomLong = exports.readBufferFromBigInt = exports.toSignedLittleBuffer = exports.isArrayLike = exports.betterConsoleLog = exports.groupBy = exports.escapeRegex = exports.generateRandomBigInt = exports.readBigIntFromBuffer = void 0; const big_integer_1 = __importDefault(require("big-integer")); const CryptoFile_1 = __importDefault(require("./CryptoFile")); /** * converts a buffer to big int * @param buffer * @param little * @param signed * @returns {bigInt.BigInteger} */ function readBigIntFromBuffer(buffer, little = true, signed = false) { let randBuffer = Buffer.from(buffer); const bytesNumber = randBuffer.length; if (little) { randBuffer = randBuffer.reverse(); } let bigIntVar = (0, big_integer_1.default)(randBuffer.toString("hex"), 16); if (signed && Math.floor(bigIntVar.toString(2).length / 8) >= bytesNumber) { bigIntVar = bigIntVar.subtract((0, big_integer_1.default)(2).pow((0, big_integer_1.default)(bytesNumber * 8))); } return bigIntVar; } exports.readBigIntFromBuffer = readBigIntFromBuffer; function generateRandomBigInt() { return readBigIntFromBuffer(generateRandomBytes(8), false); } exports.generateRandomBigInt = generateRandomBigInt; function escapeRegex(string) { return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); } exports.escapeRegex = escapeRegex; function groupBy(list, keyGetter) { const map = new Map(); list.forEach((item) => { const key = keyGetter(item); const collection = map.get(key); if (!collection) { map.set(key, [item]); } else { collection.push(item); } }); return map; } exports.groupBy = groupBy; /** * Outputs the object in a better way by hiding all the private methods/attributes. * @param object - the class to use */ function betterConsoleLog(object) { const toPrint = {}; for (const key in object) { if (object.hasOwnProperty(key)) { if (!key.startsWith("_") && key != "originalArgs") { toPrint[key] = object[key]; } } } return toPrint; } exports.betterConsoleLog = betterConsoleLog; /** * Helper to find if a given object is an array (or similar) */ const isArrayLike = (x) => x && typeof x.length === "number" && typeof x !== "function" && typeof x !== "string"; exports.isArrayLike = isArrayLike; /* export function addSurrogate(text: string) { let temp = ""; for (const letter of text) { const t = letter.charCodeAt(0); if (0x1000 < t && t < 0x10FFFF) { const b = Buffer.from(letter, "utf16le"); const r = String.fromCharCode(b.readUInt16LE(0)) + String.fromCharCode(b.readUInt16LE(2)); temp += r; } else { text += letter; } } return temp; } */ /** * Special case signed little ints * @param big * @param number * @returns {Buffer} */ function toSignedLittleBuffer(big, number = 8) { const bigNumber = returnBigInt(big); const byteArray = []; for (let i = 0; i < number; i++) { byteArray[i] = bigNumber.shiftRight(8 * i).and(255); } // smh hacks return Buffer.from(byteArray); } exports.toSignedLittleBuffer = toSignedLittleBuffer; /** * converts a big int to a buffer * @param bigIntVar {BigInteger} * @param bytesNumber * @param little * @param signed * @returns {Buffer} */ function readBufferFromBigInt(bigIntVar, bytesNumber, little = true, signed = false) { bigIntVar = (0, big_integer_1.default)(bigIntVar); const bitLength = bigIntVar.bitLength().toJSNumber(); const bytes = Math.ceil(bitLength / 8); if (bytesNumber < bytes) { throw new Error("OverflowError: int too big to convert"); } if (!signed && bigIntVar.lesser((0, big_integer_1.default)(0))) { throw new Error("Cannot convert to unsigned"); } let below = false; if (bigIntVar.lesser((0, big_integer_1.default)(0))) { below = true; bigIntVar = bigIntVar.abs(); } const hex = bigIntVar.toString(16).padStart(bytesNumber * 2, "0"); let littleBuffer = Buffer.from(hex, "hex"); if (little) { littleBuffer = littleBuffer.reverse(); } if (signed && below) { if (little) { let reminder = false; if (littleBuffer[0] !== 0) { littleBuffer[0] -= 1; } for (let i = 0; i < littleBuffer.length; i++) { if (littleBuffer[i] === 0) { reminder = true; continue; } if (reminder) { littleBuffer[i] -= 1; reminder = false; } littleBuffer[i] = 255 - littleBuffer[i]; } } else { littleBuffer[littleBuffer.length - 1] = 256 - littleBuffer[littleBuffer.length - 1]; for (let i = 0; i < littleBuffer.length - 1; i++) { littleBuffer[i] = 255 - littleBuffer[i]; } } } return littleBuffer; } exports.readBufferFromBigInt = readBufferFromBigInt; /** * Generates a random long integer (8 bytes), which is optionally signed * @returns {BigInteger} */ function generateRandomLong(signed = true) { return readBigIntFromBuffer(generateRandomBytes(8), true, signed); } exports.generateRandomLong = generateRandomLong; /** * .... really javascript * @param n {number} * @param m {number} * @returns {number} */ function mod(n, m) { return ((n % m) + m) % m; } exports.mod = mod; /** * returns a positive bigInt * @param n {bigInt.BigInteger} * @param m {bigInt.BigInteger} * @returns {bigInt.BigInteger} */ function bigIntMod(n, m) { return n.remainder(m).add(m).remainder(m); } exports.bigIntMod = bigIntMod; /** * Generates a random bytes array * @param count * @returns {Buffer} */ function generateRandomBytes(count) { return Buffer.from(CryptoFile_1.default.randomBytes(count)); } exports.generateRandomBytes = generateRandomBytes; /** * Calculate the key based on Telegram guidelines, specifying whether it's the client or not * @param sharedKey * @param msgKey * @param client * @returns {{iv: Buffer, key: Buffer}} */ /*CONTEST this is mtproto 1 (mostly used for secret chats) async function calcKey(sharedKey, msgKey, client) { const x = client === true ? 0 : 8 const [sha1a, sha1b, sha1c, sha1d] = await Promise.all([ sha1(Buffer.concat([msgKey, sharedKey.slice(x, x + 32)])), sha1(Buffer.concat([sharedKey.slice(x + 32, x + 48), msgKey, sharedKey.slice(x + 48, x + 64)])), sha1(Buffer.concat([sharedKey.slice(x + 64, x + 96), msgKey])), sha1(Buffer.concat([msgKey, sharedKey.slice(x + 96, x + 128)])) ]) const key = Buffer.concat([sha1a.slice(0, 8), sha1b.slice(8, 20), sha1c.slice(4, 16)]) const iv = Buffer.concat([sha1a.slice(8, 20), sha1b.slice(0, 8), sha1c.slice(16, 20), sha1d.slice(0, 8)]) return { key, iv } } */ function stripText(text, entities) { if (!entities || !entities.length) { return text.trim(); } while (text && text[text.length - 1].trim() === "") { const e = entities[entities.length - 1]; if (e.offset + e.length == text.length) { if (e.length == 1) { entities.pop(); if (!entities.length) { return text.trim(); } } else { e.length -= 1; } } text = text.slice(0, -1); } while (text && text[0].trim() === "") { for (let i = 0; i < entities.length; i++) { const e = entities[i]; if (e.offset != 0) { e.offset--; continue; } if (e.length == 1) { entities.shift(); if (!entities.length) { return text.trimLeft(); } } else { e.length -= 1; } } text = text.slice(1); } return text; } exports.stripText = stripText; /** * Generates the key data corresponding to the given nonces * @param serverNonceBigInt * @param newNonceBigInt * @returns {{key: Buffer, iv: Buffer}} */ async function generateKeyDataFromNonce(serverNonceBigInt, newNonceBigInt) { const serverNonce = toSignedLittleBuffer(serverNonceBigInt, 16); const newNonce = toSignedLittleBuffer(newNonceBigInt, 32); const [hash1, hash2, hash3] = await Promise.all([ sha1(Buffer.concat([newNonce, serverNonce])), sha1(Buffer.concat([serverNonce, newNonce])), sha1(Buffer.concat([newNonce, newNonce])), ]); const keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)]); const ivBuffer = Buffer.concat([ hash2.slice(12, 20), hash3, newNonce.slice(0, 4), ]); return { key: keyBuffer, iv: ivBuffer, }; } exports.generateKeyDataFromNonce = generateKeyDataFromNonce; function convertToLittle(buf) { const correct = Buffer.alloc(buf.length * 4); for (let i = 0; i < buf.length; i++) { correct.writeUInt32BE(buf[i], i * 4); } return correct; } exports.convertToLittle = convertToLittle; /** * Calculates the SHA1 digest for the given data * @param data * @returns {Promise} */ function sha1(data) { const shaSum = CryptoFile_1.default.createHash("sha1"); shaSum.update(data); return shaSum.digest(); } exports.sha1 = sha1; /** * Calculates the SHA256 digest for the given data * @param data * @returns {Promise} */ function sha256(data) { const shaSum = CryptoFile_1.default.createHash("sha256"); shaSum.update(data); return shaSum.digest(); } exports.sha256 = sha256; /** * Fast mod pow for RSA calculation. a^b % n * @param a * @param b * @param n * @returns {bigInt.BigInteger} */ function modExp(a, b, n) { a = a.remainder(n); let result = big_integer_1.default.one; let x = a; while (b.greater(big_integer_1.default.zero)) { const leastSignificantBit = b.remainder((0, big_integer_1.default)(2)); b = b.divide((0, big_integer_1.default)(2)); if (leastSignificantBit.eq(big_integer_1.default.one)) { result = result.multiply(x); result = result.remainder(n); } x = x.multiply(x); x = x.remainder(n); } return result; } exports.modExp = modExp; /** * Gets the arbitrary-length byte array corresponding to the given integer * @param integer {number,BigInteger} * @param signed {boolean} * @returns {Buffer} */ function getByteArray(integer, signed = false) { const bits = integer.toString(2).length; const byteLength = Math.floor((bits + 8 - 1) / 8); return readBufferFromBigInt(typeof integer == "number" ? (0, big_integer_1.default)(integer) : integer, byteLength, false, signed); } exports.getByteArray = getByteArray; function returnBigInt(num) { if (big_integer_1.default.isInstance(num)) { return num; } if (typeof num == "number") { return (0, big_integer_1.default)(num); } if (typeof num == "bigint") { return (0, big_integer_1.default)(num); } return (0, big_integer_1.default)(num); } exports.returnBigInt = returnBigInt; /** * Helper function to return the smaller big int in an array * @param arrayOfBigInts */ function getMinBigInt(arrayOfBigInts) { if (arrayOfBigInts.length == 0) { return big_integer_1.default.zero; } if (arrayOfBigInts.length == 1) { return returnBigInt(arrayOfBigInts[0]); } let smallest = returnBigInt(arrayOfBigInts[0]); for (let i = 1; i < arrayOfBigInts.length; i++) { if (returnBigInt(arrayOfBigInts[i]).lesser(smallest)) { smallest = returnBigInt(arrayOfBigInts[i]); } } return smallest; } exports.getMinBigInt = getMinBigInt; /** * returns a random int from min (inclusive) and max (inclusive) * @param min * @param max * @returns {number} */ function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } exports.getRandomInt = getRandomInt; /** * Sleeps a specified amount of time * @param ms time in milliseconds * @returns {Promise} */ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); exports.sleep = sleep; /** * Helper to export two buffers of same length * @returns {Buffer} */ function bufferXor(a, b) { const res = []; for (let i = 0; i < a.length; i++) { res.push(a[i] ^ b[i]); } return Buffer.from(res); } exports.bufferXor = bufferXor; // Taken from https://stackoverflow.com/questions/18638900/javascript-crc32/18639999#18639999 function makeCRCTable() { let c; const crcTable = []; for (let n = 0; n < 256; n++) { c = n; for (let k = 0; k < 8; k++) { c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; } crcTable[n] = c; } return crcTable; } let crcTable = undefined; function crc32(buf) { if (!crcTable) { crcTable = makeCRCTable(); } if (!Buffer.isBuffer(buf)) { buf = Buffer.from(buf); } let crc = -1; for (let index = 0; index < buf.length; index++) { const byte = buf[index]; crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8); } return (crc ^ -1) >>> 0; } exports.crc32 = crc32; class TotalList extends Array { constructor() { super(); this.total = 0; } } exports.TotalList = TotalList; exports._EntityType = { USER: 0, CHAT: 1, CHANNEL: 2, }; Object.freeze(exports._EntityType); function _entityType(entity) { if (typeof entity !== "object" || !("SUBCLASS_OF_ID" in entity)) { throw new Error(`${entity} is not a TLObject, cannot determine entity type`); } if (![ 0x2d45687, 0xc91c90b6, 0xe669bf46, 0x40f202fd, 0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697, // crc32('ChatFull') ].includes(entity.SUBCLASS_OF_ID)) { throw new Error(`${entity} does not have any entity type`); } const name = entity.className; if (name.includes("User")) { return exports._EntityType.USER; } else if (name.includes("Chat")) { return exports._EntityType.CHAT; } else if (name.includes("Channel")) { return exports._EntityType.CHANNEL; } else if (name.includes("Self")) { return exports._EntityType.USER; } // 'Empty' in name or not found, we don't care, not a valid entity. throw new Error(`${entity} does not have any entity type`); } exports._entityType = _entityType;