fix: Export, Typing, etc.

- Rename `RemoteAuth` > `DiscordAuthWebsocket`
- Add new method: Client.remoteAuth() & Client.createToken()
- fix: RedeemNitro (wrong Regex)
This commit is contained in:
March 7th 2022-07-11 15:04:07 +07:00
parent fe9be92bd2
commit b1257bc62d
8 changed files with 136 additions and 45 deletions

File diff suppressed because one or more lines are too long

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "discord.js-selfbot-v13", "name": "discord.js-selfbot-v13",
"version": "2.3.7", "version": "2.3.71",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "discord.js-selfbot-v13", "name": "discord.js-selfbot-v13",
"version": "2.3.7", "version": "2.3.71",
"license": "GNU General Public License v3.0", "license": "GNU General Public License v3.0",
"dependencies": { "dependencies": {
"@aikochan2k6/qrcode-terminal": "^0.12.0", "@aikochan2k6/qrcode-terminal": "^0.12.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "discord.js-selfbot-v13", "name": "discord.js-selfbot-v13",
"version": "2.3.7", "version": "2.3.71",
"description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]", "description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]",
"main": "./src/index.js", "main": "./src/index.js",
"types": "./typings/index.d.ts", "types": "./typings/index.d.ts",

View File

@ -380,6 +380,68 @@ class Client extends BaseClient {
return QR; return QR;
} }
/**
* @typedef {Object} remoteAuthConfrim
* @property {function} yes Yes
* @property {function} no No
*/
/**
* Implement `remoteAuth`, like using your phone to scan a QR code
* @param {string} url URL from QR code
* @param {boolean} forceAccept Whether to force confirm `yes`
* @returns {Promise<remoteAuthConfrim | void>}
*/
async remoteAuth(url, forceAccept = false) {
if (!this.isReady()) throw new Error('CLIENT_NOT_READY', 'Remote Auth');
// Step 1: Parse URL
url = new URL(url);
if (
!['discordapp.com', 'discord.com'].includes(url.hostname) ||
!url.pathname.startsWith('/ra/') ||
url.pathname.length <= 4
) {
throw new Error('INVALID_REMOTE_AUTH_URL');
}
const hash = url.pathname.replace('/ra/', '');
// Step 2: Post > Get handshake_token
const res = await this.api.users['@me']['remote-auth'].post({
data: {
fingerprint: hash,
},
});
const handshake_token = res.handshake_token;
// Step 3: Post
const yes = () =>
this.api.users['@me']['remote-auth'].finish.post({ data: { handshake_token, temporary_token: false } });
const no = () => this.api.users['@me']['remote-auth'].cancel.post({ data: { handshake_token } });
if (forceAccept) {
return yes();
} else {
return {
yes,
no,
};
}
}
/**
* Create a new token based on the current token
* @returns {Promise<string>} Discord Token
*/
createToken() {
return new Promise(resolve => {
// Step 1: Create DiscordAuthWebsocket
const QR = new DiscordAuthWebsocket(undefined, false, true);
// Step 2: Add event
QR.on('ready', async url => {
await this.remoteAuth(url, true);
}).on('success', (user, token) => {
resolve(token);
});
});
}
/** /**
* Returns whether the client has logged in, indicative of being able to access * Returns whether the client has logged in, indicative of being able to access
* properties such as `user` and `application`. * properties such as `user` and `application`.
@ -461,31 +523,35 @@ class Client extends BaseClient {
* @param {boolean} failIfNotExists Whether to fail if the code doesn't exist * @param {boolean} failIfNotExists Whether to fail if the code doesn't exist
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
redeemNitro(nitro, channel, failIfNotExists = true) { async redeemNitro(nitro, channel, failIfNotExists = true) {
if (typeof nitro !== 'string') throw new Error('INVALID_NITRO'); if (typeof nitro !== 'string') throw new Error('INVALID_NITRO');
channel = this.channels.resolveId(channel); channel = this.channels.resolveId(channel);
const regex = { const regex = {
gift: /(discord.gift|discord.com|discordapp.com\/gifts)\/\w{16,25}/gim, gift: /(discord.gift|discord.com|discordapp.com\/gifts)\/\w{16,25}/gim,
url: /(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)/gim, url: /(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)/gim,
}; };
const code = DataResolver.resolveCode(nitro, regex.gift); const nitroArray = nitro.match(regex.gift);
if (this.usedCodes.indexOf(code) > -1) return false; if (!nitroArray) return false;
return new Promise((resolve, reject) => { const codeArray = nitroArray.map(code => code.replace(regex.url, ''));
this.api.entitlements['gift-codes'](code) let redeem = false;
for await (const code of codeArray) {
if (this.usedCodes.indexOf(code) > -1) continue;
await this.api.entitlements['gift-codes'](code)
.redeem.post({ .redeem.post({
auth: true, auth: true,
data: { channel_id: channel || null, payment_source_id: null }, data: { channel_id: channel || null, payment_source_id: null },
}) })
.then(() => { .then(() => {
this.usedCodes.push(code); this.usedCodes.push(code);
resolve(true); redeem = true;
}) })
.catch(e => { .catch(e => {
if (failIfNotExists) reject(e); this.usedCodes.push(code);
else resolve(false); if (failIfNotExists) throw e;
});
}); });
} }
return redeem;
}
/** /**
* Obtains a template from Discord. * Obtains a template from Discord.

View File

@ -192,6 +192,8 @@ const Messages = {
MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND: customId => `Required field with custom id "${customId}" not found.`, MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND: customId => `Required field with custom id "${customId}" not found.`,
MODAL_SUBMIT_INTERACTION_FIELD_TYPE: (customId, type, expected) => MODAL_SUBMIT_INTERACTION_FIELD_TYPE: (customId, type, expected) =>
`Field with custom id "${customId}" is of type: ${type}; expected ${expected}.`, `Field with custom id "${customId}" is of type: ${type}; expected ${expected}.`,
INVALID_REMOTE_AUTH_URL: 'Invalid remote auth URL (https://discord.com/ra/{hash})',
}; };
for (const [name, message] of Object.entries(Messages)) register(name, message); for (const [name, message] of Object.entries(Messages)) register(name, message);

View File

@ -33,7 +33,6 @@ exports.UserFlags = require('./util/UserFlags');
exports.Util = require('./util/Util'); exports.Util = require('./util/Util');
exports.version = require('../package.json').version; exports.version = require('../package.json').version;
exports.DiscordAuthWebsocket = require('./util/RemoteAuth'); exports.DiscordAuthWebsocket = require('./util/RemoteAuth');
exports.RemoteAuth = require('./util/RemoteAuth');
exports.PurchasedFlags = require('./util/PurchasedFlags'); exports.PurchasedFlags = require('./util/PurchasedFlags');
// Managers // Managers

View File

@ -24,17 +24,19 @@ class DiscordUser_FromPayload {
let values = payload.split(':'); let values = payload.split(':');
this.id = values[0]; this.id = values[0];
this.username = values[3]; this.username = values[3];
this.discrim = values[1]; this.discriminator = values[1];
this.avatar_hash = values[2]; this.avatar = values[2];
this.tag = `${this.username}#${this.discriminator}`;
this.avatarURL = `https://cdn.discordapp.com/avatars/${this.id}/${this.avatar}.${
this.avatar.startsWith('a_') ? 'gif' : 'png'
}`;
this.debug = debug; this.debug = debug;
return this; return this;
} }
pretty_print() { pretty_print() {
let out = ''; let out = '';
out += `User: ${this.username}#${this.discrim} (${this.id})\n`; out += `User: ${this.tag} (${this.id})\n`;
out += `Avatar URL: https://cdn.discordapp.com/avatars/${this.id}/${this.avatar_hash}.${ out += `Avatar URL: ${this.avatarURL}\n`;
this.avatar_hash.startsWith('a_') ? 'gif' : 'png'
}\n`;
if (this.debug) out += `Token: ${this.token}\n`; if (this.debug) out += `Token: ${this.token}\n`;
return out; return out;
} }
@ -49,12 +51,14 @@ class DiscordAuthWebsocket extends EventEmitter {
/** /**
* Creates a new DiscordAuthWebsocket instance. * Creates a new DiscordAuthWebsocket instance.
* @param {?Client} client Discord.Client (Login) * @param {?Client} client Discord.Client (Login)
* @param {boolean} debug Log debug info * @param {?boolean} debug Log debug info
* @param {?boolean} hideLog Hide log ?
*/ */
constructor(client, debug = false) { constructor(client, debug = false, hideLog = false) {
super(); super();
this.debug = debug; this.debug = debug;
this.client = client; this.client = client;
this.hideLog = hideLog;
this.ws = new WebSocket(client?.options?.http?.remoteAuth || 'wss://remote-auth-gateway.discord.gg/?v=1', { this.ws = new WebSocket(client?.options?.http?.remoteAuth || 'wss://remote-auth-gateway.discord.gg/?v=1', {
headers: { headers: {
Origin: 'https://discord.com', Origin: 'https://discord.com',
@ -132,7 +136,7 @@ class DiscordAuthWebsocket extends EventEmitter {
* @param {string} url DiscordAuthWebsocket * @param {string} url DiscordAuthWebsocket
*/ */
this.emit('ready', this.authURL); this.emit('ready', this.authURL);
this.generate_qr_code(fingerprint); if (!this.hideLog) this.generate_qr_code(fingerprint);
if (this.debug) console.log('[WebSocket] QR Code generated'); if (this.debug) console.log('[WebSocket] QR Code generated');
console.log( console.log(
`Please scan the QR code to continue.\nQR Code will expire in ${this.missQR.toLocaleString('vi-VN')}`, `Please scan the QR code to continue.\nQR Code will expire in ${this.missQR.toLocaleString('vi-VN')}`,
@ -142,11 +146,17 @@ class DiscordAuthWebsocket extends EventEmitter {
let payload = this.decrypt_payload(encrypted_payload); let payload = this.decrypt_payload(encrypted_payload);
const decoder = new StringDecoder('utf-8'); const decoder = new StringDecoder('utf-8');
this.user = new DiscordUser_FromPayload(decoder.write(payload), this.debug); this.user = new DiscordUser_FromPayload(decoder.write(payload), this.debug);
console.log('\n'); if (!this.hideLog) console.log('\n');
console.log(this.user.pretty_print()); if (!this.hideLog) console.log(this.user.pretty_print());
/**
* Emitted whenever a user is scan QR Code.
* @event DiscordAuthWebsocket#pending
* @param {object} user Discord User Raw
*/
this.emit('pending', this.user);
if (this.debug) console.log('[WebSocket] Waiting for user to finish login...'); if (this.debug) console.log('[WebSocket] Waiting for user to finish login...');
console.log('\n'); if (!this.hideLog) console.log('\n');
console.log('Please check your phone again to confirm login.'); if (!this.hideLog) console.log('Please check your phone again to confirm login.');
} else if (op == Messages.FINISH) { } else if (op == Messages.FINISH) {
this.login_state = true; this.login_state = true;
let encrypted_token = data.encrypted_token; let encrypted_token = data.encrypted_token;
@ -158,29 +168,19 @@ class DiscordAuthWebsocket extends EventEmitter {
/** /**
* Emitted whenever a token is created. * Emitted whenever a token is created.
* @event DiscordAuthWebsocket#success * @event DiscordAuthWebsocket#success
* @param {object} user Discord User * @param {object} user Discord User (Raw)
* @param {string} token Discord Token * @param {string} token Discord Token
*/ */
this.emit( this.emit('success', this.user, this.token);
'success',
{
id: this.user.id,
tag: `${this.user.username}#${this.user.discrim}`,
},
this.token,
);
this.client?.login(this.user.token); this.client?.login(this.user.token);
this.destroy(); this.destroy();
} else if (op == Messages.CANCEL) { } else if (op == Messages.CANCEL) {
/** /**
* Emitted whenever a user cancels the login process. * Emitted whenever a user cancels the login process.
* @event DiscordAuthWebsocket#cancel * @event DiscordAuthWebsocket#cancel
* @param {object} user User * @param {object} user User (Raw)
*/ */
this.emit('cancel', { this.emit('cancel', this.user);
id: this.user.id,
tag: `${this.user.username}#${this.user.discrim}`,
});
this.destroy(); this.destroy();
} }
}); });
@ -200,10 +200,16 @@ class DiscordAuthWebsocket extends EventEmitter {
this.ws.close(); this.ws.close();
clearInterval(this.heartbeat_interval); clearInterval(this.heartbeat_interval);
clearTimeout(this.connectionDestroy); clearTimeout(this.connectionDestroy);
/**
* Emitted whenever a connection is closed.
* @event DiscordAuthWebsocket#closed
* @param {boolean} loginState Login state
*/
this.emit('closed', this.login_state);
if (this.debug) { if (this.debug) {
console.log(`[WebSocket] Connection Destroyed, User login state: ${this.login_state ? 'success' : 'failure'}`); console.log(`[WebSocket] Connection Destroyed, User login state: ${this.login_state ? 'success' : 'failure'}`);
} }
if (!this.login_state) throw new Error('Login failed'); if (!this.login_state && this.client) throw new Error('Login failed');
} }
public_key() { public_key() {

24
typings/index.d.ts vendored
View File

@ -162,10 +162,21 @@ import {
RawWidgetMemberData, RawWidgetMemberData,
} from './rawDataTypes'; } from './rawDataTypes';
// @ts-ignore // @ts-ignore
import DiscordAuthWebsocket from '../src/util/RemoteAuth.js';
//#region Classes //#region Classes
export abstract class DiscordAuthWebsocket extends EventEmitter {
constructor(client?: Client, debug?: boolean, hideLog?: boolean);
public authURL?: string;
public token?: string;
public user?: RawUserData;
public destroy(): void;
public generate_qr_code(fingerprint: string): void;
public on(event: 'ready', listener: (authURL: string) => void): this;
public on(event: 'success', listener: (user: RawUserData, token: string) => void): this;
public on(event: 'cancel', listener: (user: RawUserData) => void): this;
public on(event: 'pending', listener: (user: RawUserData) => void): this;
public on(event: 'closed', listener: (loginState: boolean) => void): this;
public on(event: string, listener: (...args: any[]) => Awaitable<void>): this;
}
// RPC by aiko-chan-ai // RPC by aiko-chan-ai
export interface RichButton { export interface RichButton {
name: string; name: string;
@ -754,6 +765,11 @@ export abstract class Channel extends Base {
export type If<T extends boolean, A, B = null> = T extends true ? A : T extends false ? B : A | B; export type If<T extends boolean, A, B = null> = T extends true ? A : T extends false ? B : A | B;
export interface remoteAuthConfrim {
yes(): Promise<undefined>;
no(): Promise<undefined>;
}
export class Client<Ready extends boolean = boolean> extends BaseClient { export class Client<Ready extends boolean = boolean> extends BaseClient {
public constructor(options?: ClientOptions); /* Bug report by Mavri#0001 [721347809667973141] */ public constructor(options?: ClientOptions); /* Bug report by Mavri#0001 [721347809667973141] */
private actions: unknown; private actions: unknown;
@ -797,6 +813,8 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
public generateInvite(options?: InviteGenerationOptions): string; public generateInvite(options?: InviteGenerationOptions): string;
public login(token?: string): Promise<string>; public login(token?: string): Promise<string>;
public QRLogin(debug?: boolean): DiscordAuthWebsocket; public QRLogin(debug?: boolean): DiscordAuthWebsocket;
public remoteAuth(url: string, forceAccept?: boolean): Promise<remoteAuthConfrim | undefined>;
public createToken(): Promise<string>;
public readonly callVoice: VoiceConnection | undefined; public readonly callVoice: VoiceConnection | undefined;
public isReady(): this is Client<true>; public isReady(): this is Client<true>;
/** @deprecated Use {@link Sweepers#sweepMessages} instead */ /** @deprecated Use {@link Sweepers#sweepMessages} instead */