Browse Source

Add input peer utils
Format code

painor 5 năm trước cách đây
mục cha
commit
47a27679dd

+ 245 - 0
gramjs/Helpers.js

@@ -0,0 +1,245 @@
+const crypto = require('crypto')
+
+
+/**
+ * converts a buffer to big int
+ * @param buffer
+ * @param little
+ * @param signed
+ * @returns {bigint}
+ */
+function readBigIntFromBuffer(buffer, little = true, signed = false) {
+    let randBuffer = Buffer.from(buffer)
+    const bytesNumber = randBuffer.length
+    if (little) {
+        randBuffer = randBuffer.reverse()
+    }
+    let bigInt = BigInt('0x' + randBuffer.toString('hex'))
+    if (signed && Math.floor(bigInt.toString('2').length / 8) >= bytesNumber) {
+        bigInt -= 2n ** BigInt(bytesNumber * 8)
+    }
+    return bigInt
+}
+
+/**
+ * converts a big int to a buffer
+ * @param bigInt
+ * @param bytesNumber
+ * @param little
+ * @param signed
+ * @returns {Buffer}
+ */
+function readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) {
+    const bitLength = bigInt.toString('2').length
+
+    const bytes = Math.ceil(bitLength / 8)
+    if (bytesNumber < bytes) {
+        throw new Error('OverflowError: int too big to convert')
+    }
+    if (!signed && bigInt < 0) {
+        throw new Error('Cannot convert to unsigned')
+    }
+    let below = false
+    if (bigInt < 0) {
+        below = true
+        bigInt = -bigInt
+    }
+
+    const hex = bigInt.toString('16').padStart(bytesNumber * 2, '0')
+    let l = Buffer.from(hex, 'hex')
+    if (little) {
+        l = l.reverse()
+    }
+
+    if (signed && below) {
+        if (little) {
+            l[0] = 256 - l[0]
+            for (let i = 1; i < l.length; i++) {
+                l[i] = 255 - l[i]
+            }
+        } else {
+            l[l.length - 1] = 256 - l[l.length - 1]
+            for (let i = 0; i < l.length - 1; i++) {
+                l[i] = 255 - l[i]
+            }
+        }
+    }
+    return l
+}
+
+/**
+ * Generates a random long integer (8 bytes), which is optionally signed
+ * @returns {BigInt}
+ */
+function generateRandomLong(signed = true) {
+    return readBigIntFromBuffer(generateRandomBytes(8), true, signed)
+}
+
+/**
+ * .... really javascript
+ * @param n {number}
+ * @param m {number}
+ * @returns {number}
+ */
+function mod(n, m) {
+    return ((n % m) + m) % m
+}
+
+/**
+ * Generates a random bytes array
+ * @param count
+ * @returns {Buffer}
+ */
+function generateRandomBytes(count) {
+    return crypto.randomBytes(count)
+}
+
+
+/**
+ * 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}}
+ */
+
+function calcKey(sharedKey, msgKey, client) {
+    const x = client === true ? 0 : 8
+    const sha1a = sha1(Buffer.concat([msgKey, sharedKey.slice(x, x + 32)]))
+    const sha1b = sha1(
+        Buffer.concat([sharedKey.slice(x + 32, x + 48), msgKey, sharedKey.slice(x + 48, x + 64)]),
+    )
+    const sha1c = sha1(Buffer.concat([sharedKey.slice(x + 64, x + 96), msgKey]))
+    const sha1d = 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 }
+}
+
+/**
+ * Calculates the message key from the given data
+ * @param data
+ * @returns {Buffer}
+ */
+function calcMsgKey(data) {
+    return sha1(data).slice(4, 20)
+}
+
+/**
+ * Generates the key data corresponding to the given nonces
+ * @param serverNonce
+ * @param newNonce
+ * @returns {{key: Buffer, iv: Buffer}}
+ */
+function generateKeyDataFromNonce(serverNonce, newNonce) {
+    serverNonce = readBufferFromBigInt(serverNonce, 16, true, true)
+    newNonce = readBufferFromBigInt(newNonce, 32, true, true)
+    const hash1 = sha1(Buffer.concat([newNonce, serverNonce]))
+    const hash2 = sha1(Buffer.concat([serverNonce, newNonce]))
+    const hash3 = 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 }
+}
+
+/**
+ * Calculates the SHA1 digest for the given data
+ * @param data
+ * @returns {Buffer}
+ */
+function sha1(data) {
+    const shaSum = crypto.createHash('sha1')
+    shaSum.update(data)
+    return shaSum.digest()
+}
+
+/**
+ * Calculates the SHA256 digest for the given data
+ * @param data
+ * @returns {Buffer}
+ */
+function sha256(data) {
+    const shaSum = crypto.createHash('sha256')
+    shaSum.update(data)
+    return shaSum.digest()
+}
+
+/**
+ * Fast mod pow for RSA calculation. a^b % n
+ * @param a
+ * @param b
+ * @param n
+ * @returns {bigint}
+ */
+function modExp(a, b, n) {
+    a = a % n
+    let result = 1n
+    let x = a
+    while (b > 0n) {
+        const leastSignificantBit = b % 2n
+        b = b / 2n
+        if (leastSignificantBit === 1n) {
+            result = result * x
+            result = result % n
+        }
+        x = x * x
+        x = x % n
+    }
+    return result
+}
+
+/**
+ * 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
+}
+
+/**
+ * Sleeps a specified amount of time
+ * @param ms time in milliseconds
+ * @returns {Promise}
+ */
+const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
+
+/**
+ * Checks if the obj is an array
+ * @param obj
+ * @returns {boolean}
+ */
+function isArrayLike(obj) {
+    if (!obj) return false
+    const l = obj.length
+    if (typeof l != 'number' || l < 0) return false
+    if (Math.floor(l) !== l) return false
+    // fast check
+    if (l > 0 && !(l - 1 in obj)) return false
+    // more complete check (optional)
+    for (let i = 0; i < l; ++i) {
+        if (!(i in obj)) return false
+    }
+    return true
+}
+
+
+module.exports = {
+    readBigIntFromBuffer,
+    readBufferFromBigInt,
+    generateRandomLong,
+    mod,
+    generateRandomBytes,
+    calcKey,
+    calcMsgKey,
+    generateKeyDataFromNonce,
+    sha1,
+    sha256,
+    modExp,
+    getRandomInt,
+    sleep,
+    isArrayLike,
+}

+ 241 - 0
gramjs/Utils.js

@@ -0,0 +1,241 @@
+const { types } = require('./tl')
+
+
+function _raiseCastFail(entity, target) {
+    throw new Error(`Cannot cast ${entity.constructor.name} to any kind of ${target}`)
+}
+
+/**
+ Gets the input peer for the given "entity" (user, chat or channel).
+
+ A ``TypeError`` is raised if the given entity isn't a supported type
+ or if ``check_hash is True`` but the entity's ``access_hash is None``
+ *or* the entity contains ``min`` information. In this case, the hash
+ cannot be used for general purposes, and thus is not returned to avoid
+ any issues which can derive from invalid access hashes.
+
+ Note that ``check_hash`` **is ignored** if an input peer is already
+ passed since in that case we assume the user knows what they're doing.
+ This is key to getting entities by explicitly passing ``hash = 0``.
+
+ * @param entity
+ * @param allowSelf
+ * @param checkHash
+ */
+function getInputPeer(entity, allowSelf = true, checkHash = true) {
+    if (entity.SUBCLASS_OF_ID === undefined) {
+        // e.g. custom.Dialog (can't cyclic import).
+
+        if (allowSelf && 'inputEntity' in entity) {
+            return entity.inputEntity
+        } else if ('entity' in entity) {
+            return getInputPeer(entity.entity)
+        } else {
+            _raiseCastFail(entity, 'InputPeer')
+        }
+    }
+
+    if (entity.SUBCLASS_OF_ID === 0xc91c90b6) { // crc32(b'InputPeer')
+        return entity
+    }
+
+    if (entity instanceof types.User) {
+        if (entity.isSelf && allowSelf) {
+            return new types.InputPeerSelf()
+        } else if ((entity.accessHash !== undefined && !entity.min) || !checkHash) {
+            return new types.InputPeerUser({
+                userId: entity.id,
+                accessHash: entity.accessHash,
+            })
+        } else {
+            throw new Error('User without access_hash or min info cannot be input')
+        }
+    }
+    if (entity instanceof types.Chat || entity instanceof types.ChatEmpty ||
+        entity instanceof types.ChatForbidden) {
+        return new types.InputPeerChat({ chatId: entity.id })
+    }
+    if (entity instanceof types.Channel) {
+        if ((entity.accessHash !== undefined && !entity.min) || checkHash) {
+            return new types.InputPeerChannel({ channelId: entity.id, accessHash: entity.accessHash })
+        } else {
+            throw new TypeError('Channel without access_hash or min info cannot be input')
+        }
+    }
+    if (entity instanceof types.ChannelForbidden) {
+        // "channelForbidden are never min", and since their hash is
+        // also not optional, we assume that this truly is the case.
+        return new types.InputPeerChannel({ channelId: entity.id, accessHash: entity.accessHash })
+    }
+
+    if (entity instanceof types.InputUser) {
+        return new types.InputPeerUser({ userId: entity.userId, accessHash: entity.accessHash })
+    }
+    if (entity instanceof types.InputChannel) {
+        return new types.InputPeerChannel({ userId: entity.channelId, accessHash: entity.accessHash })
+    }
+    if (entity instanceof types.UserEmpty) {
+        return new types.InputPeerEmpty()
+    }
+    if (entity instanceof types.UserFull) {
+        return getInputPeer(entity.user)
+    }
+
+    if (entity instanceof types.ChatFull) {
+        return new types.InputPeerChat(entity.id)
+    }
+
+    if (entity instanceof types.PeerChat) {
+        return new types.InputPeerChat(entity.chat_id)
+    }
+
+    _raiseCastFail(entity, 'InputPeer')
+}
+
+/**
+ Similar to :meth:`get_input_peer`, but for :tl:`InputChannel`'s alone.
+
+ .. important::
+
+ This method does not validate for invalid general-purpose access
+ hashes, unlike `get_input_peer`. Consider using instead:
+ ``get_input_channel(get_input_peer(channel))``.
+
+ * @param entity
+ * @returns {InputChannel|*}
+ */
+function getInputChannel(entity) {
+    if (entity.SUBCLASS_OF_ID === undefined) {
+        _raiseCastFail(entity, 'InputChannel')
+    }
+
+
+    if (entity.SUBCLASS_OF_ID === 0x40f202fd) { // crc32(b'InputChannel')
+        return entity
+    }
+    if (entity instanceof types.Channel || entity instanceof types.ChannelForbidden) {
+        return new types.InputChannel({ channelId: entity.id, accessHash: entity.accessHash || 0 })
+    }
+
+    if (entity instanceof types.InputPeerChannel) {
+        return new types.InputChannel({ channelId: entity.channelId, accessHash: entity.accessHash })
+    }
+    _raiseCastFail(entity, 'InputChannel')
+}
+
+/**
+ Similar to :meth:`get_input_peer`, but for :tl:`InputUser`'s alone.
+
+ .. important::
+
+ This method does not validate for invalid general-purpose access
+ hashes, unlike `get_input_peer`. Consider using instead:
+ ``get_input_channel(get_input_peer(channel))``.
+
+ * @param entity
+ */
+function getInputUser(entity) {
+    if (entity.SUBCLASS_OF_ID === undefined) {
+        _raiseCastFail(entity, 'InputUser')
+    }
+    if (entity.SUBCLASS_OF_ID === 0xe669bf46) { // crc32(b'InputUser')
+        return entity
+    }
+
+    if (entity instanceof types.User) {
+        if (entity.isSelf) {
+            return new types.InputPeerSelf()
+        } else {
+            return new types.InputUser({
+                userId: entity.id,
+                accessHash: entity.accessHash || 0,
+            })
+        }
+    }
+
+    if (entity instanceof types.InputPeerSelf) {
+        return new types.InputPeerSelf()
+    }
+    if (entity instanceof types.UserEmpty || entity instanceof types.InputPeerEmpty) {
+        return new types.InputUserEmpty()
+    }
+
+    if (entity instanceof types.UserFull) {
+        return getInputUser(entity.user)
+    }
+
+    if (entity instanceof types.InputPeerUser) {
+        return new types.InputUser({ userId: entity.userId, accessHash: entity.accessHash })
+    }
+
+    _raiseCastFail(entity, 'InputUser')
+}
+
+/**
+ Similar to :meth:`get_input_peer`, but for dialogs
+ * @param dialog
+ */
+function getInputDialog(dialog) {
+    try {
+        if (dialog.SUBCLASS_OF_ID === 0xa21c9795) { // crc32(b'InputDialogPeer')
+            return dialog
+        }
+        if (dialog.SUBCLASS_OF_ID === 0xc91c90b6) { // crc32(b'InputPeer')
+            return new types.InputDialogPeer({ peer: dialog })
+        }
+    } catch (e) {
+        _raiseCastFail(dialog, 'InputDialogPeer')
+    }
+
+    try {
+        return new types.InputDialogPeer(getInputPeer(dialog))
+        // eslint-disable-next-line no-empty
+    } catch (e) {
+
+    }
+    _raiseCastFail(dialog, 'InputDialogPeer')
+}
+
+function getInputMessage(message) {
+    try {
+        if (typeof message == 'number') { // This case is really common too
+            return new types.InputMessageID({
+                id: message,
+            })
+        } else if (message.SUBCLASS_OF_ID === 0x54b6bcc5) { // crc32(b'InputMessage')
+            return message
+        } else if (message.SUBCLASS_OF_ID === 0x790009e3) { // crc32(b'Message')
+            return new types.InputMessageID(message.id)
+        }
+        // eslint-disable-next-line no-empty
+    } catch (e) {
+    }
+
+    _raiseCastFail(message, 'InputMessage')
+}
+
+function _getEntityPair(entityId, entities, cache, getInputPeer = getInputPeer) {
+    const entity = entities.get(entityId)
+    let inputEntity = cache[entityId]
+    if (inputEntity === undefined) {
+        try {
+            inputEntity = getInputPeer(inputEntity)
+        } catch (e) {
+            inputEntity = null
+        }
+    }
+    return { entity, inputEntity }
+}
+
+function getMessageId(message) {
+    if (message === null || message === undefined) {
+        return null
+    }
+    if (typeof message == 'number') {
+        return message
+    }
+    if (message.SUBCLASS_OF_ID === 0x790009e3) { // crc32(b'Message')
+        return message.id
+    }
+    throw new Error(`Invalid message type: ${message.constructor.name}`)
+}

+ 5 - 5
gramjs/client/TelegramClient.js

@@ -1,5 +1,5 @@
 const log4js = require('log4js')
-const Helpers = require('../utils/Helpers')
+const { sleep } = require('../Helpers')
 const errors = require('../errors')
 const { addKey } = require('../crypto/RSA')
 const { TLRequest } = require('../tl/tlobject')
@@ -207,7 +207,7 @@ class TelegramClient {
                 delete this._floodWaitedRequests[request.CONSTRUCTOR_ID]
             } else if (diff <= this.floodSleepLimit) {
                 this._log.info(`Sleeping early for ${diff}s on flood wait`)
-                await Helpers.sleep(diff)
+                await sleep(diff)
                 delete this._floodWaitedRequests[request.CONSTRUCTOR_ID]
             } else {
                 throw new errors.FloodWaitError({
@@ -231,12 +231,12 @@ class TelegramClient {
                 if (e instanceof errors.ServerError || e instanceof errors.RpcCallFailError ||
                     e instanceof errors.RpcMcgetFailError) {
                     this._log.warn(`Telegram is having internal issues ${e.constructor.name}`)
-                    await Helpers.sleep(2)
+                    await Helpers.sleep(2000)
                 } else if (e instanceof errors.FloodWaitError || e instanceof errors.FloodTestPhoneWaitError) {
                     this._floodWaitedRequests = new Date().getTime() / 1000 + e.seconds
                     if (e.seconds <= this.floodSleepLimit) {
                         this._log.info(`Sleeping for ${e.seconds}s on flood wait`)
-                        await Helpers.sleep(e.seconds)
+                        await Helpers.sleep(e.seconds * 1000)
                     } else {
                         throw e
                     }
@@ -328,7 +328,7 @@ class TelegramClient {
 
     async sendCodeRequest(phone, forceSMS = false) {
         let result
-        phone = Helpers.parsePhone(phone) || this._phone
+        phone = parsePhone(phone) || this._phone
         let phoneHash = this._phoneCodeHash[phone]
 
         if (!phoneHash) {

+ 2 - 2
gramjs/crypto/AES.js

@@ -1,5 +1,5 @@
 const aesjs = require('aes-js')
-const Helpers = require('../utils/Helpers')
+const { generateRandomBytes } = require('../Helpers')
 
 class AES {
     /**
@@ -47,7 +47,7 @@ class AES {
     static encryptIge(plainText, key, iv) {
         const padding = plainText.length % 16
         if (padding) {
-            plainText = Buffer.concat([plainText, Helpers.generateRandomBytes(16 - padding)])
+            plainText = Buffer.concat([plainText, generateRandomBytes(16 - padding)])
         }
 
         let iv1 = iv.slice(0, Math.floor(iv.length / 2))

+ 5 - 5
gramjs/crypto/AuthKey.js

@@ -1,4 +1,4 @@
-const Helpers = require('../utils/Helpers')
+const { sha1, readBufferFromBigInt, readBigIntFromBuffer } = require('../Helpers')
 const BinaryReader = require('../extensions/BinaryReader')
 const struct = require('python-struct')
 
@@ -19,7 +19,7 @@ class AuthKey {
             return
         }
         this._key = value
-        const reader = new BinaryReader(Helpers.sha1(this._key))
+        const reader = new BinaryReader(sha1(this._key))
         this.auxHash = reader.readLong(false)
         reader.read(4)
         this.keyId = reader.readLong(false)
@@ -38,12 +38,12 @@ class AuthKey {
      * @returns {bigint}
      */
     calcNewNonceHash(newNonce, number) {
-        newNonce = Helpers.readBufferFromBigInt(newNonce, 32, true, true)
+        newNonce = readBufferFromBigInt(newNonce, 32, true, true)
         const data = Buffer.concat([newNonce, struct.pack('<BQ', number.toString(), this.auxHash.toString())])
 
         // Calculates the message key from the given data
-        const shaData = Helpers.sha1(data).slice(4, 20)
-        return Helpers.readBigIntFromBuffer(shaData, true, true)
+        const shaData = sha1(data).slice(4, 20)
+        return readBigIntFromBuffer(shaData, true, true)
     }
 
     equals(other) {

+ 3 - 3
gramjs/crypto/Factorizator.js

@@ -1,4 +1,4 @@
-const Helpers = require('../utils/Helpers')
+const { getRandomInt } = require('../Helpers')
 
 class Factorizator {
     /**
@@ -9,8 +9,8 @@ class Factorizator {
     static findSmallMultiplierLopatin(what) {
         let g = 0n
         for (let i = 0n; i < 3n; i++) {
-            const q = 30n || (Helpers.getRandomInt(0, 127) & 15) + 17
-            let x = 40n || Helpers.getRandomInt(0, 1000000000) + 1
+            const q = 30n || (getRandomInt(0, 127) & 15) + 17
+            let x = 40n || getRandomInt(0, 1000000000) + 1
 
             let y = x
             const lim = 1n << (i + 18n)

+ 11 - 11
gramjs/crypto/RSA.js

@@ -1,6 +1,6 @@
 const NodeRSA = require('node-rsa')
 const { TLObject } = require('../tl/tlobject')
-const Helpers = require('../utils/Helpers')
+const { readBigIntFromBuffer, sha1, modExp, readBufferFromBigInt, generateRandomBytes } = require('../Helpers')
 const _serverKeys = {}
 
 /**
@@ -12,18 +12,18 @@ const _serverKeys = {}
 function getByteArray(integer, signed = false) {
     const { length: bits } = integer.toString(2)
     const byteLength = Math.floor((bits + 8 - 1) / 8)
-    return Helpers.readBufferFromBigInt(BigInt(integer), byteLength, false, signed)
+    return readBufferFromBigInt(BigInt(integer), byteLength, false, signed)
 }
 
 function _computeFingerprint(key) {
-    const buf = Helpers.readBigIntFromBuffer(key.keyPair.n.toBuffer(), false)
+    const buf = readBigIntFromBuffer(key.keyPair.n.toBuffer(), false)
     const nArray = getByteArray(buf)
 
     const n = TLObject.serializeBytes(nArray)
     const e = TLObject.serializeBytes(getByteArray(key.keyPair.e))
     // Telegram uses the last 8 bytes as the fingerprint
-    const sh = Helpers.sha1(Buffer.concat([n, e]))
-    return Helpers.readBigIntFromBuffer(sh.slice(-8), true, true)
+    const sh = sha1(Buffer.concat([n, e]))
+    return readBigIntFromBuffer(sh.slice(-8), true, true)
 }
 
 function addKey(pub) {
@@ -36,8 +36,8 @@ function encrypt(fingerprint, data) {
     if (!key) {
         return undefined
     }
-    const buf = Helpers.readBigIntFromBuffer(key.keyPair.n.toBuffer(), false)
-    let rand = Helpers.generateRandomBytes(235 - data.length)
+    const buf = readBigIntFromBuffer(key.keyPair.n.toBuffer(), false)
+    let rand = generateRandomBytes(235 - data.length)
     rand = Buffer.from(
         '66a6f809e0dfd71d9dbbc2d6b5fe5fc0be9f5b2b0f2f85688843eea6b2c6d51329750f020c8de27a0a911b07d2a46600493d1abb7caf24' +
         '01ccd815d7de7c5ea830cdf6cce8bff12f77db589f233bce436b644c3415f16d073335fdadfe313c603485b3274e8fcd148fd1a5e18bd2' +
@@ -45,10 +45,10 @@ function encrypt(fingerprint, data) {
         'hex',
     )
 
-    const toEncrypt = Buffer.concat([Helpers.sha1(data), data, rand])
-    const payload = Helpers.readBigIntFromBuffer(toEncrypt, false)
-    const encrypted = Helpers.modExp(payload, BigInt(key.keyPair.e), buf)
-    const block = Helpers.readBufferFromBigInt(encrypted, 256, false)
+    const toEncrypt = Buffer.concat([sha1(data), data, rand])
+    const payload = readBigIntFromBuffer(toEncrypt, false)
+    const encrypted = modExp(payload, BigInt(key.keyPair.e), buf)
+    const block = readBufferFromBigInt(encrypted, 256, false)
     return block
 }
 

+ 2 - 2
gramjs/events/common.js

@@ -1,7 +1,7 @@
 class EventBuilder {
     constructor(args = {
-            chats: null, blacklistChats: null, func: null,
-        },
+        chats: null, blacklistChats: null, func: null,
+    },
     ) {
         this.chats = args.chats
         this.blacklistChats = Boolean(args.blacklistChats)

+ 3 - 3
gramjs/extensions/BinaryReader.js

@@ -2,7 +2,7 @@ const { unpack } = require('python-struct')
 const { TypeNotFoundError } = require('../errors/Common')
 const { coreObjects } = require('../tl/core')
 const { tlobjects } = require('../tl/AllTLObjects')
-const Helpers = require('../utils/Helpers')
+const { readBigIntFromBuffer } = require('../Helpers')
 
 class BinaryReader {
     /**
@@ -81,7 +81,7 @@ class BinaryReader {
      */
     readLargeInt(bits, signed = true) {
         const buffer = this.read(Math.floor(bits / 8))
-        return Helpers.readBigIntFromBuffer(buffer, true, signed)
+        return readBigIntFromBuffer(buffer, true, signed)
     }
 
     /**
@@ -96,7 +96,7 @@ class BinaryReader {
         this.offset += length
         if (result.length !== length) {
             throw Error(
-                `No more data left to read (need ${length}, got ${result.length}: ${result}); last read ${this._last}`
+                `No more data left to read (need ${length}, got ${result.length}: ${result}); last read ${this._last}`,
             )
         }
         this._last = result

+ 1 - 1
gramjs/network/Authenticator.js

@@ -2,7 +2,7 @@ const AES = require('../crypto/AES')
 const AuthKey = require('../crypto/AuthKey')
 const Factorizator = require('../crypto/Factorizator')
 const RSA = require('../crypto/RSA')
-const Helpers = require('../utils/Helpers')
+const Helpers = require('../Helpers')
 const { ServerDHParamsFail } = require('../tl/types')
 const { ServerDHParamsOk } = require('../tl/types')
 const { ReqDHParamsRequest } = require('../tl/functions')

+ 1 - 1
gramjs/network/MTProtoPlainSender.js

@@ -2,7 +2,7 @@
  *  This module contains the class used to communicate with Telegram's servers
  *  in plain text, when no authorization key has been created yet.
  */
-const Helpers = require('../utils/Helpers')
+const Helpers = require('../Helpers')
 const MTProtoState = require('./MTProtoState')
 const struct = require('python-struct')
 const BinaryReader = require('../extensions/BinaryReader')

+ 1 - 1
gramjs/network/MTProtoSender.js

@@ -1,6 +1,6 @@
 const MtProtoPlainSender = require('./MTProtoPlainSender')
 const MTProtoState = require('./MTProtoState')
-const Helpers = require('../utils/Helpers')
+const Helpers = require('../Helpers')
 const AuthKey = require('../crypto/AuthKey')
 const doAuthentication = require('./Authenticator')
 const RPCResult = require('../tl/core/RPCResult')

+ 1 - 1
gramjs/network/MTProtoState.js

@@ -1,5 +1,5 @@
 const struct = require('python-struct')
-const Helpers = require('../utils/Helpers')
+const Helpers = require('../Helpers')
 const AES = require('../crypto/AES')
 const BinaryReader = require('../extensions/BinaryReader')
 const GZIPPacked = require('../tl/core/GZIPPacked')

+ 3 - 3
gramjs/sessions/Session.js

@@ -1,4 +1,4 @@
-const Helpers = require('../utils/Helpers')
+const { generateRandomLong, getRandomInt } = require('../Helpers')
 const fs = require('fs').promises
 const { existsSync, readFileSync } = require('fs')
 const AuthKey = require('../crypto/AuthKey')
@@ -20,7 +20,7 @@ class Session {
         // this.serverAddress = "localhost";
         // this.port = 21;
         this.authKey = undefined
-        this.id = Helpers.generateRandomLong(false)
+        this.id = generateRandomLong(false)
         this.sequence = 0
         this.salt = 0n // Unsigned long
         this.timeOffset = 0n
@@ -109,7 +109,7 @@ class Session {
         let newMessageId =
             (BigInt(BigInt(Math.floor(msTime / 1000)) + this.timeOffset) << 32n) |
             (BigInt(msTime % 1000) << 22n) |
-            (BigInt(Helpers.getRandomInt(0, 524288)) << 2n) // 2^19
+            (BigInt(getRandomInt(0, 524288)) << 2n) // 2^19
 
         if (this.lastMessageId >= newMessageId) {
             newMessageId = this.lastMessageId + 4n

+ 0 - 245
gramjs/utils/Helpers.js

@@ -1,245 +0,0 @@
-const crypto = require('crypto')
-const fs = require('fs').promises
-
-class Helpers {
-    static readBigIntFromBuffer(buffer, little = true, signed = false) {
-        let randBuffer = Buffer.from(buffer)
-        const bytesNumber = randBuffer.length
-        if (little) {
-            randBuffer = randBuffer.reverse()
-        }
-        let bigInt = BigInt('0x' + randBuffer.toString('hex'))
-        if (signed && Math.floor(bigInt.toString('2').length / 8) >= bytesNumber) {
-            bigInt -= 2n ** BigInt(bytesNumber * 8)
-        }
-        return bigInt
-    }
-
-    static readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) {
-        const bitLength = bigInt.toString('2').length
-
-        const bytes = Math.ceil(bitLength / 8)
-        if (bytesNumber < bytes) {
-            throw new Error('OverflowError: int too big to convert')
-        }
-        if (!signed && bigInt < 0) {
-            throw new Error('Cannot convert to unsigned')
-        }
-        let below = false
-        if (bigInt < 0) {
-            below = true
-            bigInt = -bigInt
-        }
-
-        const hex = bigInt.toString('16').padStart(bytesNumber * 2, '0')
-        let l = Buffer.from(hex, 'hex')
-        if (little) {
-            l = l.reverse()
-        }
-
-        if (signed && below) {
-            if (little) {
-                l[0] = 256 - l[0]
-                for (let i = 1; i < l.length; i++) {
-                    l[i] = 255 - l[i]
-                }
-            } else {
-                l[l.length - 1] = 256 - l[l.length - 1]
-                for (let i = 0; i < l.length - 1; i++) {
-                    l[i] = 255 - l[i]
-                }
-            }
-        }
-        return l
-    }
-
-    /**
-     * Generates a random long integer (8 bytes), which is optionally signed
-     * @returns {BigInt}
-     */
-    static generateRandomLong(signed = true) {
-        return this.readBigIntFromBuffer(Helpers.generateRandomBytes(8), true, signed)
-    }
-
-    /**
-     * .... really javascript
-     * @param n {number}
-     * @param m {number}
-     * @returns {number}
-     */
-    static mod(n, m) {
-        return ((n % m) + m) % m
-    }
-
-    /**
-     * Generates a random bytes array
-     * @param count
-     * @returns {Buffer}
-     */
-    static generateRandomBytes(count) {
-        return crypto.randomBytes(count)
-    }
-
-    /**
-     * Loads the user settings located under `api/`
-     * @param path
-     * @returns {Promise<void>}
-     */
-    static async loadSettings(path = 'api/settings') {
-        const settings = {}
-        let left
-        let right
-        let valuePair
-
-        const data = await fs.readFile(path, 'utf-8')
-
-        for (const line of data.toString().split('\n')) {
-            valuePair = line.split('=')
-            if (valuePair.length !== 2) {
-                break
-            }
-            left = valuePair[0].replace(/ \r?\n|\r/g, '')
-            right = valuePair[1].replace(/ \r?\n|\r/g, '')
-            if (!isNaN(right)) {
-                settings[left] = Number.parseInt(right)
-            } else {
-                settings[left] = right
-            }
-        }
-
-        return settings
-    }
-
-    /**
-     * 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}}
-     */
-
-    static calcKey(sharedKey, msgKey, client) {
-        const x = client === true ? 0 : 8
-        const sha1a = Helpers.sha1(Buffer.concat([msgKey, sharedKey.slice(x, x + 32)]))
-        const sha1b = Helpers.sha1(
-            Buffer.concat([sharedKey.slice(x + 32, x + 48), msgKey, sharedKey.slice(x + 48, x + 64)]),
-        )
-        const sha1c = Helpers.sha1(Buffer.concat([sharedKey.slice(x + 64, x + 96), msgKey]))
-        const sha1d = Helpers.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 }
-    }
-
-    /**
-     * Calculates the message key from the given data
-     * @param data
-     * @returns {Buffer}
-     */
-    static calcMsgKey(data) {
-        return Helpers.sha1(data).slice(4, 20)
-    }
-
-    /**
-     * Generates the key data corresponding to the given nonces
-     * @param serverNonce
-     * @param newNonce
-     * @returns {{key: Buffer, iv: Buffer}}
-     */
-    static generateKeyDataFromNonce(serverNonce, newNonce) {
-        serverNonce = Helpers.readBufferFromBigInt(serverNonce, 16, true, true)
-        newNonce = Helpers.readBufferFromBigInt(newNonce, 32, true, true)
-        const hash1 = Helpers.sha1(Buffer.concat([newNonce, serverNonce]))
-        const hash2 = Helpers.sha1(Buffer.concat([serverNonce, newNonce]))
-        const hash3 = Helpers.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 }
-    }
-
-    /**
-     * Calculates the SHA1 digest for the given data
-     * @param data
-     * @returns {Buffer}
-     */
-    static sha1(data) {
-        const shaSum = crypto.createHash('sha1')
-        shaSum.update(data)
-        return shaSum.digest()
-    }
-
-    /**
-     * Calculates the SHA256 digest for the given data
-     * @param data
-     * @returns {Buffer}
-     */
-    static sha256(data) {
-        const shaSum = crypto.createHash('sha256')
-        shaSum.update(data)
-        return shaSum.digest()
-    }
-
-    /**
-     * Fast mod pow for RSA calculation. a^b % n
-     * @param a
-     * @param b
-     * @param n
-     * @returns {bigint}
-     */
-    static modExp(a, b, n) {
-        a = a % n
-        let result = 1n
-        let x = a
-        while (b > 0n) {
-            const leastSignificantBit = b % 2n
-            b = b / 2n
-            if (leastSignificantBit === 1n) {
-                result = result * x
-                result = result % n
-            }
-            x = x * x
-            x = x % n
-        }
-        return result
-    }
-
-    /**
-     * returns a random int from min (inclusive) and max (inclusive)
-     * @param min
-     * @param max
-     * @returns {number}
-     */
-    static getRandomInt(min, max) {
-        min = Math.ceil(min)
-        max = Math.floor(max)
-        return Math.floor(Math.random() * (max - min + 1)) + min
-    }
-
-    /**
-     * Sleeps a specified amount of time
-     * @param ms time in milliseconds
-     * @returns {Promise}
-     */
-    static sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
-
-    /**
-     * Checks if the obj is an array
-     * @param obj
-     * @returns {boolean}
-     */
-    static isArrayLike(obj) {
-        if (!obj) return false
-        const l = obj.length
-        if (typeof l != 'number' || l < 0) return false
-        if (Math.floor(l) !== l) return false
-        // fast check
-        if (l > 0 && !(l - 1 in obj)) return false
-        // more complete check (optional)
-        for (let i = 0; i < l; ++i) {
-            if (!(i in obj)) return false
-        }
-        return true
-    }
-}
-
-module.exports = Helpers