Merge branch 'main' of https://github.com/aiko-chan-ai/discord.js-selfbot-v13
This commit is contained in:
@@ -35,6 +35,7 @@ const DataResolver = require('../util/DataResolver');
|
|||||||
const Intents = require('../util/Intents');
|
const Intents = require('../util/Intents');
|
||||||
const DiscordAuthWebsocket = require('../util/RemoteAuth');
|
const DiscordAuthWebsocket = require('../util/RemoteAuth');
|
||||||
const Sweepers = require('../util/Sweepers');
|
const Sweepers = require('../util/Sweepers');
|
||||||
|
const TOTP = require('../util/Totp');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main hub for interacting with the Discord API, and the starting point for any bot.
|
* The main hub for interacting with the Discord API, and the starting point for any bot.
|
||||||
@@ -281,14 +282,13 @@ class Client extends BaseClient {
|
|||||||
* Logs the client in, establishing a WebSocket connection to Discord.
|
* Logs the client in, establishing a WebSocket connection to Discord.
|
||||||
* @param {string} email The email associated with the account
|
* @param {string} email The email associated with the account
|
||||||
* @param {string} password The password assicated with the account
|
* @param {string} password The password assicated with the account
|
||||||
* @param {string|number} [mfaCode = null] The mfa code if you have it enabled
|
|
||||||
* @returns {string | null} Token of the account used
|
* @returns {string | null} Token of the account used
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* client.passLogin("test@gmail.com", "SuperSecretPa$$word", 1234)
|
* client.passLogin("test@gmail.com", "SuperSecretPa$$word", 1234)
|
||||||
* @deprecated This method will not be updated until I find the most convenient way to implement MFA.
|
* @deprecated This method will not be updated until I find the most convenient way to implement MFA.
|
||||||
*/
|
*/
|
||||||
async passLogin(email, password, mfaCode = null) {
|
async passLogin(email, password) {
|
||||||
const initial = await this.api.auth.login.post({
|
const initial = await this.api.auth.login.post({
|
||||||
auth: false,
|
auth: false,
|
||||||
versioned: true,
|
versioned: true,
|
||||||
@@ -298,10 +298,12 @@ class Client extends BaseClient {
|
|||||||
if ('token' in initial) {
|
if ('token' in initial) {
|
||||||
return this.login(initial.token);
|
return this.login(initial.token);
|
||||||
} else if ('ticket' in initial) {
|
} else if ('ticket' in initial) {
|
||||||
|
if (!this.options.TOTPKey) throw new Error('TOTPKEY_MISSING');
|
||||||
|
const { otp } = await TOTP.generate(this.options.TOTPKey);
|
||||||
const totp = await this.api.auth.mfa.totp.post({
|
const totp = await this.api.auth.mfa.totp.post({
|
||||||
auth: false,
|
auth: false,
|
||||||
versioned: true,
|
versioned: true,
|
||||||
data: { gift_code_sku_id: null, login_source: null, code: mfaCode, ticket: initial.ticket },
|
data: { gift_code_sku_id: null, login_source: null, code: otp, ticket: initial.ticket },
|
||||||
});
|
});
|
||||||
if ('token' in totp) {
|
if ('token' in totp) {
|
||||||
return this.login(totp.token);
|
return this.login(totp.token);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const Messages = {
|
|||||||
|
|
||||||
TOKEN_INVALID: 'An invalid token was provided.',
|
TOKEN_INVALID: 'An invalid token was provided.',
|
||||||
TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.',
|
TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.',
|
||||||
|
TOTPKEY_MISSING: 'Request to use mfa, but TOTPKey was not set in client options.',
|
||||||
|
|
||||||
WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.',
|
WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.',
|
||||||
WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
|
WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
|
||||||
|
|||||||
194
src/util/Totp.js
Normal file
194
src/util/Totp.js
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** @typedef {"SHA-1" | "SHA-256" | "SHA-384" | "SHA-512"} TOTPAlgorithm */
|
||||||
|
/** @typedef {"hex" | "ascii"} TOTPEncoding */
|
||||||
|
/**
|
||||||
|
* @description Options for TOTP generation
|
||||||
|
* @typedef {Object} generateOptions
|
||||||
|
* @property {number} [digits=6] - The number of digits in the OTP.
|
||||||
|
* @property {TOTPAlgorithm} [algorithm="SHA-1"] - Algorithm used for hashing.
|
||||||
|
* @property {TOTPEncoding} [encoding="hex"] - Encoding used for the OTP.
|
||||||
|
* @property {number} [period=30] - The time period for OTP validity in seconds.
|
||||||
|
* @property {number} [timestamp=Date.now()] - The current timestamp.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class TOTP {
|
||||||
|
// eslint-disable-next-line valid-jsdoc
|
||||||
|
/**
|
||||||
|
* Generates a Time-based One-Time Password (TOTP)
|
||||||
|
* @public
|
||||||
|
* @param {string} key - The secret key for TOTP
|
||||||
|
* @param {generateOptions} options - Optional parameters for TOTP
|
||||||
|
* @returns {Promise<{ otp: string, expires: number }>}
|
||||||
|
*/
|
||||||
|
static async generate(key, options = {}) {
|
||||||
|
const _options = {
|
||||||
|
digits: 6,
|
||||||
|
algorithm: 'SHA-1',
|
||||||
|
encoding: 'hex',
|
||||||
|
period: 30,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
const epochSeconds = Math.floor(_options.timestam / 1000);
|
||||||
|
const timeHex = this.dec2hex(Math.floor(epochSeconds / _options.period)).padStart(16, '0');
|
||||||
|
|
||||||
|
const keyBuffer = _options.encoding === 'hex' ? this.base32ToBuffer(key) : this.asciiToBuffer(key);
|
||||||
|
|
||||||
|
const hmacKey = await this.crypto.importKey(
|
||||||
|
'raw',
|
||||||
|
keyBuffer,
|
||||||
|
{ name: 'HMAC', hash: { name: _options.algorithm } },
|
||||||
|
false,
|
||||||
|
['sign'],
|
||||||
|
);
|
||||||
|
const signature = await this.crypto.sign('HMAC', hmacKey, this.hex2buf(timeHex));
|
||||||
|
|
||||||
|
const signatureHex = this.buf2hex(signature);
|
||||||
|
const offset = this.hex2dec(signatureHex.slice(-1)) * 2;
|
||||||
|
const masked = this.hex2dec(signatureHex.slice(offset, offset + 8)) & 0x7fffffff;
|
||||||
|
const otp = masked.toString().slice(-_options.digits);
|
||||||
|
|
||||||
|
const period = _options.period * 1000;
|
||||||
|
const expires = Math.ceil((_options.timestamp + 1) / period) * period;
|
||||||
|
|
||||||
|
return { otp, expires };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converta a hexadecimal string into a decimal number
|
||||||
|
* @private
|
||||||
|
* @param {string} hex - The hexadecimal string
|
||||||
|
* @returns {number} The decimal representation
|
||||||
|
*/
|
||||||
|
static hex2dec(hex) {
|
||||||
|
return parseInt(hex, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a decimal number into a hexadeciamal string
|
||||||
|
* @private
|
||||||
|
* @param {number} dec - The decimal number
|
||||||
|
* @returns {string} The hex representation
|
||||||
|
*/
|
||||||
|
static dec2hex(dec) {
|
||||||
|
return (dec < 15.5 ? '0' : '') + Math.round(dec).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a base32 encoded string to an ArrayBuffer
|
||||||
|
* @private
|
||||||
|
* @param {string} str - The base32 encoded string to convert.
|
||||||
|
* @returns {ArrayBuffer} The ArrayBuffer representation of the base32 encoded string
|
||||||
|
*/
|
||||||
|
static base32ToBuffer(str) {
|
||||||
|
str = str.toUpperCase();
|
||||||
|
let length = str.length;
|
||||||
|
while (str.charCodeAt(length - 1) === 61) length--; // Remove padding
|
||||||
|
|
||||||
|
const bufferSize = (length * 5) / 8;
|
||||||
|
const buffer = new Uint8Array(bufferSize);
|
||||||
|
let value = 0,
|
||||||
|
bits = 0,
|
||||||
|
index = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const charCode = this.base32[str.charCodeAt(i)];
|
||||||
|
if (charCode === undefined) throw new Error('Invalid base32 character in key');
|
||||||
|
value = (value << 5) | charCode;
|
||||||
|
bits += 5;
|
||||||
|
|
||||||
|
if (bits >= 8) buffer[index++] = value >>> (bits -= 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an ASCII string to an ArrayBuffer
|
||||||
|
* @private
|
||||||
|
* @param {string} str - The ASCII string to convert
|
||||||
|
* @returns {ArrayBuffer} The ArrayBuffer representation of the string
|
||||||
|
*/
|
||||||
|
static asciiToBuffer(str) {
|
||||||
|
const buffer = new Uint8Array(str.length);
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
buffer[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return buffer.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a hexadecimal string to an ArrayBuffer
|
||||||
|
* @private
|
||||||
|
* @param {string} hex - The hexadecimal string to convert
|
||||||
|
* @returns {ArrayBuffer} The ArrayBuffer representation of the string
|
||||||
|
*/
|
||||||
|
static hex2buf(hex) {
|
||||||
|
const buffer = new Uint8Array(hex.length / 2);
|
||||||
|
|
||||||
|
for (let i = 0, j = 0; i < hex.length; i += 2, j++) buffer[j] = this.hex2dec(hex.slice(i, i + 2));
|
||||||
|
|
||||||
|
return buffer.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an ArrayBuffer to a hexadecimal string
|
||||||
|
* @private
|
||||||
|
* @param {ArrayBuffer} buffer - The ArrayBuffer to convert
|
||||||
|
* @returns {string} The hexadecimal string representation of the buffer
|
||||||
|
*/
|
||||||
|
static buf2hex(buffer) {
|
||||||
|
return [...new Uint8Array(buffer)].map(x => x.toString(16).padStart(2, '0')).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A precalculated mapping from base32 character codes to their corresponding index values for performance optimization
|
||||||
|
* This mapping is used in the base32ToBuffer method to convert base32 encoded strings to their binary representation
|
||||||
|
* @private
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
static base32 = {
|
||||||
|
50: 26,
|
||||||
|
51: 27,
|
||||||
|
52: 28,
|
||||||
|
53: 29,
|
||||||
|
54: 30,
|
||||||
|
55: 31,
|
||||||
|
65: 0,
|
||||||
|
66: 1,
|
||||||
|
67: 2,
|
||||||
|
68: 3,
|
||||||
|
69: 4,
|
||||||
|
70: 5,
|
||||||
|
71: 6,
|
||||||
|
72: 7,
|
||||||
|
73: 8,
|
||||||
|
74: 9,
|
||||||
|
75: 10,
|
||||||
|
76: 11,
|
||||||
|
77: 12,
|
||||||
|
78: 13,
|
||||||
|
79: 14,
|
||||||
|
80: 15,
|
||||||
|
81: 16,
|
||||||
|
82: 17,
|
||||||
|
83: 18,
|
||||||
|
84: 19,
|
||||||
|
85: 20,
|
||||||
|
86: 21,
|
||||||
|
87: 22,
|
||||||
|
88: 23,
|
||||||
|
89: 24,
|
||||||
|
90: 25,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @static
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
static crypto = (globalThis.crypto || require('crypto').webcrypto).subtle;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TOTP;
|
||||||
26
typings/index.d.ts
vendored
26
typings/index.d.ts
vendored
@@ -782,7 +782,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
|
|||||||
public sleep(timeout: number): Promise<void>;
|
public sleep(timeout: number): Promise<void>;
|
||||||
public login(token?: string): Promise<string>;
|
public login(token?: string): Promise<string>;
|
||||||
/** @deprecated This method will not be updated until I find the most convenient way to implement MFA. */
|
/** @deprecated This method will not be updated until I find the most convenient way to implement MFA. */
|
||||||
public passLogin(email: string, password: string, mfaCode?: string | number): Promise<string | null>;
|
public passLogin(email: string, password: string): Promise<string | null>;
|
||||||
public QRLogin(): Promise<void>;
|
public QRLogin(): Promise<void>;
|
||||||
public logout(): Promise<void>;
|
public logout(): Promise<void>;
|
||||||
public isReady(): this is Client<true>;
|
public isReady(): this is Client<true>;
|
||||||
@@ -3428,6 +3428,29 @@ export class ThreadMemberFlags extends BitField<ThreadMemberFlagsString> {
|
|||||||
public static resolve(bit?: BitFieldResolvable<ThreadMemberFlagsString, number>): number;
|
public static resolve(bit?: BitFieldResolvable<ThreadMemberFlagsString, number>): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TOTP {
|
||||||
|
private static hex2dec(hex: string): number;
|
||||||
|
private static dec2hex(dec: number): string;
|
||||||
|
private static base32ToBuffer(str: string): ArrayBuffer;
|
||||||
|
private static asciiToBuffer(str: string): ArrayBuffer;
|
||||||
|
private static hex2buf(hex: string): ArrayBuffer;
|
||||||
|
private static buf2hex(buf: ArrayBuffer): string;
|
||||||
|
private static readonly base32: { [key: number]: number };
|
||||||
|
private static readonly crypto: SubtleCrypto;
|
||||||
|
public static generate(key: string, options?: generateOptions): Promise<{ otp: string; expires: number}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TOTPAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512";
|
||||||
|
export type TOTPEncoding = "hex" | "ascii";
|
||||||
|
|
||||||
|
export interface generateOptions {
|
||||||
|
digits: number;
|
||||||
|
algorithm: TOTPAlgorithm;
|
||||||
|
encoding: TOTPEncoding;
|
||||||
|
period: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class Typing extends Base {
|
export class Typing extends Base {
|
||||||
private constructor(channel: TextBasedChannel, user: PartialUser, data?: RawTypingData);
|
private constructor(channel: TextBasedChannel, user: PartialUser, data?: RawTypingData);
|
||||||
public channel: TextBasedChannel;
|
public channel: TextBasedChannel;
|
||||||
@@ -5711,6 +5734,7 @@ export interface ClientOptions {
|
|||||||
ws?: WebSocketOptions;
|
ws?: WebSocketOptions;
|
||||||
http?: HTTPOptions;
|
http?: HTTPOptions;
|
||||||
rejectOnRateLimit?: string[] | ((data: RateLimitData) => boolean | Promise<boolean>);
|
rejectOnRateLimit?: string[] | ((data: RateLimitData) => boolean | Promise<boolean>);
|
||||||
|
TOTPKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClientPresenceStatus = 'online' | 'idle' | 'dnd';
|
export type ClientPresenceStatus = 'online' | 'idle' | 'dnd';
|
||||||
|
|||||||
Reference in New Issue
Block a user