From 039dd34cf26a8124c5a8d566b932ae52bdd4ca4e Mon Sep 17 00:00:00 2001
From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com>
Date: Sun, 14 Jan 2024 12:33:11 +0700
Subject: [PATCH] Update
---
Document/FetchGuildMember.md | 187 --
Document/Message.md | 40 +-
Document/RichPresence.md | 61 +-
Document/SlashCommand.md | 7 -
Document/User.md | 170 --
Document/VoiceCall.md | 628 -----
README.md | 8 +-
package.json | 8 +-
src/client/Client.js | 565 +----
src/client/websocket/handlers/READY.js | 185 +-
.../handlers/USER_GUILD_SETTINGS_UPDATE.js | 12 -
.../handlers/USER_SETTINGS_UPDATE.js | 9 -
src/client/websocket/handlers/index.js | 33 +-
src/errors/Messages.js | 92 +-
src/index.js | 53 +-
src/managers/ClientUserSettingManager.js | 490 ----
src/managers/DeveloperPortalManager.js | 104 -
src/managers/GuildFolderManager.js | 24 -
src/managers/GuildSettingManager.js | 148 --
src/managers/SessionManager.js | 57 -
src/structures/DeveloperPortalApplication.js | 520 -----
src/structures/GuildFolder.js | 75 -
src/structures/Session.js | 81 -
src/util/RemoteAuth.js | 597 ++---
src/util/Util.js | 259 +--
typings/enums.d.ts | 81 +-
typings/index.d.ts | 2053 ++++++-----------
27 files changed, 1250 insertions(+), 5297 deletions(-)
delete mode 100644 Document/FetchGuildMember.md
delete mode 100644 Document/User.md
delete mode 100644 Document/VoiceCall.md
delete mode 100644 src/client/websocket/handlers/USER_GUILD_SETTINGS_UPDATE.js
delete mode 100644 src/client/websocket/handlers/USER_SETTINGS_UPDATE.js
delete mode 100644 src/managers/ClientUserSettingManager.js
delete mode 100644 src/managers/DeveloperPortalManager.js
delete mode 100644 src/managers/GuildFolderManager.js
delete mode 100644 src/managers/GuildSettingManager.js
delete mode 100644 src/managers/SessionManager.js
delete mode 100644 src/structures/DeveloperPortalApplication.js
delete mode 100644 src/structures/GuildFolder.js
delete mode 100644 src/structures/Session.js
diff --git a/Document/FetchGuildMember.md b/Document/FetchGuildMember.md
deleted file mode 100644
index fa8b924..00000000
--- a/Document/FetchGuildMember.md
+++ /dev/null
@@ -1,187 +0,0 @@
-### Credit: Discord-S.C.U.M
-- Link: [Here](https://github.com/Merubokkusu/Discord-S.C.U.M/blob/master/docs/using/fetchingGuildMembers.md#Algorithm)
-
-# Fetching Guild Members
-
-### Assume you don't have one of the following permissions
-> `ADMINISTRATOR`, `KICK_MEMBERS`, `BAN_MEMBERS`, `MANAGE_ROLES`
-
-Alright so this really needs a page of its own because it's special. There's no actual api endpoint to get the guild members, so instead you have 2 options:
-1) fetch the member list sidebar (guild.members.fetchMemberList, which uses opcode 14)
- - when to use: if you can see any categories/channels in the guild
- - pro: fast
- - con: for large servers (```guild.memberCount > 1000```), only not-offline members are fetched
-
-2) search for members by query (guild.members.fetchBruteforce, which uses opcode 8)
- - when to use: if you cannot see any categories/channels in the guild
- - pro: can potentially get the entire member list, can scrape members from multiple guilds at the same time
- - con: slow af (speed is dependent on brute forcer optimizations)
-
-____________________________________
-# Links/Table of Contents
-- [fetch the member list sidebar (faster, but less members)](#fetch-the-member-list-sidebar)
- - [Algorithm](#Algorithm)
- - [How many members can I fetch?](#how-many-members-can-i-fetch)
- - [Examples](#Examples)
- - [Efficiency & Effectiveness](#efficiency--effectiveness)
- - [POC: fetching the memberlist backwards](#fetching-the-member-list-backwards)
-- [search for members by query (slower, but more members)](#search-for-members-by-query)
- - [Usage](#Usage)
- - [Algorithm](#Algorithm-1)
- - [How many members can I fetch?](#how-many-members-can-i-fetch-1)
-___________________________________
-## Fetch the Member List Sidebar
-#### Algorithm
-1) load guild data (send an op14 with range [0,99]). If the guild is unavailable, discord will send over a GUILD_CREATE event.
-2) subscribe to a list of ranges in member list sidebar.
-3) after a GUILD_MEMBER_LIST_UPDATE is received, update the saved member list data and subscribe to a new list of ranges
-
-note:
-- you don't have to wait for a GUILD_MEMBER_LIST_UPDATE event to send the next list of member ranges
-- there're 2 methods to fetch the member list:
- - overlap. Ranges subscribed to (in order) are:
- ```
- [[0,99], [100,199]]
- [[0,99], [100,199], [200,299]]
- [[0,99], [200,299], [300,399]]
- ...
- ```
- - nonoverlap. Ranges subscribed to (in order) are:
- ```
- [[0,99], [100,199]]
- [[0,99], [200,299], [300,399]]
- [[0,99], [400,499], [500,599]]
- ...
- ```
-- more info: https://arandomnewaccount.gitlab.io/discord-unofficial-docs/lazy_guilds.html
-
-#### How many members can I fetch?
-Even though it's not yet known how discord calculates this, you can still come up with a "ground truth" number. The steps are as follows:
-1) open your browser's dev tools (chrome dev tools is a favorite)
-2) click on the network tab and make sure you can see websocket connections
-3) go to a guild and scroll all the way down on the member list
-4) see what are the ranges of the last gateway request your client sends (the # of fetchable members is somewhere in these ranges)
-
-#### Examples
-all examples shown use the "overlap" method
-
-```js
-const guild = client.guilds.cache.get('id');
-const channel = guild.channels.cache.get('id');
-// Overlap (slow)
-for (let index = 0; index <= guild.memberCount; index += 100) {
- await guild.members.fetchMemberList(channel, index, index !== 100).catch(() => {});
- await client.sleep(500);
-}
-// Non-overlap (fast)
-for (let index = 0; index <= guild.memberCount; index += 200) {
- await guild.members.fetchMemberList(channel, index == 0 ? 100 : index, index !== 100).catch(() => {});
- await client.sleep(500);
-}
-console.log(guild.members.cache.size); // will print the number of members in the guild
-```
-
-It's possible that fetchMembers doesn't fetch all not-offline members due to rate limiting. Don't worry if this happens, you can start fetching members from any index.
-```js
-const guild = client.guilds.cache.get('id');
-const channel = guild.channels.cache.get('id');
-// Fetch member range 5000-5099
-await guild.members.fetchMemberList(channel, 5000);
-```
-
-#### Efficiency & Effectiveness
-
-| | overlap | no overlap |
-|------|---------|------------|
-| 2.1k |![a](https://github.com/Merubokkusu/Discord-S.C.U.M/raw/master/docs/using/memberFetchingStats/2100a.jpg) |![c](https://github.com/Merubokkusu/Discord-S.C.U.M/raw/master/docs/using/memberFetchingStats/2100b.jpg) |
-| 128k |![b](https://github.com/Merubokkusu/Discord-S.C.U.M/raw/master/docs/using/memberFetchingStats/128ka.jpg) |![d](https://github.com/Merubokkusu/Discord-S.C.U.M/raw/master/docs/using/memberFetchingStats/128kb.jpg) |
-
-As you can see, the "no overlap" method fetches 200 members/second while the "overlap" method fetches 100 members/second. However, "no overlap" is also a lot less effective. After doing a few more tests with both methods ("overlap" and "no overlap"), "no overlap" shows a lot less consistency/reliability than "overlap".
-
-
-#### Fetching the member list backwards
-(and in pretty much any "style" you want)
-So, this is more proof-of-concept, but here's a short explanation.
-Suppose you're in a guild with 1000 members and want to fetch the member list backwards (I dunno...more undetectable since noone fetches it backwards? lol).
- Since discum requests members in 200-member chunks, you'll either have to request for the following range groups (safer):
- ```
- [[0,99],[800,899],[900,999]] #target start: 800
- [[0,99],[700,799],[800,899]] #target start: 700
- [[0,99],[600,699],[700,799]] #target start: 600
- [[0,99],[500,599],[600,699]] #target start: 500
- [[0,99],[400,499],[500,599]] #target start: 400
- [[0,99],[300,399],[400,499]] #target start: 300
- [[0,99],[200,299],[300,399]] #target start: 200
- [[0,99],[100,199],[200,299]] #target start: 100
- [[0,99],[100,199]] #target start: 0
- ```
- or the following range groups (faster):
- ```
- [[0,99],[800,899],[900,999]] #target start: 800
- [[0,99],[600,699],[700,799]] #target start: 600
- [[0,99],[400,499],[500,599]] #target start: 400
- [[0,99],[200,299],[300,399]] #target start: 200
- [[0,99],[100,199]] #target start: 0
- ```
- The first one looks like an overlap method while the second looks like a no-overlap method. However, since we're fetching the memberlist backwards, we cannot
- use 100 and 200 for the methods. Instead, we need a list of multipliers (method) and a startIndex.
-____________________________________
-## Search for Members by Query
-#### Usage
-1) run the function:
- ```js
- guild.members.fetchBruteforce({
- delay: 500,
- depth: 1, // ['a', 'b', 'c', 'd', ...] or ['aa', 'ab', 'ac', 'ad', ...] if depth is 2, ...
- })
- ```
- A wait time of at least 0.5 is needed to prevent the brute forcer from rate limiting too often. In the event that the brute forcer does get rate limited, some time will be lost reconnecting.
-#### Algorithm
-for simplicity, assume that the list of characters to search for is ['a', 'b', 'c', 'd']
-1) query for up to 100 members in guild who have a nickname/username starting with 'a'
-2) on a GUILD_MEMBERS_CHUNK event:
- - if there are 100 results:
- - add on the 2nd character of the last result. For example, if the results are
- ```
- aaaaaaaaaaaa
- aaadfd3fgdftjh
- ...
- Acaddd
- ```
- ,
- the next query will be 'ac'. Note: searches are case-insensitive and consecutive spaces are treated like single spaces.
- - if there are less than 100 results:
- - replace the last index of the query with the next option in the list
-
-This algorithm can definitely be made a lot better so have at it. The brute forcer example is just there to help you get started.
-
-#### How many members can I fetch?
-- a limit is posed if many users have the same nickname & username (but different discriminators). Only the 1st 100 members will be able to be fetched. There's no known way to include the discriminator # in the search.
-- also, in order to query users with fancy characters in their username/nickname, the op8 brute forcer needs to be slowed down (cause, more characters to search)
-
-#### Testing !!!
-
-- Example
-```js
-const Discord = require('discord.js-selfbot-v13');
-const client = new Discord.Client();
-client.on('debug', console.log)
-client.on('ready', () => {
- console.log('Logged in as', client.user.tag);
- const guild = client.guilds.cache.get('662267976984297473'); // Midjourney - 13M members
- guild.members.fetchBruteforce({
- depth: 2, // 2 levels of recursion
- delay: 500, // 500ms delay between each request
- });
- setInterval(() => {
- console.log('Fetching members...', guild.members.cache.size);
- }, 1000);
-});
-client.login('token');
-```
-
-- 2000 years later...
-
-
-
-`138k/13.8M (1%)` members fetched in `~30 mins` (with a delay of 500ms) :skull:
\ No newline at end of file
diff --git a/Document/Message.md b/Document/Message.md
index 718a945..c2f909c 100644
--- a/Document/Message.md
+++ b/Document/Message.md
@@ -17,40 +17,10 @@
## Interaction
-Button Click
-
-```js
-await Button.click(Message);
-//
-await message.clickButton(buttonID);
-//
-await message.clickButton(); // first button
-//
-await message.clickButton({ row: 0, col: 0})
-```
-
-
-Message Select Menu
-
-```js
-await MessageSelectMenu.select(Message, options); // (v1)
-// value: ['value1', 'value2' , ...]
-await message.selectMenu(menuID, options) // If message has >= 2 menu
-await message.selectMenu(options) // If message has 1 menu
-```
-
-
Slash Command
### [Click here](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/SlashCommand.md)
-
-
-Message Context Command
-
-```js
-await message.contextMenu(botID, commandName);
-```
@@ -64,13 +34,7 @@ await message.contextMenu(botID, commandName);
Code:
```js
const Discord = require('discord.js-selfbot-v13');
-// Selfhost WebEmbed: https://github.com/aiko-chan-ai/WebEmbed
-const w = new Discord.WebEmbed({
- shorten: true,
- hidden: false, // if you send this embed with MessagePayload.options.embeds, it must set to false
- baseURL: '', // if you want self-host API, else skip :v
- shortenAPI: '', // if you want Custom shortenAPI (Method: Get, response: Text => URL), else skip :v
-})
+const w = new Discord.WebEmbed()
.setAuthor({ name: 'hello', url: 'https://google.com' })
.setColor('RED')
.setDescription('description uh')
@@ -83,7 +47,7 @@ const w = new Discord.WebEmbed({
.setVideo(
'https://cdn.discordapp.com/attachments/877060758092021801/957691816143097936/The_Quintessential_Quintuplets_And_Rick_Astley_Autotune_Remix.mp4',
);
-message.channel.send({ content: `Hello world`, embeds: [w] }) // Patched :)
+message.channel.send({ content: `Hello world ${Discord.WebEmbed.hiddenEmbed} ${w}` });
```
### Features & Issues
diff --git a/Document/RichPresence.md b/Document/RichPresence.md
index 36a81bc..9c29b64 100644
--- a/Document/RichPresence.md
+++ b/Document/RichPresence.md
@@ -1,10 +1,3 @@
-## Setup
-```js
-const client = new Client({
- syncStatus: false,
-});
-```
-
## Custom Status and RPC
Custom Status
@@ -68,57 +61,7 @@ client.user.setActivity(r);
> Tutorial:
-## Method 1:
-
-+ Step 1: Send image to Discord
-
-
-
-+ Step 2: Get Image URL
-
-
-
-```sh
-Demo URL: https://cdn.discordapp.com/attachments/820557032016969751/991172011483218010/unknown.png
-```
-
-+ Step 3: Replace `https://cdn.discordapp.com/` or `https://media.discordapp.net/` with `mp:`
-
-```diff
-- https://cdn.discordapp.com/attachments/820557032016969751/991172011483218010/unknown.png
-
-- https://media.discordapp.net/attachments/820557032016969751/991172011483218010/unknown.png
-
-+ mp:attachments/820557032016969751/991172011483218010/unknown.png
-
-```
-
-+ Step 4:
-
-```js
-const Discord = require('discord.js-selfbot-v13');
-const r = new Discord.RichPresence()
- .setApplicationId('817229550684471297')
- .setType('PLAYING')
- .setURL('https://youtube.com/watch?v=dQw4w9WgXcQ')
- .setState('State')
- .setName('Name')
- .setDetails('Details')
- .setParty({
- max: 9,
- current: 1,
- id: Discord.getUUID(),
- })
- .setStartTimestamp(Date.now())
- .setAssetsLargeImage('mp:attachments/820557032016969751/991172011483218010/unknown.png')
- .setAssetsLargeText('Youtube')
- .setAssetsSmallImage('895316294222635008')
- .setAssetsSmallText('Bot')
- .addButton('name', 'https://link.com/')
-client.user.setActivity(r);
-```
-
-## Method 2: (Discord URL, 2.3.78+)
+## Method 1: (Discord URL, v2.3.78+)
```js
const Discord = require('discord.js-selfbot-v13');
@@ -145,7 +88,7 @@ client.user.setActivity(r);
-## Method 3 (Custom URL, 2.3.78+)
+## Method 2 (Custom URL, 2.3.78+)
```js
const Discord = require('discord.js-selfbot-v13');
diff --git a/Document/SlashCommand.md b/Document/SlashCommand.md
index 5bd6d77..b7ca1b7 100644
--- a/Document/SlashCommand.md
+++ b/Document/SlashCommand.md
@@ -42,10 +42,3 @@ await message.channel.sendSlash('718642000898818048', 'sauce', a)
### Result
![image](https://user-images.githubusercontent.com/71698422/173347075-5c8a1347-3845-489e-956b-63975911b6e0.png)
-
-# Events
-
-- [interactionCreate](https://discordjs-self-v13.netlify.app/#/docs/docs/main/class/Client?scrollTo=e-interactionCreate)
-- [interactionFailure](https://discordjs-self-v13.netlify.app/#/docs/docs/main/class/Client?scrollTo=e-interactionFailure)
-- [interactionSuccess](https://discordjs-self-v13.netlify.app/#/docs/docs/main/class/Client?scrollTo=e-interactionSuccess)
-- [interactionModalCreate](https://discordjs-self-v13.netlify.app/#/docs/docs/main/class/Client?scrollTo=e-interactionModalCreate)
diff --git a/Document/User.md b/Document/User.md
deleted file mode 100644
index 7da9279..00000000
--- a/Document/User.md
+++ /dev/null
@@ -1,170 +0,0 @@
-# Quick Links:
-- [Setting](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/User.md#user-settings)
-- [User Info](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/User.md#discord-user-info)
-- [Relationship](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/User.md#discord-user-friend--blocked)
-- [Other](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/User.md#user--clientuser-method)
-
-## User Settings
-
-Click to show
-
-```js
-client.setting // Return Data Setting User;
-client.settings.setDisplayCompactMode(true | false); // Message Compact Mode
-client.settings.setTheme('dark' | 'light'); // Discord App theme
-client.settings.setLocale(value); // Set Language
- /**
- * * Locale Setting, must be one of:
- * * `DANISH`
- * * `GERMAN`
- * * `ENGLISH_UK`
- * * `ENGLISH_US`
- * * `SPANISH`
- * * `FRENCH`
- * * `CROATIAN`
- * * `ITALIAN`
- * * `LITHUANIAN`
- * * `HUNGARIAN`
- * * `DUTCH`
- * * `NORWEGIAN`
- * * `POLISH`
- * * `BRAZILIAN_PORTUGUESE`
- * * `ROMANIA_ROMANIAN`
- * * `FINNISH`
- * * `SWEDISH`
- * * `VIETNAMESE`
- * * `TURKISH`
- * * `CZECH`
- * * `GREEK`
- * * `BULGARIAN`
- * * `RUSSIAN`
- * * `UKRAINIAN`
- * * `HINDI`
- * * `THAI`
- * * `CHINA_CHINESE`
- * * `JAPANESE`
- * * `TAIWAN_CHINESE`
- * * `KOREAN`
- */
-// Setting Status
-client.settings.setCustomStatus({
- status: 'online', // 'online' | 'idle' | 'dnd' | 'invisible' | null
- text: 'Hello world', // String | null
- emoji: '🎮', // UnicodeEmoji | DiscordEmoji | null
- expires: null, // Date.now() + 1 * 3600 * 1000 <= 1h to ms
-});
-// => Clear
-client.settings.setCustomStatus();
-```
-
-
-
-## Discord User Info
-
-Click to show
-
-Code:
-```js
-GuildMember.user.getProfile();
-// or
-User.getProfile();
-```
-Response
-```js
-User {
- id: '721746046543331449',
- bot: false,
- system: false,
- flags: UserFlagsBitField { bitfield: 256 },
- connectedAccounts: [],
- premiumSince: 1623357181151,
- premiumGuildSince: 0,
- bio: null,
- mutualGuilds: Collection(3) [Map] {
- '906765260017516605' => { id: '906765260017516605', nick: null },
- '809133733591384155' => { id: '809133733591384155', nick: 'uwu' },
- '926065180788531261' => { id: '926065180788531261', nick: 'shiro' }
- },
- username: 'Shiraori',
- discriminator: '1782',
- avatar: 'f9ba7fb35b223e5f1a12eb910faa40c2',
- banner: undefined,
- accentColor: undefined
-}
-```
-
-
-## Discord User Friend / Blocked
-
-Click to show
-
-Code:
-```js
-// You can use client.relationships to manage your friends and blocked users.
-GuildMember.user.setFriend();
-User.unFriend();
-Message.member.user.sendFriendRequest();
-// or
-GuildMember.user.setBlock();
-User.unBlock();
-```
-Response
-```js
-User {
- id: '721746046543331449',
- bot: false,
- system: false,
- flags: UserFlagsBitField { bitfield: 256 },
- note: null,
- connectedAccounts: [],
- premiumSince: 1623357181151,
- premiumGuildSince: 0,
- bio: null,
- mutualGuilds: Collection(3) [Map] {
- '906765260017516605' => { id: '906765260017516605', nick: null },
- '809133733591384155' => { id: '809133733591384155', nick: 'uwu' },
- '926065180788531261' => { id: '926065180788531261', nick: 'shiro' }
- },
- username: 'Shiraori',
- discriminator: '1782',
- avatar: 'f9ba7fb35b223e5f1a12eb910faa40c2',
- banner: undefined,
- accentColor: undefined
-}
-```
-
-
-## User & ClientUser Method
-
-Click to show
-
-```js
-// HypeSquad
-await client.user.setHypeSquad('HOUSE_BRAVERY');
-await client.user.setHypeSquad('HOUSE_BRILLIANCE');
-await client.user.setHypeSquad('HOUSE_BALANCE');
-await client.user.setHypeSquad('LEAVE');
-// Set Note to User
-await user.setNote('Hello World');
-// Set Username
-await client.user.setUsername('new username', 'password');
-// Set Accent Color
-await client.user.setAccentColor('RED'); // set color same as Embed.setColor()
-// Set Banner
-await client.user.setBanner('image file / image url'); // same as setAvatar & Require Nitro level 2
-// Set Discord Tag
-await client.user.setDiscriminator('1234', 'password'); // #1234 & Require Nitro
-// Set About me
-await client.user.setAboutMe('Hello World');
-// Set Email
-await client.user.setEmail('aiko.dev@mail.nezukobot.vn', 'password'); // It is clone email =))
-// Change Password
-await client.user.setPassword('old password', 'new password');
-// Disable Account
-await client.user.disableAccount('password');
-// Delete Account [WARNING] Cannot be changed once used!
-await client.user.deleteAccount('password');
-// Redeem Nitro
-await client.redeemNitro('code')
-```
-
diff --git a/Document/VoiceCall.md b/Document/VoiceCall.md
deleted file mode 100644
index 8f02383..00000000
--- a/Document/VoiceCall.md
+++ /dev/null
@@ -1,628 +0,0 @@
-# Setup
-- Before you use it, properly initialize the module (`@discordjs/voice` patch)
-
-```js
-new Client({
- patchVoice: true,
-})
-```
-
-# Usage: Call DM / Group DM
-
-```js
-const dmChannel = client.channels.cache.get('id');
-/* or
-const dmChannel = User.dmChannel || await User.createDM();
-*/
-const connection = await dmChannel.call();
-/* Return @discordjs/voice VoiceConnection */
-```
-
-# Play Music using `play-dl`
-
-```js
-const play = require('play-dl');
-const {
- createAudioPlayer,
- createAudioResource,
- NoSubscriberBehavior,
-} = require('@discordjs/voice');
-const channel = (await (message.member.user.dmChannel || message.member.user.createDM()));
-const connection = channel.voiceConnection || await channel.call();
-let stream;
-if (!args[0]) {
- return message.channel.send('Enter something to search for.');
-} else if (args[0].startsWith('https://www.youtube.com/watch?v=')) {
- stream = await play.stream(args.join(' '));
-} else {
- const yt_info = await play.search(args, {
- limit: 1
- });
- stream = await play.stream(yt_info[0].url);
-}
-const resource = createAudioResource(stream.stream, {
- inputType: stream.type,
- inlineVolume: true,
-});
-resource.volume.setVolume(0.25);
-const player = createAudioPlayer({
- behaviors: {
- noSubscriber: NoSubscriberBehavior.Play,
- },
-});
-let i = setInterval(() => {
- const m = channel.voiceUsers.get(message.author.id);
- if (m) {
- player.play(resource);
- connection.subscribe(player);
- clearInterval(i);
- }
- else console.log('waiting for voice connection');
-}, 250);
-```
-
-# Play Music with module (support Play, Pause, Search, Skip, Previous, Volume, Loop)
-
-```js
-/* Copyright aiko-chan-ai @2022. All rights reserved. */
-const DjsVoice = require('@discordjs/voice');
-const Discord = require('discord.js-selfbot-v13');
-const playDL = require('play-dl');
-const EventEmitter = require('events');
-const Event = {
- READY: 'ready',
- NO_SEARCH_RESULT: 'searchNoResult',
- SEARCH_RESULT: 'searchResult',
- PLAY_SONG: 'playSong',
- ADD_SONG: 'addSong',
- ADD_PLAYLIST: 'addPlaylist',
- LEAVE_VC: 'disconnect',
- FINISH: 'finish',
- EMPTY: 'empty',
- ERROR: 'error',
-}
-class Stack {
- constructor() {
- this.data = [];
- }
-
- push(item) {
- return this.data.push(item);
- }
-
- pop() {
- return this.data.pop();
- }
-
- peek() {
- return this.data[this.length - 1];
- }
-
- get length() {
- return this.data.length;
- }
-
- isEmpty() {
- return this.length === 0;
- }
-
- reset () {
- this.data = [];
- return this;
- }
-}
-
-class Queue {
- constructor() {
- this.data = [];
- }
-
- enqueue(item) {
- return this.data.unshift(item);
- }
-
- dequeue() {
- return this.data.pop();
- }
-
- peek() {
- return this.data[this.length - 1];
- }
-
- get length() {
- return this.data.length;
- }
-
- isEmpty() {
- return this.data.length === 0;
- }
-
- reset() {
- this.data = [];
- return this;
- }
-}
-class Player extends EventEmitter {
- constructor(client, options = {}) {
- super();
- if (!client || !client instanceof Discord.Client) throw new Error('Invalid Discord Client (Selfbot)');
- Object.defineProperty(this, 'client', { value: client });
- this._playDl = playDL;
- this._queue = new Queue();
- this._previousSongs = new Stack();
- this.song = null;
- this.guild = undefined;
- this.channel = undefined;
- this._currentResourceAudio = undefined;
- this._currentTime = 0;
- this._playingTime = null;
- this.isPlaying = false;
- this.volume = 100;
- this.loopMode = 0;
- this.message = undefined;
- this._timeoutEmpty = undefined;
- this._player = DjsVoice.createAudioPlayer({
- behaviors: {
- noSubscriber: DjsVoice.NoSubscriberBehavior.Play,
- },
- });
- this._playerEvent();
- this._validateOptions(options);
- this._discordEvent();
- this._privateEvent();
- }
- get currentTime() {
- return this._currentTime || Date.now() - this._playingTime;
- }
- get currentConnection() {
- return DjsVoice.getVoiceConnection(this.guild?.id || null);
- }
- get queue() {
- return this._queue.data;
- }
- get previousSongs() {
- return this._previousSongs.data;
- }
- authorization() {
- this._playDl.authorization();
- }
- _validateOptions(options) {
- if (typeof options !== 'object') throw new Error(`Invalid options type (Required: object, got: ${typeof options})`);
- this.options = {
- directLink: true,
- joinNewVoiceChannel: true,
- waitingUserToPlayInDMs: true,
- nsfw: false,
- leaveOnEmpty: true,
- leaveOnFinish: true,
- leaveOnStop: true,
- savePreviousSongs: true,
- emptyCooldown: 10_000,
- }
- if (typeof options.directLink === 'boolean') {
- this.options.directLink = options.directLink;
- }
- if (typeof options.joinNewVoiceChannel === 'boolean') {
- this.options.joinNewVoiceChannel = options.joinNewVoiceChannel;
- }
- if (typeof options.waitingUserToPlayInDMs === 'boolean') {
- this.options.waitingUserToPlayInDMs = options.waitingUserToPlayInDMs;
- }
- if (typeof options.nsfw === 'boolean') {
- this.options.nsfw = options.nsfw;
- }
- if (typeof options.leaveOnEmpty === 'boolean') {
- if (typeof options.emptyCooldown === 'number') {
- this.options.leaveOnEmpty = options.leaveOnEmpty;
- this.options.emptyCooldown = options.emptyCooldown;
- } else {
- this.options.leaveOnEmpty = false;
- }
- }
- if (typeof options.leaveOnFinish === 'boolean') {
- this.options.leaveOnFinish = options.leaveOnFinish;
- }
- if (typeof options.leaveOnStop === 'boolean') {
- this.options.leaveOnStop = options.leaveOnStop;
- }
- if (typeof options.savePreviousSongs === 'boolean') {
- this.options.savePreviousSongs = options.savePreviousSongs;
- }
- }
- async play(options = {}) {
- const {
- message,
- channel,
- query,
- } = options;
- if (!(message instanceof Discord.Message)) throw new Error(`Invalid message type (Required: Message, got: ${typeof message})`);
- if (channel &&
- (
- channel instanceof Discord.DMChannel ||
- channel instanceof Discord.PartialGroupDMChannel ||
- channel instanceof Discord.VoiceChannel ||
- channel instanceof Discord.StageChannel
- )
- ) {
- let checkChangeVC = false;
- if (!this.channel) this.channel = channel;
- else {
- if (this.options.joinNewVoiceChannel) {
- if (this.channel.id !== channel.id) checkChangeVC = true;
- this.channel = channel;
- }
- }
- this.guild = channel.guild;
- this.message = message;
- if (typeof query !== 'string') throw new Error(`Invalid query type (Required: string, got: ${typeof query})`);
- const result = await this.search(message, query);
- if (result.length < 1) {
- throw new Error('No search result with the given query: ' + query);
- } else {
- for (let i = 0; i < result.length; i++) {
- this._queue.enqueue(result[i]);
- }
- if (!this.isPlaying) {
- this._skip(checkChangeVC);
- } else if (!result[0].playlist) {
- this.emit(Event.ADD_SONG, result[0]);
- }
- }
- } else {
- throw new Error(`Invalid channel. Make sure the channel is a DMChannel | PartialGroupDMChannel | VoiceChannel | StageChannel.`);
- }
- }
- async search(message, query, limit = 1) {
- if (!(message instanceof Discord.Message)) throw new Error(`Invalid message type (Required: Message, got: ${typeof message})`);
- if (typeof query !== 'string') throw new Error(`Invalid query type (Required: string, got: ${typeof query})`);
- if (typeof limit !== 'number') throw new Error(`Invalid limit type (Required: number, got: ${typeof limit})`);
- if (limit < 1) {
- limit = 1;
- process.emitWarning(`Invalid limit value (Required: 1 or more, got: ${limit})`);
- };
- if (limit > 10) {
- limit = 10;
- process.emitWarning(`Invalid limit value (Required: 10 or less, got: ${limit})`);
- };
- if (/^(https?\:\/\/)?(www\.youtube\.com|youtu\.be)\/.+$/.test(query)) {
- const validateData = this._playDl.yt_validate(query);
- if (validateData == 'video') {
- const result = await this._playDl.video_info(query);
- return [result.video_details];
- } else if (validateData == 'playlist') {
- const result = await this._playDl.playlist_info(query);
- this.emit(Event.ADD_PLAYLIST, result);
- const allVideo = await result.all_videos();
- return allVideo.map(video => {
- Object.defineProperty(video, 'playlist', { value: result });
- return video;
- });
- } else {
- return this.emit(Event.ERROR, new Error('Invalid YouTube URL: ' + query));
- }
- } else {
- const result = await this._playDl.search(query, {
- limit,
- unblurNSFWThumbnails: this.options.nsfw,
- });
- if (result.length < 1) {
- this.emit(Event.NO_SEARCH_RESULT, message, query, limit);
- return [];
- } else {
- this.emit(Event.SEARCH_RESULT, message, result, query, limit);
- return result;
- }
- }
- }
- setLoopMode(mode) {
- if ([0, 1, 2].includes(mode)) {
- this._loopMode = mode;
- } else {
- throw new Error(`Invalid mode value (Required: 0 [No loop], 1 [Loop song], 2 [Loop queue], got: ${mode})`);
- }
- }
- async createStream(url) {
- const stream = await this._playDl.stream(url);
- const resource = DjsVoice.createAudioResource(stream.stream, {
- inputType: stream.type,
- inlineVolume: true,
- });
- this._currentResourceAudio = resource;
- this.setVolume(this.volume);
- }
- _play() {
- this._player.play(this._currentResourceAudio);
- }
- setVolume(volume) {
- if (!this._currentResourceAudio) throw new Error('No current resource audio');
- if (typeof volume !== 'number') throw new Error(`Invalid volume type (Required: number, got: ${typeof volume})`);
- if (volume < 0) {
- volume = 0;
- process.emitWarning(`Invalid volume value (Required: 0 or more, got: ${volume})`);
- } else if (volume > 100) {
- volume = 100;
- process.emitWarning(`Invalid volume value (Required: 100 or less, got: ${volume})`);
- }
- this._currentResourceAudio.volume.setVolume((volume / 100).toFixed(2));
- this.volume = (volume / 100).toFixed(2) * 100;
- return (volume / 100).toFixed(2) * 100;
- }
- pause() {
- if (!this._currentResourceAudio) throw new Error('No current resource audio');
- this._player.pause();
- }
- resume() {
- if (!this._currentResourceAudio) throw new Error('No current resource audio');
- this._player.unpause();
- }
- stop() {
- if (!this._currentResourceAudio) throw new Error('No current resource audio');
- this._stop(false, this.options.leaveOnStop);
- }
- _reset(){
- this._currentTime = 0;
- this._currentResourceAudio = null;
- this._playingTime = 0;
- this.isPlaying = false;
- this._player.stop();
- }
- _stop(finish = false, force = false) {
- if (!this._currentResourceAudio) return;
- this._queue.reset();
- this._previousSongs.reset();
- this._timeoutEmpty = undefined;
- this._reset();
- if (force || finish && this.options.leaveOnFinish) this.currentConnection?.destroy();
- this.channel = null;
- this.guild = null;
- this.song = null;
- this.volume = 100;
- this.loopMode = 0;
- this.message = null;
- }
- skip() {
- this._skip();
- }
- async _skip(checkChangeVC = false) {
- if (!this._queue.length) throw new Error('No song in the queue');
- const currentSong = this.song;
- if (this.loopMode == 0) {
- if (this.options.savePreviousSongs) this._previousSongs.push(currentSong);
- const nextSong = this._queue.dequeue();
- this.song = nextSong;
- } else if (this.loopMode == 1) {
- this.song = currentSong;
- } else if (this.loopMode == 2) {
- this._queue.enqueue(currentSong);
- const nextSong = this._queue.dequeue();
- this.song = nextSong;
- }
- await this.createStream(this.song.url);
- await this.joinVC(checkChangeVC);
- this.emit(Event.PLAY_SONG, this.song);
- if (!this.guild?.id) await this._awaitDM();
- this._play();
- this._playingTime = Date.now();
- }
- async previous() {
- if (!this._previousSongs.length) throw new Error('No previous songs');
- const currentSong = this.song;
- // add to queue
- this._queue.enqueue(currentSong);
- const previousSong = this._previousSongs.pop();
- this.song = previousSong;
- await this.createStream(this.song.url);
- await this.joinVC();
- this._play();
- this.emit(Event.PLAY_SONG, this.song, this.queue);
- }
- async joinVC(changeVC = false) {
- if (this.currentConnection && !changeVC) {
- this.currentConnection.subscribe(this._player);
- } else if (this.channel instanceof Discord.VoiceChannel) {
- const connection = DjsVoice.joinVoiceChannel({
- channelId: this.channel.id,
- guildId: this.guild.id,
- adapterCreator: this.guild.voiceAdapterCreator,
- });
- await DjsVoice.entersState(
- connection,
- DjsVoice.VoiceConnectionStatus.Ready,
- 10_000,
- );
- connection.subscribe(this._player);
- } else if (this.channel instanceof Discord.StageChannel) {
- const connection = DjsVoice.joinVoiceChannel({
- channelId: this.channel.id,
- guildId: this.guild.id,
- adapterCreator: this.guild.voiceAdapterCreator,
- });
- await DjsVoice.entersState(
- connection,
- DjsVoice.VoiceConnectionStatus.Ready,
- 10_000,
- );
- connection.subscribe(this._player);
- await this.channel.guild.members.me.voice
- .setSuppressed(false)
- .catch(async () => {
- return await this.channel.guild.members.me.voice
- .setRequestToSpeak(true);
- });
- } else {
- const connection = this.channel.voiceConnection || await this.channel.call();
- connection.subscribe(this._player);
- }
- }
- _discordEvent() {
- // Event sus .-.
- this.client.on('voiceStateUpdate', (oldState, newState) => {
- if (!this._currentResourceAudio) return;
- if (newState.guild?.id == this.guild?.id) {
- if (oldState.channel?.id !== newState.channel?.id && oldState.channel?.id && newState.channel?.id && newState.id == this.client.user.id) {
- // change vc
- }
- if (newState.id == this.client.user.id && oldState.channel?.members?.has(this.client.user.id) && !newState.channel?.members?.has(this.client.user.id)) {
- this._stop();
- this.emit(Event.LEAVE_VC, this.message);
- }
- if (newState.channel?.members?.has(this.client.user.id) && !newState.channel?.members?.filter(m => m.id != this.client.user.id && !m.bot).size) {
- // empty
- if (this.options.leaveOnEmpty && !this._timeoutEmpty) {
- this._timeoutEmpty = setTimeout(() => {
- this._stop(false, true);
- this.emit(Event.EMPTY, this.message);
- }, this.options.emptyCooldown);
- }
- }
- if (newState.channel?.members?.has(this.client.user.id) && newState.channel?.members?.filter(m => m.id != this.client.user.id && !m.bot).size > 0) {
- // not empty
- if (this._timeoutEmpty) clearTimeout(this._timeoutEmpty);
- this._timeoutEmpty = undefined;
- }
- } else if (!this.guild?.id && !newState.guild?.id) {
- // DM channels
- if (!newState.channel?.voiceUsers?.filter(m => m.id != this.client.user.id).size) {
- // empty
- if (this.options.leaveOnEmpty && !this._timeoutEmpty) {
- this._timeoutEmpty = setTimeout(() => {
- this._stop(false, true);
- this.emit(Event.EMPTY, this.message);
- }, this.options.emptyCooldown);
- }
- }
- if (newState.channel?.voiceUsers?.filter(m => m.id != this.client.user.id).size > 0) {
- // not empty
- if (this._timeoutEmpty) clearTimeout(this._timeoutEmpty);
- this._timeoutEmpty = undefined;
- }
- }
- });
- }
- _awaitDM () {
- if (!this.options.waitingUserToPlayInDMs) return true;
- return new Promise(resolve => {
- let i = setInterval(() => {
- const m = this.channel.voiceUsers.get(this.client.user.id);
- if (m) {
- clearInterval(i);
- resolve(true);
- }
- }, 250);
- })
- }
- _privateEvent() {
- this.on('next_song', async () => {
- await this._skip().catch(() => {
- if (this.message) this.emit(Event.FINISH, this.message);
- this._reset();
- });
- });
- }
- _playerEvent() {
- const player = this._player;
- player.on('stateChange', async (oldState, newState) => {
- // idle -> playing
- // idle -> buffering
- // buffering -> playing
- // playing -> idle
- if (newState.status.toLowerCase() == 'idle') {
- this.isPlaying = false;
- } else if (newState.status.toLowerCase() == 'paused' || newState.status.toLowerCase() == 'autopaused') {
- this.isPlaying = false;
- } else {
- this.isPlaying = true;
- }
- this._currentTime = newState.playbackDuration;
- //
- if (oldState.status == 'playing' && newState.status == 'idle') {
- this.emit('next_song');
- }
- });
- player.on('error', (e) => {
- this.emit(Event.ERROR, e);
- });
- }
-}
-
-module.exports = Player;
-
-/* Example
-const player = new Player(client, options);
-
-player
- .on('playSong', song => {
- player.message.channel.send(`Now playing: ${song.title}`);
- })
- .on('addSong', song => {
- player.message.channel.send(`Added: ${song.title}`);
- })
- .on('addPlaylist', playlist => {
- player.message.channel.send(`Added Playlist: ${playlist.title}`);
- })
- .on('disconnect', (message) => {
- message.channel.send('Disconnected from voice channel.');
- })
- .on('finish', (message) => {
- message.channel.send('Finished playing.');
- })
- .on('empty', (message) => {
- message.channel.send('The queue is empty.');
- })
- .on('error', error => {
- console.log('Music error', error);
- })
-
-client.player = player;
-
-// Method
-
-client.player.play({
- message,
- channel: message.member.voice.channel, // VoiceChannel | DMChannel | StageChannel | GroupDMChannel
- query: string,
-});
-
-client.player.skip();
-
-client.player.previous();
-
-client.player.pause();
-
-client.player.resume();
-
-client.player.setVolume(50); // 50%
-
-client.player.setLoopMode(1); // 0: none, 1: song, 2: queue;
-
-client.player.stop();
-
-// Options
-
-options = {
- directLink: true, // Whether or not play direct link of the song (not support)
- joinNewVoiceChannel: true, // Whether or not joining the new voice channel when using #play method
- waitingUserToPlayInDMs: true, // Waiting User join Call to play in DM channels
- nsfw: false, // Whether or not play NSFW
- leaveOnEmpty: true, // Whether or not leave voice channel when empty (not working)
- leaveOnFinish: true, // Whether or not leave voice channel when finish
- leaveOnStop: true, // Whether or not leave voice channel when stop
- savePreviousSongs: true, // Whether or not save previous songs
- emptyCooldown: 10_000, // Cooldown when empty voice channel
-}
-
-// Properties
-
-song = Song;
-guild = Discord.Guild;
-channel = Channel;
-client = Discord.Client;
-isPlaying = false;
-volume = 100;
-currentTime = Unix timestamp miliseconds;
-currentConnection = VoiceConnection;
-queue: Song[];
-previousSongs: Song[];
-loopMode = 0;
-*/
-```
diff --git a/README.md b/README.md
index 3c8ab27..e4dbf45 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
## About
-Welcome to `discord.js-selfbot-v13@v2.15`, based on `discord.js@13.17`
+Welcome to `discord.js-selfbot-v13@v3.0`, based on `discord.js@13.17`
- discord.js-selfbot-v13 is a [Node.js](https://nodejs.org) module that allows user accounts to interact with the Discord API v9.
@@ -57,7 +57,7 @@
**Node.js 16.6.0 or newer is required**
-> Recommended Node.js version: 18 (LTS)
+> Recommended Node.js version: 18+ (LTS)
```sh-session
npm install discord.js-selfbot-v13@latest
@@ -106,9 +106,6 @@ console.log('%cWorked!', 'font-size: 50px');
console.log(`%cYou now have your token in the clipboard!`, 'font-size: 16px');
```
-Credit: [hxr404](https://github.com/hxr404/Discord-Console-hacks)
-
-
## Contributing
- Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
@@ -127,7 +124,6 @@ Github Discussion: [Here](https://github.com/aiko-chan-ai/discord.js-selfbot-v13
A patched version of discord, with bot login support
- 📕 [***aiko-chan-ai/Discord-Markdown***](https://github.com/aiko-chan-ai/Discord-Markdown)
Better Markdown to text chat Discord.
-- 📗 ...
## Star History
diff --git a/package.json b/package.json
index cf1bbc7..743a76c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "discord.js-selfbot-v13",
- "version": "2.15.1",
+ "version": "3.0.0",
"description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]",
"main": "./src/index.js",
"types": "./typings/index.d.ts",
@@ -54,7 +54,6 @@
"@aikochan2k6/qrcode-terminal": "^0.12.1",
"@discordjs/builders": "^1.6.3",
"@discordjs/collection": "^1.5.3",
- "@discordjs/voice": "^0.16.0",
"@sapphire/async-queue": "^1.5.0",
"@sapphire/shapeshift": "^3.9.3",
"@types/node-fetch": "^2.6.7",
@@ -62,12 +61,7 @@
"discord-api-types": "^0.37.61",
"fetch-cookie": "^2.1.0",
"form-data": "^4.0.0",
- "json-bigint": "^1.0.0",
- "lodash.permutations": "^1.0.0",
"node-fetch": "^2.6.9",
- "safe-base64": "^2.0.1-0",
- "string-similarity": "^4.0.4",
- "string_decoder": "^1.3.0",
"tough-cookie": "^4.1.3",
"ws": "^8.14.2"
},
diff --git a/src/client/Client.js b/src/client/Client.js
index 8459cb3..f520433 100644
--- a/src/client/Client.js
+++ b/src/client/Client.js
@@ -1,46 +1,40 @@
'use strict';
const process = require('node:process');
-const { setInterval, setTimeout } = require('node:timers');
+const { setInterval } = require('node:timers');
+const { setTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
-const { getVoiceConnection } = require('@discordjs/voice');
-const chalk = require('chalk');
-const fetch = require('node-fetch');
const BaseClient = require('./BaseClient');
const ActionsManager = require('./actions/ActionsManager');
const ClientVoiceManager = require('./voice/ClientVoiceManager');
const WebSocketManager = require('./websocket/WebSocketManager');
const { Error, TypeError, RangeError } = require('../errors');
-const Discord = require('../index');
const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager');
const BillingManager = require('../managers/BillingManager');
const ChannelManager = require('../managers/ChannelManager');
-const ClientUserSettingManager = require('../managers/ClientUserSettingManager');
-const DeveloperPortalManager = require('../managers/DeveloperPortalManager');
const GuildManager = require('../managers/GuildManager');
+const PresenceManager = require('../managers/PresenceManager');
const RelationshipManager = require('../managers/RelationshipManager');
-const SessionManager = require('../managers/SessionManager');
const UserManager = require('../managers/UserManager');
+const UserNoteManager = require('../managers/UserNoteManager');
const VoiceStateManager = require('../managers/VoiceStateManager');
const ShardClientUtil = require('../sharding/ShardClientUtil');
const ClientPresence = require('../structures/ClientPresence');
const GuildPreview = require('../structures/GuildPreview');
const GuildTemplate = require('../structures/GuildTemplate');
const Invite = require('../structures/Invite');
-const { CustomStatus } = require('../structures/RichPresence');
const { Sticker } = require('../structures/Sticker');
const StickerPack = require('../structures/StickerPack');
const VoiceRegion = require('../structures/VoiceRegion');
const Webhook = require('../structures/Webhook');
const Widget = require('../structures/Widget');
-const { Events, InviteScopes, Status, captchaServices } = require('../util/Constants');
+const { Events, Status } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Intents = require('../util/Intents');
const Options = require('../util/Options');
const Permissions = require('../util/Permissions');
const DiscordAuthWebsocket = require('../util/RemoteAuth');
const Sweepers = require('../util/Sweepers');
-const { getProxyObject } = require('../util/Util');
/**
* The main hub for interacting with the Discord API, and the starting point for any bot.
@@ -50,7 +44,7 @@ class Client extends BaseClient {
/**
* @param {ClientOptions} options Options for the client
*/
- constructor(options = {}) {
+ constructor(options) {
super(options);
const data = require('node:worker_threads').workerData ?? process.env;
@@ -141,17 +135,6 @@ class Client extends BaseClient {
*/
this.users = new UserManager(this);
- // Patch
- /**
- * All of the relationships {@link User}
- * @type {RelationshipManager}
- */
- this.relationships = new RelationshipManager(this);
- /**
- * All of the settings {@link Object}
- * @type {ClientUserSettingManager}
- */
- this.settings = new ClientUserSettingManager(this);
/**
* All of the guilds the client is currently handling, mapped by their ids -
* as long as sharding isn't being used, this will be *every* guild the bot is a member of
@@ -159,18 +142,6 @@ class Client extends BaseClient {
*/
this.guilds = new GuildManager(this);
- /**
- * Manages the API methods
- * @type {BillingManager}
- */
- this.billing = new BillingManager(this);
-
- /**
- * All of the sessions of the client
- * @type {SessionManager}
- */
- this.sessions = new SessionManager(this);
-
/**
* All of the {@link Channel}s that the client is currently handling, mapped by their ids -
* as long as sharding isn't being used, this will be *every* channel in *every* guild the bot
@@ -186,12 +157,6 @@ class Client extends BaseClient {
*/
this.sweepers = new Sweepers(this, this.options.sweepers);
- /**
- * The developer portal manager of the client
- * @type {DeveloperPortalManager}
- */
- this.developerPortal = new DeveloperPortalManager(this);
-
/**
* The presence of the Client
* @private
@@ -199,6 +164,30 @@ class Client extends BaseClient {
*/
this.presence = new ClientPresence(this, this.options.presence);
+ /**
+ * A manager of the presences belonging to this client
+ * @type {PresenceManager}
+ */
+ this.presences = new PresenceManager(this);
+
+ /**
+ * All of the note that have been cached at any point, mapped by their ids
+ * @type {UserManager}
+ */
+ this.notes = new UserNoteManager(this);
+
+ /**
+ * All of the relationships {@link User}
+ * @type {RelationshipManager}
+ */
+ this.relationships = new RelationshipManager(this);
+
+ /**
+ * Manages the API methods
+ * @type {BillingManager}
+ */
+ this.billing = new BillingManager(this);
+
Object.defineProperty(this, 'token', { writable: true });
if (!this.token && 'DISCORD_TOKEN' in process.env) {
/**
@@ -212,20 +201,12 @@ class Client extends BaseClient {
this.token = null;
}
- this._interactionCache = new Collection();
-
/**
* User that the client is logged in as
* @type {?ClientUser}
*/
this.user = null;
- /**
- * The application of this bot
- * @type {?ClientApplication}
- */
- this.application = null;
-
/**
* Time at which the client was last regarded as being in the `READY` state
* (each time the client disconnects and successfully reconnects, this will be overwritten)
@@ -233,12 +214,6 @@ class Client extends BaseClient {
*/
this.readyAt = null;
- /**
- * Password cache
- * @type {?string}
- */
- this.password = this.options.password;
-
if (this.options.messageSweepInterval > 0) {
process.emitWarning(
'The message sweeping client options are deprecated, use the global sweepers instead.',
@@ -251,15 +226,6 @@ class Client extends BaseClient {
}
}
- /**
- * Session ID
- * @type {?string}
- * @readonly
- */
- get sessionId() {
- return this.ws.shards.first()?.sessionId;
- }
-
/**
* All custom emojis that the client has access to, mapped by their ids
* @type {BaseGuildEmojiManager}
@@ -291,19 +257,6 @@ class Client extends BaseClient {
return this.readyAt ? Date.now() - this.readyAt : null;
}
- /**
- * @external VoiceConnection
- * @see {@link https://discord.js.org/#/docs/voice/main/class/VoiceConnection}
- */
- /**
- * Get connection to current call
- * @type {?VoiceConnection}
- * @readonly
- */
- get callVoice() {
- return getVoiceConnection(null);
- }
-
/**
* Logs the client in, establishing a WebSocket connection to Discord.
* @param {string} [token=this.token] Token of the account to log in with
@@ -320,8 +273,7 @@ class Client extends BaseClient {
Logging on with a user token is unfortunately against the Discord
\`Terms of Service\`
and doing so might potentially get your account banned.
- Use this at your own risk.
-`,
+ Use this at your own risk.`,
);
this.emit(
Events.DEBUG,
@@ -346,178 +298,10 @@ class Client extends BaseClient {
}
}
- /**
- * Login Discord with Username and Password
- * @param {string} username Email or Phone Number
- * @param {?string} password Password
- * @param {?string} mfaCode 2FA Code / Backup Code
- * @returns {Promise}
- */
- async normalLogin(username, password = this.password, mfaCode) {
- if (!username || !password || typeof username !== 'string' || typeof password !== 'string') {
- throw new Error('NORMAL_LOGIN');
- }
- this.emit(
- Events.DEBUG,
- `Connecting to Discord with:
- username: ${username}
- password: ${password.replace(/./g, '*')}`,
- );
- const data = await this.api.auth.login.post({
- data: {
- login: username,
- password: password,
- undelete: false,
- captcha_key: null,
- login_source: null,
- gift_code_sku_id: null,
- },
- auth: false,
- });
- this.password = password;
- if (!data.token && data.ticket && data.mfa) {
- this.emit(Events.DEBUG, `Using 2FA Code: ${mfaCode}`);
- const normal2fa = /(\d{6})/g;
- const backupCode = /([a-z0-9]{4})-([a-z0-9]{4})/g;
- if (!mfaCode || typeof mfaCode !== 'string') {
- throw new Error('LOGIN_FAILED_2FA');
- }
- if (normal2fa.test(mfaCode) || backupCode.test(mfaCode)) {
- const data2 = await this.api.auth.mfa.totp.post({
- data: {
- code: mfaCode,
- ticket: data.ticket,
- login_source: null,
- gift_code_sku_id: null,
- },
- auth: false,
- });
- return this.login(data2.token);
- } else {
- throw new Error('LOGIN_FAILED_2FA');
- }
- } else if (data.token) {
- return this.login(data.token);
- } else {
- throw new Error('LOGIN_FAILED_UNKNOWN');
- }
- }
-
- /**
- * Switch the user
- * @param {string} token User Token
- * @returns {Promise}
- */
- switchUser(token) {
- this._clearCache(this.emojis.cache);
- this._clearCache(this.guilds.cache);
- this._clearCache(this.channels.cache);
- this._clearCache(this.users.cache);
- this._clearCache(this.relationships.cache);
- this._clearCache(this.sessions.cache);
- this._clearCache(this.voiceStates.cache);
- this.ws.status = Status.IDLE;
- return this.login(token);
- }
-
- /**
- * Sign in with the QR code on your phone.
- * @param {DiscordAuthWebsocketOptions} options Options
- * @returns {DiscordAuthWebsocket}
- * @example
- * client.QRLogin();
- */
- QRLogin(options = {}) {
- const QR = new DiscordAuthWebsocket({ ...options, autoLogin: true });
- this.emit(Events.DEBUG, `Preparing to connect to the gateway (QR Login)`, QR);
- return QR.connect(this);
- }
-
- /**
- * Implement `remoteAuth`, like using your phone to scan a QR code
- * @param {string} url URL from QR code
- * @returns {Promise}
- */
- async remoteAuth(url) {
- 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
- return this.api.users['@me']['remote-auth'].finish.post({ data: { handshake_token, temporary_token: false } });
- // Cancel
- // this.api.users['@me']['remote-auth'].cancel.post({ data: { handshake_token } });
- }
-
- /**
- * Create a new token based on the current token
- * @returns {Promise} New Discord Token
- */
- createToken() {
- return new Promise((resolve, reject) => {
- // Step 1: Create DiscordAuthWebsocket
- const QR = new DiscordAuthWebsocket({
- hiddenLog: true,
- generateQR: false,
- autoLogin: false,
- debug: false,
- failIfError: false,
- userAgent: this.options.http.headers['User-Agent'],
- wsProperties: this.options.ws.properties,
- });
- // Step 2: Add event
- QR.once('ready', async (_, url) => {
- try {
- await this.remoteAuth(url);
- } catch (e) {
- reject(e);
- }
- }).once('finish', (user, token) => {
- resolve(token);
- });
- // Step 3: Connect
- QR.connect();
- });
- }
-
- /**
- * Emitted whenever clientOptions.checkUpdate = false
- * @event Client#update
- * @param {string} oldVersion Current version
- * @param {string} newVersion Latest version
- */
-
- /**
- * Check for updates
- * @returns {Promise}
- */
- async checkUpdate() {
- const res_ = await (
- await fetch(`https://registry.npmjs.com/${encodeURIComponent('discord.js-selfbot-v13')}`)
- ).json();
- try {
- const latest_tag = res_['dist-tags'].latest;
- this.emit('update', Discord.version, latest_tag);
- this.emit('debug', `${chalk.greenBright('[OK]')} Check Update success`);
- } catch {
- this.emit('debug', `${chalk.redBright('[Fail]')} Check Update error`);
- this.emit('update', Discord.version, false);
- }
- return this;
+ QRLogin() {
+ const ws = new DiscordAuthWebsocket();
+ ws.once('ready', () => ws.generateQR());
+ return ws.connect(this);
}
/**
@@ -544,7 +328,6 @@ class Client extends BaseClient {
this.sweepers.destroy();
this.ws.destroy();
this.token = null;
- this.password = null;
}
/**
@@ -558,7 +341,7 @@ class Client extends BaseClient {
voip_provider: null,
},
});
- await this.destroy();
+ return this.destroy();
}
/**
@@ -586,51 +369,6 @@ class Client extends BaseClient {
return new Invite(this, data);
}
- /**
- * Join this Guild using this invite (fast)
- * @param {InviteResolvable} invite Invite code or URL
- * @returns {Promise}
- * @example
- * await client.acceptInvite('https://discord.gg/genshinimpact')
- */
- async acceptInvite(invite) {
- const code = DataResolver.resolveInviteCode(invite);
- if (!code) throw new Error('INVITE_RESOLVE_CODE');
- if (invite instanceof Invite) {
- await invite.acceptInvite();
- } else {
- await this.api.invites(code).post({
- headers: {
- 'X-Context-Properties': 'eyJsb2NhdGlvbiI6Ik1hcmtkb3duIExpbmsifQ==', // Markdown Link
- },
- data: {
- session_id: this.sessionId,
- },
- });
- }
- }
-
- /**
- * Redeem nitro from code or url.
- * @param {string} nitro Nitro url or code
- * @param {TextChannelResolvable} channel Channel that the code was sent in
- * @param {Snowflake} [paymentSourceId] Payment source id
- * @returns {Promise}
- */
- redeemNitro(nitro, channel, paymentSourceId) {
- if (typeof nitro !== 'string') throw new Error('INVALID_NITRO');
- const nitroCode =
- nitro.match(/(discord.gift|discord.com|discordapp.com\/gifts)\/(\w{16,25})/) ||
- nitro.match(/(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)(\w+)/);
- if (!nitroCode) return false;
- const code = nitroCode[2];
- channel = this.channels.resolveId(channel);
- return this.api.entitlements['gift-codes'](code).redeem.post({
- auth: true,
- data: { channel_id: channel || null, payment_source_id: paymentSourceId || null },
- });
- }
-
/**
* Obtains a template from Discord.
* @param {GuildTemplateResolvable} template Template code or URL
@@ -721,16 +459,6 @@ class Client extends BaseClient {
}
}
- /**
- * Clear a cache
- * @param {Collection} cache The cache to clear
- * @returns {number} The number of removed entries
- * @private
- */
- _clearCache(cache) {
- return cache.sweep(() => true);
- }
-
/**
* Sweeps all text-based channels' messages and removes the ones older than the max message lifetime.
* If the message has been edited, the time of the edit is used rather than the time of the original message.
@@ -791,65 +519,13 @@ class Client extends BaseClient {
*/
/**
- * Generates a link that can be used to invite the bot to a guild.
- * @param {InviteGenerationOptions} [options={}] Options for the invite
- * @returns {string}
- * @example
- * const link = client.generateInvite({
- * scopes: ['applications.commands'],
- * });
- * console.log(`Generated application invite link: ${link}`);
- * @example
- * const link = client.generateInvite({
- * permissions: [
- * Permissions.FLAGS.SEND_MESSAGES,
- * Permissions.FLAGS.MANAGE_GUILD,
- * Permissions.FLAGS.MENTION_EVERYONE,
- * ],
- * scopes: ['bot'],
- * });
- * console.log(`Generated bot invite link: ${link}`);
+ * The sleep function in JavaScript returns a promise that resolves after a specified timeout.
+ * @param {number} timeout - The timeout parameter is the amount of time, in milliseconds, that the sleep
+ * function will wait before resolving the promise and continuing execution.
+ * @returns {void} The `sleep` function is returning a Promise.
*/
- generateInvite(options = {}) {
- if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true);
- if (!this.application) throw new Error('CLIENT_NOT_READY', 'generate an invite link');
-
- const query = new URLSearchParams({
- client_id: this.application.id,
- });
-
- const { scopes } = options;
- if (typeof scopes === 'undefined') {
- throw new TypeError('INVITE_MISSING_SCOPES');
- }
- if (!Array.isArray(scopes)) {
- throw new TypeError('INVALID_TYPE', 'scopes', 'Array of Invite Scopes', true);
- }
- if (!scopes.some(scope => ['bot', 'applications.commands'].includes(scope))) {
- throw new TypeError('INVITE_MISSING_SCOPES');
- }
- const invalidScope = scopes.find(scope => !InviteScopes.includes(scope));
- if (invalidScope) {
- throw new TypeError('INVALID_ELEMENT', 'Array', 'scopes', invalidScope);
- }
- query.set('scope', scopes.join(' '));
-
- if (options.permissions) {
- const permissions = Permissions.resolve(options.permissions);
- if (permissions) query.set('permissions', permissions);
- }
-
- if (options.disableGuildSelect) {
- query.set('disable_guild_select', true);
- }
-
- if (options.guild) {
- const guildId = this.guilds.resolveId(options.guild);
- if (!guildId) throw new TypeError('INVALID_TYPE', 'options.guild', 'GuildResolvable');
- query.set('guild_id', guildId);
- }
-
- return `${this.options.http.api}${this.api.oauth2.authorize}?${query}`;
+ sleep(timeout) {
+ return new Promise(r => setTimeout(r, timeout));
}
toJSON() {
@@ -859,41 +535,46 @@ class Client extends BaseClient {
}
/**
- * Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script
- * with the client as `this`.
- * @param {string} script Script to eval
- * @returns {*}
- * @private
+ * Join this Guild using this invite (fast)
+ * @param {InviteResolvable} invite Invite code or URL
+ * @returns {Promise}
+ * @example
+ * await client.acceptInvite('https://discord.gg/genshinimpact')
*/
- _eval(script) {
- return eval(script);
+ async acceptInvite(invite) {
+ const code = DataResolver.resolveInviteCode(invite);
+ if (!code) throw new Error('INVITE_RESOLVE_CODE');
+ if (invite instanceof Invite) {
+ await invite.acceptInvite();
+ } else {
+ await this.api.invites(code).post({
+ DiscordContext: { location: 'Markdown Link' },
+ data: {
+ session_id: this.ws.shards.first()?.sessionId,
+ },
+ });
+ }
}
/**
- * Sets the client's presence. (Sync Setting).
- * @param {Client} client Discord Client
- * @private
+ * Redeem nitro from code or url.
+ * @param {string} nitro Nitro url or code
+ * @param {TextChannelResolvable} [channel] Channel that the code was sent in
+ * @param {Snowflake} [paymentSourceId] Payment source id
+ * @returns {Promise}
*/
- customStatusAuto(client) {
- client = client ?? this;
- if (!client.user) return;
- const custom_status = new CustomStatus();
- if (!client.settings.rawSetting.custom_status?.text && !client.settings.rawSetting.custom_status?.emoji_name) {
- client.user.setPresence({
- activities: this.presence.activities.filter(a => a.type !== 'CUSTOM'),
- status: client.settings.rawSetting.status ?? 'invisible',
- });
- } else {
- custom_status.setEmoji({
- name: client.settings.rawSetting.custom_status?.emoji_name,
- id: client.settings.rawSetting.custom_status?.emoji_id,
- });
- custom_status.setState(client.settings.rawSetting.custom_status?.text);
- client.user.setPresence({
- activities: [custom_status.toJSON(), ...this.presence.activities.filter(a => a.type !== 'CUSTOM')],
- status: client.settings.rawSetting.status ?? 'invisible',
- });
- }
+ redeemNitro(nitro, channel, paymentSourceId) {
+ if (typeof nitro !== 'string') throw new Error('INVALID_NITRO');
+ const nitroCode =
+ nitro.match(/(discord.gift|discord.com|discordapp.com\/gifts)\/(\w{16,25})/) ||
+ nitro.match(/(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)(\w+)/);
+ if (!nitroCode) return false;
+ const code = nitroCode[2];
+ channel = this.channels.resolveId(channel);
+ return this.api.entitlements['gift-codes'](code).redeem.post({
+ auth: true,
+ data: { channel_id: channel || null, payment_source_id: paymentSourceId || null },
+ });
}
/**
@@ -909,7 +590,7 @@ class Client extends BaseClient {
* Authorize an application.
* @param {string} url Discord Auth URL
* @param {OAuth2AuthorizeOptions} options Oauth2 options
- * @returns {Promise