This commit is contained in:
Elysia 2024-01-14 12:33:11 +07:00
parent e15b9ab7fe
commit 039dd34cf2
27 changed files with 1250 additions and 5297 deletions

View File

@ -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
### <strong>Assume you don't have one of the following permissions</strong>
> `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&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | 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...
<img src='https://cdn.discordapp.com/attachments/820557032016969751/1090606227265966140/image.png'>
`138k/13.8M (1%)` members fetched in `~30 mins` (with a delay of 500ms) :skull:

View File

@ -17,40 +17,10 @@
## Interaction ## Interaction
<details open> <details open>
<summary>Button Click</summary>
```js
await Button.click(Message);
//
await message.clickButton(buttonID);
//
await message.clickButton(); // first button
//
await message.clickButton({ row: 0, col: 0})
```
</details>
<details open>
<summary>Message Select Menu</summary>
```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
```
</details>
<details open>
<summary>Slash Command</summary> <summary>Slash Command</summary>
### [Click here](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/SlashCommand.md) ### [Click here](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/SlashCommand.md)
</details>
<details open>
<summary>Message Context Command</summary>
```js
await message.contextMenu(botID, commandName);
```
</details> </details>
<details open> <details open>
@ -64,13 +34,7 @@ await message.contextMenu(botID, commandName);
Code: Code:
```js ```js
const Discord = require('discord.js-selfbot-v13'); const Discord = require('discord.js-selfbot-v13');
// Selfhost WebEmbed: https://github.com/aiko-chan-ai/WebEmbed const w = new Discord.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
})
.setAuthor({ name: 'hello', url: 'https://google.com' }) .setAuthor({ name: 'hello', url: 'https://google.com' })
.setColor('RED') .setColor('RED')
.setDescription('description uh') .setDescription('description uh')
@ -83,7 +47,7 @@ const w = new Discord.WebEmbed({
.setVideo( .setVideo(
'https://cdn.discordapp.com/attachments/877060758092021801/957691816143097936/The_Quintessential_Quintuplets_And_Rick_Astley_Autotune_Remix.mp4', '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 ### Features & Issues

View File

@ -1,10 +1,3 @@
## Setup
```js
const client = new Client({
syncStatus: false,
});
```
## Custom Status and RPC ## Custom Status and RPC
<strong>Custom Status</strong> <strong>Custom Status</strong>
@ -68,57 +61,7 @@ client.user.setActivity(r);
> Tutorial: > Tutorial:
## Method 1: ## Method 1: (Discord URL, v2.3.78+)
+ Step 1: Send image to Discord
<img src='https://cdn.discordapp.com/attachments/820557032016969751/995297572732284968/unknown.png'>
+ Step 2: Get Image URL
<img src='https://cdn.discordapp.com/attachments/820557032016969751/995298082474426418/unknown.png'>
```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+)
```js ```js
const Discord = require('discord.js-selfbot-v13'); const Discord = require('discord.js-selfbot-v13');
@ -145,7 +88,7 @@ client.user.setActivity(r);
<img src='https://cdn.discordapp.com/attachments/820557032016969751/995301015257616414/unknown.png'> <img src='https://cdn.discordapp.com/attachments/820557032016969751/995301015257616414/unknown.png'>
## Method 3 (Custom URL, 2.3.78+) ## Method 2 (Custom URL, 2.3.78+)
```js ```js
const Discord = require('discord.js-selfbot-v13'); const Discord = require('discord.js-selfbot-v13');

View File

@ -42,10 +42,3 @@ await message.channel.sendSlash('718642000898818048', 'sauce', a)
### Result ### Result
![image](https://user-images.githubusercontent.com/71698422/173347075-5c8a1347-3845-489e-956b-63975911b6e0.png) ![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)

View File

@ -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
<details open>
<summary><strong>Click to show</strong></summary>
```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();
```
</details>
## Discord User Info
<details open>
<summary><strong>Click to show</strong></summary>
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
}
```
</details>
## Discord User Friend / Blocked
<details open>
<summary><strong>Click to show</strong></summary>
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
}
```
</details>
## User & ClientUser Method
<details open>
<summary><strong>Click to show</strong></summary>
```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')
```
</details>

View File

@ -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;
*/
```

View File

@ -14,7 +14,7 @@
## About ## About
<strong>Welcome to `discord.js-selfbot-v13@v2.15`, based on `discord.js@13.17`</strong> <strong>Welcome to `discord.js-selfbot-v13@v3.0`, based on `discord.js@13.17`</strong>
- discord.js-selfbot-v13 is a [Node.js](https://nodejs.org) module that allows user accounts to interact with the Discord API v9. - 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** **Node.js 16.6.0 or newer is required**
> Recommended Node.js version: 18 (LTS) > Recommended Node.js version: 18+ (LTS)
```sh-session ```sh-session
npm install discord.js-selfbot-v13@latest 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'); console.log(`%cYou now have your token in the clipboard!`, 'font-size: 16px');
``` ```
Credit: <img src="https://cdn.discordapp.com/emojis/889092230063734795.png" alt="." width="16" height="16"/> [<strong>hxr404</strong>](https://github.com/hxr404/Discord-Console-hacks)
## Contributing ## Contributing
- Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the - 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 A patched version of discord, with bot login support
- 📕 [***aiko-chan-ai/Discord-Markdown***](https://github.com/aiko-chan-ai/Discord-Markdown) <br/> - 📕 [***aiko-chan-ai/Discord-Markdown***](https://github.com/aiko-chan-ai/Discord-Markdown) <br/>
Better Markdown to text chat Discord. Better Markdown to text chat Discord.
- 📗 ...
## Star History ## Star History

View File

@ -1,6 +1,6 @@
{ {
"name": "discord.js-selfbot-v13", "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]", "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",
@ -54,7 +54,6 @@
"@aikochan2k6/qrcode-terminal": "^0.12.1", "@aikochan2k6/qrcode-terminal": "^0.12.1",
"@discordjs/builders": "^1.6.3", "@discordjs/builders": "^1.6.3",
"@discordjs/collection": "^1.5.3", "@discordjs/collection": "^1.5.3",
"@discordjs/voice": "^0.16.0",
"@sapphire/async-queue": "^1.5.0", "@sapphire/async-queue": "^1.5.0",
"@sapphire/shapeshift": "^3.9.3", "@sapphire/shapeshift": "^3.9.3",
"@types/node-fetch": "^2.6.7", "@types/node-fetch": "^2.6.7",
@ -62,12 +61,7 @@
"discord-api-types": "^0.37.61", "discord-api-types": "^0.37.61",
"fetch-cookie": "^2.1.0", "fetch-cookie": "^2.1.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"json-bigint": "^1.0.0",
"lodash.permutations": "^1.0.0",
"node-fetch": "^2.6.9", "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", "tough-cookie": "^4.1.3",
"ws": "^8.14.2" "ws": "^8.14.2"
}, },

View File

@ -1,46 +1,40 @@
'use strict'; 'use strict';
const process = require('node:process'); 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 { Collection } = require('@discordjs/collection');
const { getVoiceConnection } = require('@discordjs/voice');
const chalk = require('chalk');
const fetch = require('node-fetch');
const BaseClient = require('./BaseClient'); const BaseClient = require('./BaseClient');
const ActionsManager = require('./actions/ActionsManager'); const ActionsManager = require('./actions/ActionsManager');
const ClientVoiceManager = require('./voice/ClientVoiceManager'); const ClientVoiceManager = require('./voice/ClientVoiceManager');
const WebSocketManager = require('./websocket/WebSocketManager'); const WebSocketManager = require('./websocket/WebSocketManager');
const { Error, TypeError, RangeError } = require('../errors'); const { Error, TypeError, RangeError } = require('../errors');
const Discord = require('../index');
const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager'); const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager');
const BillingManager = require('../managers/BillingManager'); const BillingManager = require('../managers/BillingManager');
const ChannelManager = require('../managers/ChannelManager'); const ChannelManager = require('../managers/ChannelManager');
const ClientUserSettingManager = require('../managers/ClientUserSettingManager');
const DeveloperPortalManager = require('../managers/DeveloperPortalManager');
const GuildManager = require('../managers/GuildManager'); const GuildManager = require('../managers/GuildManager');
const PresenceManager = require('../managers/PresenceManager');
const RelationshipManager = require('../managers/RelationshipManager'); const RelationshipManager = require('../managers/RelationshipManager');
const SessionManager = require('../managers/SessionManager');
const UserManager = require('../managers/UserManager'); const UserManager = require('../managers/UserManager');
const UserNoteManager = require('../managers/UserNoteManager');
const VoiceStateManager = require('../managers/VoiceStateManager'); const VoiceStateManager = require('../managers/VoiceStateManager');
const ShardClientUtil = require('../sharding/ShardClientUtil'); const ShardClientUtil = require('../sharding/ShardClientUtil');
const ClientPresence = require('../structures/ClientPresence'); const ClientPresence = require('../structures/ClientPresence');
const GuildPreview = require('../structures/GuildPreview'); const GuildPreview = require('../structures/GuildPreview');
const GuildTemplate = require('../structures/GuildTemplate'); const GuildTemplate = require('../structures/GuildTemplate');
const Invite = require('../structures/Invite'); const Invite = require('../structures/Invite');
const { CustomStatus } = require('../structures/RichPresence');
const { Sticker } = require('../structures/Sticker'); const { Sticker } = require('../structures/Sticker');
const StickerPack = require('../structures/StickerPack'); const StickerPack = require('../structures/StickerPack');
const VoiceRegion = require('../structures/VoiceRegion'); const VoiceRegion = require('../structures/VoiceRegion');
const Webhook = require('../structures/Webhook'); const Webhook = require('../structures/Webhook');
const Widget = require('../structures/Widget'); 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 DataResolver = require('../util/DataResolver');
const Intents = require('../util/Intents'); const Intents = require('../util/Intents');
const Options = require('../util/Options'); const Options = require('../util/Options');
const Permissions = require('../util/Permissions'); const Permissions = require('../util/Permissions');
const DiscordAuthWebsocket = require('../util/RemoteAuth'); const DiscordAuthWebsocket = require('../util/RemoteAuth');
const Sweepers = require('../util/Sweepers'); 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. * 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 * @param {ClientOptions} options Options for the client
*/ */
constructor(options = {}) { constructor(options) {
super(options); super(options);
const data = require('node:worker_threads').workerData ?? process.env; const data = require('node:worker_threads').workerData ?? process.env;
@ -141,17 +135,6 @@ class Client extends BaseClient {
*/ */
this.users = new UserManager(this); 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 - * 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 * 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); 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 - * 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 * 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); 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 * The presence of the Client
* @private * @private
@ -199,6 +164,30 @@ class Client extends BaseClient {
*/ */
this.presence = new ClientPresence(this, this.options.presence); 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 }); Object.defineProperty(this, 'token', { writable: true });
if (!this.token && 'DISCORD_TOKEN' in process.env) { if (!this.token && 'DISCORD_TOKEN' in process.env) {
/** /**
@ -212,20 +201,12 @@ class Client extends BaseClient {
this.token = null; this.token = null;
} }
this._interactionCache = new Collection();
/** /**
* User that the client is logged in as * User that the client is logged in as
* @type {?ClientUser} * @type {?ClientUser}
*/ */
this.user = null; 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 * 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) * (each time the client disconnects and successfully reconnects, this will be overwritten)
@ -233,12 +214,6 @@ class Client extends BaseClient {
*/ */
this.readyAt = null; this.readyAt = null;
/**
* Password cache
* @type {?string}
*/
this.password = this.options.password;
if (this.options.messageSweepInterval > 0) { if (this.options.messageSweepInterval > 0) {
process.emitWarning( process.emitWarning(
'The message sweeping client options are deprecated, use the global sweepers instead.', '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 * All custom emojis that the client has access to, mapped by their ids
* @type {BaseGuildEmojiManager} * @type {BaseGuildEmojiManager}
@ -291,19 +257,6 @@ class Client extends BaseClient {
return this.readyAt ? Date.now() - this.readyAt : null; 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. * Logs the client in, establishing a WebSocket connection to Discord.
* @param {string} [token=this.token] Token of the account to log in with * @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 Logging on with a user token is unfortunately against the Discord
\`Terms of Service\` <https://support.discord.com/hc/en-us/articles/115002192352> \`Terms of Service\` <https://support.discord.com/hc/en-us/articles/115002192352>
and doing so might potentially get your account banned. and doing so might potentially get your account banned.
Use this at your own risk. Use this at your own risk.`,
`,
); );
this.emit( this.emit(
Events.DEBUG, Events.DEBUG,
@ -346,178 +298,10 @@ class Client extends BaseClient {
} }
} }
/** QRLogin() {
* Login Discord with Username and Password const ws = new DiscordAuthWebsocket();
* @param {string} username Email or Phone Number ws.once('ready', () => ws.generateQR());
* @param {?string} password Password return ws.connect(this);
* @param {?string} mfaCode 2FA Code / Backup Code
* @returns {Promise<string>}
*/
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<string>}
*/
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<void>}
*/
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<string>} 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<Client>}
*/
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;
} }
/** /**
@ -544,7 +328,6 @@ class Client extends BaseClient {
this.sweepers.destroy(); this.sweepers.destroy();
this.ws.destroy(); this.ws.destroy();
this.token = null; this.token = null;
this.password = null;
} }
/** /**
@ -558,7 +341,7 @@ class Client extends BaseClient {
voip_provider: null, voip_provider: null,
}, },
}); });
await this.destroy(); return this.destroy();
} }
/** /**
@ -586,51 +369,6 @@ class Client extends BaseClient {
return new Invite(this, data); return new Invite(this, data);
} }
/**
* Join this Guild using this invite (fast)
* @param {InviteResolvable} invite Invite code or URL
* @returns {Promise<void>}
* @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<any>}
*/
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. * Obtains a template from Discord.
* @param {GuildTemplateResolvable} template Template code or URL * @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. * 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. * 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. * The sleep function in JavaScript returns a promise that resolves after a specified timeout.
* @param {InviteGenerationOptions} [options={}] Options for the invite * @param {number} timeout - The timeout parameter is the amount of time, in milliseconds, that the sleep
* @returns {string} * function will wait before resolving the promise and continuing execution.
* @example * @returns {void} The `sleep` function is returning a Promise.
* 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}`);
*/ */
generateInvite(options = {}) { sleep(timeout) {
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); return new Promise(r => setTimeout(r, timeout));
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}`;
} }
toJSON() { 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 * Join this Guild using this invite (fast)
* with the client as `this`. * @param {InviteResolvable} invite Invite code or URL
* @param {string} script Script to eval * @returns {Promise<void>}
* @returns {*} * @example
* @private * await client.acceptInvite('https://discord.gg/genshinimpact')
*/ */
_eval(script) { async acceptInvite(invite) {
return eval(script); 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). * Redeem nitro from code or url.
* @param {Client} client Discord Client * @param {string} nitro Nitro url or code
* @private * @param {TextChannelResolvable} [channel] Channel that the code was sent in
* @param {Snowflake} [paymentSourceId] Payment source id
* @returns {Promise<any>}
*/ */
customStatusAuto(client) { redeemNitro(nitro, channel, paymentSourceId) {
client = client ?? this; if (typeof nitro !== 'string') throw new Error('INVALID_NITRO');
if (!client.user) return; const nitroCode =
const custom_status = new CustomStatus(); nitro.match(/(discord.gift|discord.com|discordapp.com\/gifts)\/(\w{16,25})/) ||
if (!client.settings.rawSetting.custom_status?.text && !client.settings.rawSetting.custom_status?.emoji_name) { nitro.match(/(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)(\w+)/);
client.user.setPresence({ if (!nitroCode) return false;
activities: this.presence.activities.filter(a => a.type !== 'CUSTOM'), const code = nitroCode[2];
status: client.settings.rawSetting.status ?? 'invisible', 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 },
}); });
} 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',
});
}
} }
/** /**
@ -909,7 +590,7 @@ class Client extends BaseClient {
* Authorize an application. * Authorize an application.
* @param {string} url Discord Auth URL * @param {string} url Discord Auth URL
* @param {OAuth2AuthorizeOptions} options Oauth2 options * @param {OAuth2AuthorizeOptions} options Oauth2 options
* @returns {Promise<Object>} * @returns {Promise<any>}
* @example * @example
* client.authorizeURL(`https://discord.com/api/oauth2/authorize?client_id=botID&permissions=8&scope=applications.commands%20bot`, { * client.authorizeURL(`https://discord.com/api/oauth2/authorize?client_id=botID&permissions=8&scope=applications.commands%20bot`, {
guild_id: "guildID", guild_id: "guildID",
@ -937,12 +618,14 @@ class Client extends BaseClient {
} }
/** /**
* Makes waiting time for Client. * Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script
* @param {number} miliseconds Sleeping time as milliseconds. * with the client as `this`.
* @returns {Promise<void> | null} * @param {string} script Script to eval
* @returns {*}
* @private
*/ */
sleep(miliseconds) { _eval(script) {
return typeof miliseconds === 'number' ? new Promise(r => setTimeout(r, miliseconds).unref()) : null; return eval(script);
} }
/** /**
@ -956,73 +639,8 @@ class Client extends BaseClient {
} else { } else {
options.intents = Intents.resolve(options.intents); options.intents = Intents.resolve(options.intents);
} }
if (options && typeof options.checkUpdate !== 'boolean') { if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount !== 1) {
throw new TypeError('CLIENT_INVALID_OPTION', 'checkUpdate', 'a boolean'); throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number equal to 1');
}
if (options && typeof options.syncStatus !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'syncStatus', 'a boolean');
}
if (options && typeof options.autoRedeemNitro !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'autoRedeemNitro', 'a boolean');
}
if (options && options.captchaService && !captchaServices.includes(options.captchaService)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaService', captchaServices.join(', '));
}
// Parse captcha key
if (options && captchaServices.includes(options.captchaService) && options.captchaService !== 'custom') {
if (typeof options.captchaKey !== 'string') {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a string');
}
switch (options.captchaService) {
case '2captcha':
if (options.captchaKey.length !== 32) {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a 32 character string');
}
break;
case 'capmonster':
if (options.captchaKey.length !== 32) {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a 32 character string');
}
break;
case 'nopecha': {
if (options.captchaKey.length !== 16) {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a 16 character string');
}
break;
}
}
}
if (typeof options.captchaRetryLimit !== 'number' || isNaN(options.captchaRetryLimit)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaRetryLimit', 'a number');
}
if (options && typeof options.captchaSolver !== 'function') {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaSolver', 'a function');
}
if (options && typeof options.captchaWithProxy !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaWithProxy', 'a boolean');
}
if (options && typeof options.DMSync !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'DMSync', 'a boolean');
}
if (options && typeof options.patchVoice !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'patchVoice', 'a boolean');
}
if (options && options.password && typeof options.password !== 'string') {
throw new TypeError('CLIENT_INVALID_OPTION', 'password', 'a string');
}
if (options && options.usingNewAttachmentAPI && typeof options.usingNewAttachmentAPI !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'usingNewAttachmentAPI', 'a boolean');
}
if (options && options.interactionTimeout && typeof options.interactionTimeout !== 'number') {
throw new TypeError('CLIENT_INVALID_OPTION', 'interactionTimeout', 'a number');
}
if (options && typeof options.proxy !== 'string') {
throw new TypeError('CLIENT_INVALID_OPTION', 'proxy', 'a string');
} else if (options && options.proxy && typeof options.proxy === 'string') {
getProxyObject(options.proxy);
}
if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount < 1) {
throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number greater than or equal to 1');
} }
if (options.shards && !(options.shards === 'auto' || Array.isArray(options.shards))) { if (options.shards && !(options.shards === 'auto' || Array.isArray(options.shards))) {
throw new TypeError('CLIENT_INVALID_OPTION', 'shards', "'auto', a number or array of numbers"); throw new TypeError('CLIENT_INVALID_OPTION', 'shards', "'auto', a number or array of numbers");
@ -1046,12 +664,15 @@ class Client extends BaseClient {
if (!Array.isArray(options.partials)) { if (!Array.isArray(options.partials)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array'); throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array');
} }
if (typeof options.waitGuildTimeout !== 'number' || isNaN(options.waitGuildTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'waitGuildTimeout', 'a number');
}
if (typeof options.messageCreateEventGuildTimeout !== 'number' || isNaN(options.messageCreateEventGuildTimeout)) { if (typeof options.messageCreateEventGuildTimeout !== 'number' || isNaN(options.messageCreateEventGuildTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'messageCreateEventGuildTimeout', 'a number'); throw new TypeError('CLIENT_INVALID_OPTION', 'messageCreateEventGuildTimeout', 'a number');
} }
if (typeof options.DMChannelVoiceStatusSync !== 'number' || isNaN(options.DMChannelVoiceStatusSync)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'DMChannelVoiceStatusSync', 'a number');
}
if (typeof options.waitGuildTimeout !== 'number' || isNaN(options.waitGuildTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'waitGuildTimeout', 'a number');
}
if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) { if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number'); throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number');
} }

View File

@ -1,105 +1,13 @@
'use strict'; 'use strict';
const USER_REQUIRED_ACTION = require('./USER_REQUIRED_ACTION_UPDATE');
const { Opcodes } = require('../../../util/Constants');
let ClientUser; let ClientUser;
const { VoiceConnection } = require('@discordjs/voice');
const chalk = require('chalk');
const { Events, Opcodes } = require('../../../util/Constants');
const Util = require('../../../util/Util');
const { VoiceConnection: VoiceConnection_patch } = require('../../../util/Voice');
let firstReady = false;
function patchVoice(client) { module.exports = (client, { d: data }, shard) => {
try { // Check
/* eslint-disable */ USER_REQUIRED_ACTION(client, { d: data });
VoiceConnection.prototype.configureNetworking = VoiceConnection_patch.prototype.configureNetworking;
client.emit(
'debug',
`${chalk.greenBright('[OK]')} Patched ${chalk.cyanBright(
'VoiceConnection.prototype.configureNetworking',
)} [${chalk.bgMagentaBright('@discordjs/voice')} - ${chalk.redBright('v0.16.0')}]`,
);
/* eslint-enable */
} catch (e) {
client.emit(
'debug',
`${chalk.redBright('[Fail]')} Patched ${chalk.cyanBright(
'VoiceConnection.prototype.configureNetworking',
)} [${chalk.bgMagentaBright('@discordjs/voice')} - ${chalk.redBright('v0.16.0')}]\n${e.stack}`,
);
client.emit(
Events.ERROR,
`${chalk.redBright('[Fail]')} Patched ${chalk.cyanBright(
'VoiceConnection.prototype.configureNetworking',
)} [${chalk.bgMagentaBright('@discordjs/voice')} - ${chalk.redBright('v0.16.0')}]`,
);
client.emit(
Events.ERROR,
`${chalk.redBright('[Error]')} Please install ${chalk.bgMagentaBright(
'@discordjs/voice',
)} version ${chalk.redBright('v0.16.0')}`,
);
}
}
module.exports = async (client, { d: data }, shard) => {
Util.clientRequiredAction(client, data.required_action);
if (!firstReady) {
if (client.options.checkUpdate) {
client.once('update', (currentVersion, newVersion) => {
if (!newVersion) {
console.log(`
${chalk.redBright('[WARNING]')} Cannot check new Discord.js-selfbot-v13 version.
Current: ${chalk.blueBright(currentVersion)}
If you don't want to show this message, set ${chalk.cyanBright('checkUpdate')} to false
const client = new Client({
checkUpdate: false,
});
and using event update
https://discordjs-self-v13.netlify.app/#/docs/docs/main/class/Client?scrollTo=e-update\n`);
} else if (currentVersion !== newVersion && !currentVersion.includes('-')) {
console.log(`
${chalk.yellowBright('[WARNING]')} New Discord.js-selfbot-v13 version.
Current: ${chalk.redBright(currentVersion)} => Latest: ${chalk.greenBright(newVersion)}
If you don't want to show this message, set ${chalk.cyanBright('checkUpdate')} to false
const client = new Client({
checkUpdate: false,
});
and using event update
https://discordjs-self-v13.netlify.app/#/docs/docs/main/class/Client?scrollTo=e-update\n`);
} else {
console.log(
`
${chalk.greenBright('[OK]')} Discord.js-selfbot-v13 is up to date. Current: ${chalk.blueBright(currentVersion)}
If you don't want to show this message, set ${chalk.cyanBright('checkUpdate')} to false
const client = new Client({
checkUpdate: false,
});
and using event update
https://discordjs-self-v13.netlify.app/#/docs/docs/main/class/Client?scrollTo=e-update\n`,
);
}
});
client.checkUpdate();
}
if (client.options.patchVoice) {
patchVoice(client);
}
if (client.options.syncStatus) {
client.customStatusAuto(client);
}
firstReady = true;
}
if (client.user) { if (client.user) {
client.user._patch(data.user); client.user._patch(data.user);
@ -109,31 +17,8 @@ module.exports = async (client, { d: data }, shard) => {
client.users.cache.set(client.user.id, client.user); client.users.cache.set(client.user.id, client.user);
} }
client.settings._patch(data.user_settings);
client.user.connectedAccounts = data.connected_accounts ?? [];
client.relationships._setup(data.relationships);
client.user._patchNote(data.notes);
const syncTime = Date.now();
for (const private_channel of data.private_channels) { for (const private_channel of data.private_channels) {
const channel = client.channels._add(private_channel); client.channels._add(private_channel);
// Rate limit warning
if (client.options.DMSync) {
client.ws.broadcast({
op: Opcodes.DM_UPDATE,
d: {
channel_id: channel.id,
},
});
}
}
if (client.options.DMSync) {
console.warn(
`Gateway Rate Limit Warning: Sending ${data.private_channels.length} Requests / ${Date.now() - syncTime || 1} ms`,
);
} }
for (const guild of data.guilds) { for (const guild of data.guilds) {
@ -141,18 +26,17 @@ module.exports = async (client, { d: data }, shard) => {
client.guilds._add(guild); client.guilds._add(guild);
} }
for (const gSetting of Array.isArray(data.user_guild_settings) ? data.user_guild_settings : []) {
const guild = client.guilds.cache.get(gSetting.guild_id);
if (guild) guild.settings._patch(gSetting);
}
const largeGuilds = data.guilds.filter(g => g.large); const largeGuilds = data.guilds.filter(g => g.large);
client.emit('debug', `[READY] Received ${data.guilds.length} guilds, ${largeGuilds.length} large guilds`); client.emit('debug', `[READY] Received ${data.guilds.length} guilds, ${largeGuilds.length} large guilds`);
// Receive messages in large guilds // User Notes
for (const guild of largeGuilds) { client.notes._reload(data.notes);
await client.sleep(client.options.messageCreateEventGuildTimeout);
// Relationship
client.relationships._setup(data.relationships);
Promise.all(
largeGuilds.map(async (guild, index) => {
client.ws.broadcast({ client.ws.broadcast({
op: Opcodes.GUILD_SUBSCRIPTIONS, op: Opcodes.GUILD_SUBSCRIPTIONS,
d: { d: {
@ -165,7 +49,18 @@ module.exports = async (client, { d: data }, shard) => {
channels: {}, channels: {},
}, },
}); });
} client.emit('debug', `[READY] Register guild ${guild.id}`);
await client.sleep(client.options.messageCreateEventGuildTimeout * index);
shard.checkReady(); }),
data.private_channels.map(async (c, index) => {
if (client.options.DMChannelVoiceStatusSync < 1) return;
client.ws.broadcast({
op: Opcodes.DM_UPDATE,
d: {
channel_id: c.id,
},
});
await client.sleep(client.options.DMChannelVoiceStatusSync * index);
}),
).then(() => shard.checkReady());
}; };

View File

@ -1,12 +0,0 @@
'use strict';
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => {
const guild = client.guilds.cache.get(data.guild_id);
guild?.settings._patch(data);
/**
* Emitted whenever guild settings are updated
* @event Client#userGuildSettingsUpdate
* @param {Guild} guild Guild
*/
return client.emit(Events.USER_GUILD_SETTINGS_UPDATE, guild);
};

View File

@ -1,9 +0,0 @@
'use strict';
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => {
client.settings._patch(data);
if (('status' in data || 'custom_status' in data) && client.options.syncStatus) {
client.customStatusAuto(client);
}
return client.emit(Events.USER_SETTINGS_UPDATE, data);
};

View File

@ -3,10 +3,6 @@
const handlers = Object.fromEntries([ const handlers = Object.fromEntries([
['READY', require('./READY')], ['READY', require('./READY')],
['RESUMED', require('./RESUMED')], ['RESUMED', require('./RESUMED')],
['RELATIONSHIP_ADD', require('./RELATIONSHIP_ADD')],
['RELATIONSHIP_REMOVE', require('./RELATIONSHIP_REMOVE')],
['RELATIONSHIP_UPDATE', require('./RELATIONSHIP_UPDATE')],
['APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE', require('./APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE')],
['APPLICATION_COMMAND_CREATE', require('./APPLICATION_COMMAND_CREATE')], ['APPLICATION_COMMAND_CREATE', require('./APPLICATION_COMMAND_CREATE')],
['APPLICATION_COMMAND_DELETE', require('./APPLICATION_COMMAND_DELETE')], ['APPLICATION_COMMAND_DELETE', require('./APPLICATION_COMMAND_DELETE')],
['APPLICATION_COMMAND_UPDATE', require('./APPLICATION_COMMAND_UPDATE')], ['APPLICATION_COMMAND_UPDATE', require('./APPLICATION_COMMAND_UPDATE')],
@ -15,9 +11,6 @@ const handlers = Object.fromEntries([
['AUTO_MODERATION_RULE_CREATE', require('./AUTO_MODERATION_RULE_CREATE')], ['AUTO_MODERATION_RULE_CREATE', require('./AUTO_MODERATION_RULE_CREATE')],
['AUTO_MODERATION_RULE_DELETE', require('./AUTO_MODERATION_RULE_DELETE')], ['AUTO_MODERATION_RULE_DELETE', require('./AUTO_MODERATION_RULE_DELETE')],
['AUTO_MODERATION_RULE_UPDATE', require('./AUTO_MODERATION_RULE_UPDATE')], ['AUTO_MODERATION_RULE_UPDATE', require('./AUTO_MODERATION_RULE_UPDATE')],
['CALL_CREATE', require('./CALL_CREATE')],
['CALL_UPDATE', require('./CALL_UPDATE')],
['CALL_DELETE', require('./CALL_DELETE')],
['GUILD_CREATE', require('./GUILD_CREATE')], ['GUILD_CREATE', require('./GUILD_CREATE')],
['GUILD_DELETE', require('./GUILD_DELETE')], ['GUILD_DELETE', require('./GUILD_DELETE')],
['GUILD_UPDATE', require('./GUILD_UPDATE')], ['GUILD_UPDATE', require('./GUILD_UPDATE')],
@ -27,8 +20,6 @@ const handlers = Object.fromEntries([
['GUILD_MEMBER_REMOVE', require('./GUILD_MEMBER_REMOVE')], ['GUILD_MEMBER_REMOVE', require('./GUILD_MEMBER_REMOVE')],
['GUILD_MEMBER_UPDATE', require('./GUILD_MEMBER_UPDATE')], ['GUILD_MEMBER_UPDATE', require('./GUILD_MEMBER_UPDATE')],
['GUILD_MEMBERS_CHUNK', require('./GUILD_MEMBERS_CHUNK')], ['GUILD_MEMBERS_CHUNK', require('./GUILD_MEMBERS_CHUNK')],
['GUILD_MEMBER_LIST_UPDATE', require('./GUILD_MEMBER_LIST_UPDATE.js')],
['GUILD_APPLICATION_COMMANDS_UPDATE', require('./GUILD_APPLICATION_COMMANDS_UPDATE.js')],
['GUILD_INTEGRATIONS_UPDATE', require('./GUILD_INTEGRATIONS_UPDATE')], ['GUILD_INTEGRATIONS_UPDATE', require('./GUILD_INTEGRATIONS_UPDATE')],
['GUILD_ROLE_CREATE', require('./GUILD_ROLE_CREATE')], ['GUILD_ROLE_CREATE', require('./GUILD_ROLE_CREATE')],
['GUILD_ROLE_DELETE', require('./GUILD_ROLE_DELETE')], ['GUILD_ROLE_DELETE', require('./GUILD_ROLE_DELETE')],
@ -40,9 +31,6 @@ const handlers = Object.fromEntries([
['CHANNEL_DELETE', require('./CHANNEL_DELETE')], ['CHANNEL_DELETE', require('./CHANNEL_DELETE')],
['CHANNEL_UPDATE', require('./CHANNEL_UPDATE')], ['CHANNEL_UPDATE', require('./CHANNEL_UPDATE')],
['CHANNEL_PINS_UPDATE', require('./CHANNEL_PINS_UPDATE')], ['CHANNEL_PINS_UPDATE', require('./CHANNEL_PINS_UPDATE')],
['CHANNEL_RECIPIENT_ADD', require('./CHANNEL_RECIPIENT_ADD')],
['CHANNEL_RECIPIENT_REMOVE', require('./CHANNEL_RECIPIENT_REMOVE')],
['MESSAGE_ACK', require('./MESSAGE_ACK')],
['MESSAGE_CREATE', require('./MESSAGE_CREATE')], ['MESSAGE_CREATE', require('./MESSAGE_CREATE')],
['MESSAGE_DELETE', require('./MESSAGE_DELETE')], ['MESSAGE_DELETE', require('./MESSAGE_DELETE')],
['MESSAGE_UPDATE', require('./MESSAGE_UPDATE')], ['MESSAGE_UPDATE', require('./MESSAGE_UPDATE')],
@ -57,21 +45,12 @@ const handlers = Object.fromEntries([
['THREAD_LIST_SYNC', require('./THREAD_LIST_SYNC')], ['THREAD_LIST_SYNC', require('./THREAD_LIST_SYNC')],
['THREAD_MEMBER_UPDATE', require('./THREAD_MEMBER_UPDATE')], ['THREAD_MEMBER_UPDATE', require('./THREAD_MEMBER_UPDATE')],
['THREAD_MEMBERS_UPDATE', require('./THREAD_MEMBERS_UPDATE')], ['THREAD_MEMBERS_UPDATE', require('./THREAD_MEMBERS_UPDATE')],
['USER_SETTINGS_UPDATE', require('./USER_SETTINGS_UPDATE')], // Opcode 0
['USER_GUILD_SETTINGS_UPDATE', require('./USER_GUILD_SETTINGS_UPDATE')],
// USER_SETTINGS_PROTO_UPDATE // opcode 0
['USER_NOTE_UPDATE', require('./USER_NOTE_UPDATE')],
['USER_REQUIRED_ACTION_UPDATE', require('./USER_REQUIRED_ACTION_UPDATE')],
['USER_UPDATE', require('./USER_UPDATE')], ['USER_UPDATE', require('./USER_UPDATE')],
['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')], ['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')],
['TYPING_START', require('./TYPING_START')], ['TYPING_START', require('./TYPING_START')],
['VOICE_STATE_UPDATE', require('./VOICE_STATE_UPDATE')], ['VOICE_STATE_UPDATE', require('./VOICE_STATE_UPDATE')],
['VOICE_SERVER_UPDATE', require('./VOICE_SERVER_UPDATE')], ['VOICE_SERVER_UPDATE', require('./VOICE_SERVER_UPDATE')],
['WEBHOOKS_UPDATE', require('./WEBHOOKS_UPDATE')], ['WEBHOOKS_UPDATE', require('./WEBHOOKS_UPDATE')],
['INTERACTION_CREATE', require('./INTERACTION_CREATE')],
['INTERACTION_SUCCESS', require('./INTERACTION_SUCCESS')],
['INTERACTION_MODAL_CREATE', require('./INTERACTION_MODAL_CREATE')],
['INTERACTION_FAILURE', require('./INTERACTION_FAILURE')],
['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE')], ['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE')],
['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')], ['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')],
['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')], ['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')],
@ -82,6 +61,18 @@ const handlers = Object.fromEntries([
['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')], ['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')],
['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')], ['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')],
['GUILD_AUDIT_LOG_ENTRY_CREATE', require('./GUILD_AUDIT_LOG_ENTRY_CREATE')], ['GUILD_AUDIT_LOG_ENTRY_CREATE', require('./GUILD_AUDIT_LOG_ENTRY_CREATE')],
// Selfbot
['RELATIONSHIP_ADD', require('./RELATIONSHIP_ADD')],
['RELATIONSHIP_REMOVE', require('./RELATIONSHIP_REMOVE')],
['RELATIONSHIP_UPDATE', require('./RELATIONSHIP_UPDATE')],
['USER_NOTE_UPDATE', require('./USER_NOTE_UPDATE')],
['CHANNEL_RECIPIENT_ADD', require('./CHANNEL_RECIPIENT_ADD')],
['CHANNEL_RECIPIENT_REMOVE', require('./CHANNEL_RECIPIENT_REMOVE')],
['INTERACTION_MODAL_CREATE', require('./INTERACTION_MODAL_CREATE')],
['USER_REQUIRED_ACTION_UPDATE', require('./USER_REQUIRED_ACTION_UPDATE')],
['CALL_CREATE', require('./CALL_CREATE')],
['CALL_UPDATE', require('./CALL_UPDATE')],
['CALL_DELETE', require('./CALL_DELETE')],
]); ]);
module.exports = handlers; module.exports = handlers;

View File

@ -11,8 +11,6 @@ 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.',
MFA_INVALID: 'An invalid mfa code was provided',
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.',
WS_NOT_OPEN: (data = 'data') => `WebSocket not open to send ${data}`, WS_NOT_OPEN: (data = 'data') => `WebSocket not open to send ${data}`,
@ -20,8 +18,8 @@ const Messages = {
BITFIELD_INVALID: bit => `Invalid bitfield flag or number: ${bit}.`, BITFIELD_INVALID: bit => `Invalid bitfield flag or number: ${bit}.`,
SHARDING_INVALID: 'Invalid shard settings were provided.', SHARDING_INVALID: '[Bot Token] Invalid shard settings were provided.',
SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.', SHARDING_REQUIRED: '[Bot Token] This session would have handled too many guilds - Sharding is required.',
INVALID_INTENTS: '[Bot Token] Invalid intent provided for WebSocket intents.', INVALID_INTENTS: '[Bot Token] Invalid intent provided for WebSocket intents.',
DISALLOWED_INTENTS: '[Bot Token] Privileged intent provided is not enabled or whitelisted.', DISALLOWED_INTENTS: '[Bot Token] Privileged intent provided is not enabled or whitelisted.',
SHARDING_NO_SHARDS: 'No shards have been spawned.', SHARDING_NO_SHARDS: 'No shards have been spawned.',
@ -49,12 +47,6 @@ const Messages = {
EMBED_FOOTER_TEXT: 'MessageEmbed footer text must be a string.', EMBED_FOOTER_TEXT: 'MessageEmbed footer text must be a string.',
EMBED_DESCRIPTION: 'MessageEmbed description must be a string.', EMBED_DESCRIPTION: 'MessageEmbed description must be a string.',
EMBED_AUTHOR_NAME: 'MessageEmbed author name must be a string.', EMBED_AUTHOR_NAME: 'MessageEmbed author name must be a string.',
/* Add */
MISSING_PERMISSIONS: (...permission) => `You can't do this action [Missing Permission(s): ${permission.join(', ')}]`,
EMBED_PROVIDER_NAME: 'MessageEmbed provider name must be a string.',
INVALID_COMMAND_NAME: allCMD => `Could not parse subGroupCommand and subCommand due to too long: ${allCMD.join(' ')}`,
INVALID_RANGE_QUERY_MEMBER: 'Invalid range query member. (0<x<=100)',
MUST_SPECIFY_BOT: 'You must specify a bot to use this command.',
BUTTON_LABEL: 'MessageButton label must be a string', BUTTON_LABEL: 'MessageButton label must be a string',
BUTTON_URL: 'MessageButton URL must be a string', BUTTON_URL: 'MessageButton URL must be a string',
@ -66,16 +58,22 @@ const Messages = {
SELECT_OPTION_VALUE: 'MessageSelectOption value must be a string', SELECT_OPTION_VALUE: 'MessageSelectOption value must be a string',
SELECT_OPTION_DESCRIPTION: 'MessageSelectOption description must be a string', SELECT_OPTION_DESCRIPTION: 'MessageSelectOption description must be a string',
TEXT_INPUT_CUSTOM_ID: 'TextInputComponent customId must be a string',
TEXT_INPUT_LABEL: 'TextInputComponent label must be a string',
TEXT_INPUT_PLACEHOLDER: 'TextInputComponent placeholder must be a string',
TEXT_INPUT_VALUE: 'TextInputComponent value must be a string',
MODAL_CUSTOM_ID: 'Modal customId must be a string',
MODAL_TITLE: 'Modal title must be a string',
INTERACTION_COLLECTOR_ERROR: reason => `Collector received no interactions before ending with reason: ${reason}`, INTERACTION_COLLECTOR_ERROR: reason => `Collector received no interactions before ending with reason: ${reason}`,
FILE_NOT_FOUND: file => `File could not be found: ${file}`, FILE_NOT_FOUND: file => `File could not be found: ${file}`,
USER_BANNER_NOT_FETCHED: "You must fetch this user's banner before trying to generate its URL!", USER_BANNER_NOT_FETCHED: "You must fetch this user's banner before trying to generate its URL!",
USER_NO_DM_CHANNEL: 'No DM Channel exists!', USER_NO_DM_CHANNEL: 'No DM Channel exists!',
CLIENT_NO_CALL: 'No call exists!',
VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.', VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.',
VOICE_NOT_IN_GUILD: 'You are only allowed to do this in guild channels.',
VOICE_STATE_NOT_OWN: VOICE_STATE_NOT_OWN:
'You cannot self-deafen/mute/request to speak on VoiceStates that do not belong to the ClientUser.', 'You cannot self-deafen/mute/request to speak on VoiceStates that do not belong to the ClientUser.',
@ -103,17 +101,11 @@ const Messages = {
GUILD_CHANNEL_UNOWNED: "The fetched channel does not belong to this manager's guild.", GUILD_CHANNEL_UNOWNED: "The fetched channel does not belong to this manager's guild.",
GUILD_OWNED: 'Guild is owned by the client.', GUILD_OWNED: 'Guild is owned by the client.',
GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.", GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.",
GUILD_APPLICATION_COMMANDS_SEARCH_TIMEOUT: "Application commands didn't arrive in time.",
GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.', GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.',
CHANNEL_NOT_CACHED: 'Could not find the channel where this message came from in the cache!', CHANNEL_NOT_CACHED: 'Could not find the channel where this message came from in the cache!',
STAGE_CHANNEL_RESOLVE: 'Could not resolve channel to a stage channel.', STAGE_CHANNEL_RESOLVE: 'Could not resolve channel to a stage channel.',
GUILD_SCHEDULED_EVENT_RESOLVE: 'Could not resolve the guild scheduled event.', GUILD_SCHEDULED_EVENT_RESOLVE: 'Could not resolve the guild scheduled event.',
REQUIRE_PASSWORD: 'You must provide a password.',
INVALIDATE_MEMBER: range => `Invalid member range: [${range[0]}, ${range[1]}]`,
MISSING_VALUE: (where, type) => `Missing value for ${where} (${type})`,
INVALID_TYPE: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`, INVALID_TYPE: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`,
INVALID_ELEMENT: (type, name, elem) => `Supplied ${type} ${name} includes an invalid element: ${elem}`, INVALID_ELEMENT: (type, name, elem) => `Supplied ${type} ${name} includes an invalid element: ${elem}`,
@ -141,10 +133,6 @@ const Messages = {
INVITE_NOT_FOUND: 'Could not find the requested invite.', INVITE_NOT_FOUND: 'Could not find the requested invite.',
NOT_OWNER_GROUP_DM_CHANNEL: "You can't do this action [Missing Permission]",
USER_ALREADY_IN_GROUP_DM_CHANNEL: 'User is already in the channel.',
USER_NOT_IN_GROUP_DM_CHANNEL: 'User is not in the channel.',
DELETE_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot delete them", DELETE_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot delete them",
FETCH_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot fetch them", FETCH_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot fetch them",
@ -169,60 +157,28 @@ const Messages = {
COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP: 'No subcommand group specified for interaction.', COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP: 'No subcommand group specified for interaction.',
AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION: 'No focused option for autocomplete interaction.', AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION: 'No focused option for autocomplete interaction.',
MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND: customId => `Required field with custom id "${customId}" not found.`,
MODAL_SUBMIT_INTERACTION_FIELD_TYPE: (customId, type, expected) =>
`Field with custom id "${customId}" is of type: ${type}; expected ${expected}.`,
INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite', INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite',
NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`, NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,
SWEEP_FILTER_RETURN: 'The return value of the sweepFilter function was not false or a Function', SWEEP_FILTER_RETURN: 'The return value of the sweepFilter function was not false or a Function',
INVALID_BOT_METHOD: 'Bot accounts cannot use this method',
INVALID_USER_METHOD: 'User accounts cannot use this method',
BOT_ONLY: 'This method only for bots',
USER_ONLY: 'This method only for users',
INTERACTION_SEND_FAILURE: msg => `${msg}`,
INVALID_LOCALE: 'Unable to select this location',
FOLDER_NOT_FOUND: 'Server directory not found',
FOLDER_POSITION_INVALID: 'The server index in the directory is invalid',
APPLICATION_ID_INVALID: "The application isn't BOT",
INVALID_NITRO: 'Invalid Nitro Code',
MESSAGE_ID_NOT_FOUND: 'Message ID not found',
MESSAGE_EMBED_LINK_LENGTH: 'Message content with embed link length is too long',
GUILD_MEMBERS_FETCH: msg => `${msg}`,
USER_NOT_STREAMING: 'User is not streaming',
// Djs v13.7
TEXT_INPUT_CUSTOM_ID: 'TextInputComponent customId must be a string',
TEXT_INPUT_LABEL: 'TextInputComponent label must be a string',
TEXT_INPUT_PLACEHOLDER: 'TextInputComponent placeholder must be a string',
TEXT_INPUT_VALUE: 'TextInputComponent value must be a string',
MODAL_CUSTOM_ID: 'Modal customId must be a string',
MODAL_TITLE: 'Modal title must be a string',
MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND: customId => `Required field with custom id "${customId}" not found.`,
MODAL_SUBMIT_INTERACTION_FIELD_TYPE: (customId, type, 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})',
INVALID_URL: url =>
`Invalid URL: ${url}.\nMake sure you are using a valid URL (https://discord.com/oauth2/authorize?...)`,
NITRO_REQUIRED: 'This feature is only available for Nitro users.',
NITRO_BOOST_REQUIRED: feature => `This feature (${feature}) is only available for Nitro Boost users.`,
ONLY_ME: 'This feature is only available for self.',
MISSING_CAPTCHA_SERVICE: 'This feature is only available for enabled captcha handler.',
GUILD_FORUM_MESSAGE_REQUIRED: 'You must provide a message to create a guild forum thread', GUILD_FORUM_MESSAGE_REQUIRED: 'You must provide a message to create a guild forum thread',
NORMAL_LOGIN: 'Username and password are required for normal login',
LOGIN_FAILED_UNKNOWN: 'Login failed',
LOGIN_FAILED_2FA: 'Login failed, 2FA code is required',
GUILD_IS_LARGE: 'This guild is too large to fetch all members with this method',
TEAM_MEMBER_FORMAT: 'The member provided is either not real or not of the User class', // Selfbot
INVALID_USER_API: 'User accounts cannot use this endpoint',
MISSING_MODULE: (name, installCommand) => INVALID_COMMAND_NAME: allCMD => `Could not parse subGroupCommand and subCommand due to too long: ${allCMD.join(' ')}`,
`The module "${name}" is missing. Please install it with "${installCommand}" and try again.`, INVALID_SLASH_COMMAND_CHOICES: (parentOptions, value) =>
`${value} is not a valid choice for this option (${parentOptions})`,
SLASH_COMMAND_REQUIRED_OPTIONS_MISSING: (req, opt) => `Value required (${req}) missing (Options: ${opt})`,
SLASH_COMMAND_SUB_COMMAND_GROUP_INVALID: n => `${n} is not a valid sub command group`,
SLASH_COMMAND_SUB_COMMAND_INVALID: n => `${n} is not a valid sub command`,
INTERACTION_FAILED: 'No responsed from Application Command',
USER_NOT_STREAMING: 'User is not streaming',
}; };
for (const [name, message] of Object.entries(Messages)) register(name, message); for (const [name, message] of Object.entries(Messages)) register(name, message);

View File

@ -1,11 +1,5 @@
'use strict'; 'use strict';
const tls = require('tls');
// Cipher
tls.DEFAULT_CIPHERS = tls.DEFAULT_CIPHERS.split(':')
.sort(() => Math.random() - 0.5)
.join(':');
// "Root" classes (starting points) // "Root" classes (starting points)
exports.BaseClient = require('./client/BaseClient'); exports.BaseClient = require('./client/BaseClient');
exports.Client = require('./client/Client'); exports.Client = require('./client/Client');
@ -15,7 +9,6 @@ exports.ShardingManager = require('./sharding/ShardingManager');
exports.WebhookClient = require('./client/WebhookClient'); exports.WebhookClient = require('./client/WebhookClient');
// Utilities // Utilities
exports.DiscordRPCServer = require('./util/arRPC/index');
exports.ActivityFlags = require('./util/ActivityFlags'); exports.ActivityFlags = require('./util/ActivityFlags');
exports.ApplicationFlags = require('./util/ApplicationFlags'); exports.ApplicationFlags = require('./util/ApplicationFlags');
exports.AttachmentFlags = require('./util/AttachmentFlags'); exports.AttachmentFlags = require('./util/AttachmentFlags');
@ -42,8 +35,6 @@ exports.ThreadMemberFlags = require('./util/ThreadMemberFlags');
exports.UserFlags = require('./util/UserFlags'); 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.PurchasedFlags = require('./util/PurchasedFlags');
// Managers // Managers
exports.ApplicationCommandManager = require('./managers/ApplicationCommandManager'); exports.ApplicationCommandManager = require('./managers/ApplicationCommandManager');
@ -71,7 +62,6 @@ exports.PresenceManager = require('./managers/PresenceManager');
exports.ReactionManager = require('./managers/ReactionManager'); exports.ReactionManager = require('./managers/ReactionManager');
exports.ReactionUserManager = require('./managers/ReactionUserManager'); exports.ReactionUserManager = require('./managers/ReactionUserManager');
exports.RoleManager = require('./managers/RoleManager'); exports.RoleManager = require('./managers/RoleManager');
exports.SessionManager = require('./managers/SessionManager');
exports.StageInstanceManager = require('./managers/StageInstanceManager'); exports.StageInstanceManager = require('./managers/StageInstanceManager');
exports.ThreadManager = require('./managers/ThreadManager'); exports.ThreadManager = require('./managers/ThreadManager');
exports.ThreadMemberManager = require('./managers/ThreadMemberManager'); exports.ThreadMemberManager = require('./managers/ThreadMemberManager');
@ -80,6 +70,7 @@ exports.VoiceStateManager = require('./managers/VoiceStateManager');
exports.WebSocketManager = require('./client/websocket/WebSocketManager'); exports.WebSocketManager = require('./client/websocket/WebSocketManager');
exports.WebSocketShard = require('./client/websocket/WebSocketShard'); exports.WebSocketShard = require('./client/websocket/WebSocketShard');
exports.RelationshipManager = require('./managers/RelationshipManager'); exports.RelationshipManager = require('./managers/RelationshipManager');
exports.UserNoteManager = require('./managers/UserNoteManager');
// Structures // Structures
exports.Activity = require('./structures/Presence').Activity; exports.Activity = require('./structures/Presence').Activity;
@ -88,26 +79,18 @@ exports.Application = require('./structures/interfaces/Application');
exports.ApplicationCommand = require('./structures/ApplicationCommand'); exports.ApplicationCommand = require('./structures/ApplicationCommand');
exports.ApplicationRoleConnectionMetadata = exports.ApplicationRoleConnectionMetadata =
require('./structures/ApplicationRoleConnectionMetadata').ApplicationRoleConnectionMetadata; require('./structures/ApplicationRoleConnectionMetadata').ApplicationRoleConnectionMetadata;
exports.AutocompleteInteraction = require('./structures/AutocompleteInteraction');
exports.AutoModerationActionExecution = require('./structures/AutoModerationActionExecution'); exports.AutoModerationActionExecution = require('./structures/AutoModerationActionExecution');
exports.AutoModerationRule = require('./structures/AutoModerationRule'); exports.AutoModerationRule = require('./structures/AutoModerationRule');
exports.Base = require('./structures/Base'); exports.Base = require('./structures/Base');
exports.BaseCommandInteraction = require('./structures/BaseCommandInteraction');
exports.BaseGuild = require('./structures/BaseGuild'); exports.BaseGuild = require('./structures/BaseGuild');
exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji'); exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji');
exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel'); exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel');
exports.BaseGuildVoiceChannel = require('./structures/BaseGuildVoiceChannel'); exports.BaseGuildVoiceChannel = require('./structures/BaseGuildVoiceChannel');
exports.BaseMessageComponent = require('./structures/BaseMessageComponent');
exports.ButtonInteraction = require('./structures/ButtonInteraction');
exports.CategoryChannel = require('./structures/CategoryChannel'); exports.CategoryChannel = require('./structures/CategoryChannel');
exports.Channel = require('./structures/Channel').Channel; exports.Channel = require('./structures/Channel').Channel;
exports.ClientApplication = require('./structures/ClientApplication');
exports.ClientPresence = require('./structures/ClientPresence'); exports.ClientPresence = require('./structures/ClientPresence');
exports.ClientUser = require('./structures/ClientUser'); exports.ClientUser = require('./structures/ClientUser');
exports.Collector = require('./structures/interfaces/Collector'); exports.Collector = require('./structures/interfaces/Collector');
exports.CommandInteraction = require('./structures/CommandInteraction');
exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver');
exports.ContextMenuInteraction = require('./structures/ContextMenuInteraction');
exports.DMChannel = require('./structures/DMChannel'); exports.DMChannel = require('./structures/DMChannel');
exports.Emoji = require('./structures/Emoji').Emoji; exports.Emoji = require('./structures/Emoji').Emoji;
exports.Guild = require('./structures/Guild').Guild; exports.Guild = require('./structures/Guild').Guild;
@ -123,49 +106,27 @@ exports.GuildScheduledEvent = require('./structures/GuildScheduledEvent').GuildS
exports.GuildTemplate = require('./structures/GuildTemplate'); exports.GuildTemplate = require('./structures/GuildTemplate');
exports.Integration = require('./structures/Integration'); exports.Integration = require('./structures/Integration');
exports.IntegrationApplication = require('./structures/IntegrationApplication'); exports.IntegrationApplication = require('./structures/IntegrationApplication');
exports.Interaction = require('./structures/Interaction');
exports.InteractionCollector = require('./structures/InteractionCollector');
exports.InteractionWebhook = require('./structures/InteractionWebhook');
exports.Invite = require('./structures/Invite'); exports.Invite = require('./structures/Invite');
exports.InviteStageInstance = require('./structures/InviteStageInstance'); exports.InviteStageInstance = require('./structures/InviteStageInstance');
exports.InviteGuild = require('./structures/InviteGuild'); exports.InviteGuild = require('./structures/InviteGuild');
exports.Message = require('./structures/Message').Message; exports.Message = require('./structures/Message').Message;
exports.MessageActionRow = require('./structures/MessageActionRow'); exports.MessageActionRow = require('./structures/MessageActionRow');
exports.MessageAttachment = require('./structures/MessageAttachment'); exports.MessageAttachment = require('./structures/MessageAttachment');
exports.MessageButton = require('./structures/MessageButton');
exports.MessageCollector = require('./structures/MessageCollector'); exports.MessageCollector = require('./structures/MessageCollector');
exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction');
exports.MessageContextMenuInteraction = require('./structures/MessageContextMenuInteraction');
exports.MessageEmbed = require('./structures/MessageEmbed'); exports.MessageEmbed = require('./structures/MessageEmbed');
exports.WebEmbed = require('./structures/WebEmbed');
exports.MessageMentions = require('./structures/MessageMentions'); exports.MessageMentions = require('./structures/MessageMentions');
exports.MessagePayload = require('./structures/MessagePayload'); exports.MessagePayload = require('./structures/MessagePayload');
exports.MessageReaction = require('./structures/MessageReaction'); exports.MessageReaction = require('./structures/MessageReaction');
exports.MessageSelectMenu = require('./structures/MessageSelectMenu');
exports.Modal = require('./structures/Modal'); exports.Modal = require('./structures/Modal');
exports.ModalSubmitInteraction = require('./structures/ModalSubmitInteraction');
exports.NewsChannel = require('./structures/NewsChannel'); exports.NewsChannel = require('./structures/NewsChannel');
exports.OAuth2Guild = require('./structures/OAuth2Guild'); exports.OAuth2Guild = require('./structures/OAuth2Guild');
exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel'); exports.GroupDMChannel = require('./structures/GroupDMChannel');
exports.PermissionOverwrites = require('./structures/PermissionOverwrites'); exports.PermissionOverwrites = require('./structures/PermissionOverwrites');
exports.Presence = require('./structures/Presence').Presence; exports.Presence = require('./structures/Presence').Presence;
exports.ReactionCollector = require('./structures/ReactionCollector'); exports.ReactionCollector = require('./structures/ReactionCollector');
exports.ReactionEmoji = require('./structures/ReactionEmoji'); exports.ReactionEmoji = require('./structures/ReactionEmoji');
exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets; exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets;
exports.Role = require('./structures/Role').Role; exports.Role = require('./structures/Role').Role;
exports.Session = require('./structures/Session');
// RPC
exports.getUUID = require('./structures/RichPresence').getUUID;
exports.CustomStatus = require('./structures/RichPresence').CustomStatus;
exports.RichPresence = require('./structures/RichPresence').RichPresence;
exports.SpotifyRPC = require('./structures/RichPresence').SpotifyRPC;
// SelectMenu
exports.ChannelSelectInteraction = require('./structures/SelectMenuInteraction').ChannelSelectInteraction;
exports.MentionableSelectInteraction = require('./structures/SelectMenuInteraction').MentionableSelectInteraction;
exports.RoleSelectInteraction = require('./structures/SelectMenuInteraction').RoleSelectInteraction;
exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction').SelectMenuInteraction;
exports.UserSelectInteraction = require('./structures/SelectMenuInteraction').UserSelectInteraction;
//
exports.StageChannel = require('./structures/StageChannel'); exports.StageChannel = require('./structures/StageChannel');
exports.StageInstance = require('./structures/StageInstance').StageInstance; exports.StageInstance = require('./structures/StageInstance').StageInstance;
exports.Sticker = require('./structures/Sticker').Sticker; exports.Sticker = require('./structures/Sticker').Sticker;
@ -179,7 +140,6 @@ exports.ThreadChannel = require('./structures/ThreadChannel');
exports.ThreadMember = require('./structures/ThreadMember'); exports.ThreadMember = require('./structures/ThreadMember');
exports.Typing = require('./structures/Typing'); exports.Typing = require('./structures/Typing');
exports.User = require('./structures/User'); exports.User = require('./structures/User');
exports.UserContextMenuInteraction = require('./structures/UserContextMenuInteraction');
exports.VoiceChannel = require('./structures/VoiceChannel'); exports.VoiceChannel = require('./structures/VoiceChannel');
exports.VoiceRegion = require('./structures/VoiceRegion'); exports.VoiceRegion = require('./structures/VoiceRegion');
exports.VoiceState = require('./structures/VoiceState'); exports.VoiceState = require('./structures/VoiceState');
@ -188,7 +148,12 @@ exports.Widget = require('./structures/Widget');
exports.WidgetMember = require('./structures/WidgetMember'); exports.WidgetMember = require('./structures/WidgetMember');
exports.WelcomeChannel = require('./structures/WelcomeChannel'); exports.WelcomeChannel = require('./structures/WelcomeChannel');
exports.WelcomeScreen = require('./structures/WelcomeScreen'); exports.WelcomeScreen = require('./structures/WelcomeScreen');
exports.WebSocket = require('./WebSocket'); exports.WebSocket = require('./WebSocket');
// DiscordJSVoice Patch exports.CustomStatus = require('./structures/RichPresence').CustomStatus;
exports.DiscordJSVoice = require('./util/Voice'); exports.RichPresence = require('./structures/RichPresence').RichPresence;
exports.SpotifyRPC = require('./structures/RichPresence').SpotifyRPC;
exports.WebEmbed = require('./structures/WebEmbed');
exports.DiscordAuthWebsocket = require('./util/RemoteAuth');
exports.PurchasedFlags = require('./util/PurchasedFlags');

View File

@ -1,490 +0,0 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const BaseManager = require('./BaseManager');
const GuildFolderManager = require('./GuildFolderManager');
const { Error, TypeError } = require('../errors/DJSError');
const GuildFolder = require('../structures/GuildFolder');
const { CustomStatus } = require('../structures/RichPresence');
const { localeSetting, DMScanLevel, stickerAnimationMode } = require('../util/Constants');
/**
* Manages API methods for users and stores their cache.
* @extends {BaseManager}
* @see {@link https://luna.gitlab.io/discord-unofficial-docs/user_settings.html}
*/
class ClientUserSettingManager extends BaseManager {
constructor(client) {
super(client);
/**
* Raw data
* @type {Object}
*/
this.rawSetting = {};
/**
* Language
* @type {?string}
*/
this.locale = null;
/**
* From: Setting => ACTIVITY SETTINGS => Activity Status => Display current activity as a status message
* @type {?boolean}
*/
this.activityDisplay = null;
/**
* Disable Direct Message from servers
* @type {Collection<Snowflake, boolean>}
*/
this.disableDMfromServer = new Collection();
/**
* Allow direct messages from server members
* @type {?boolean}
*/
this.DMfromServerMode = null;
/**
* Display images
* @type {?boolean}
*/
this.displayImage = null;
/**
* Display linked images
* @type {?boolean}
*/
this.linkedImageDisplay = null;
/**
* From: Setting => APP SETTINGS => Accessibility => Automatically play GIFs when Discord is focused.
* @type {?boolean}
*/
this.autoplayGIF = null;
/**
* Show embeds and preview website links pasted into chat
* @type {?boolean}
*/
this.previewLink = null;
/**
* From: Setting => APP SETTINGS => Accessibility => Play Animated Emojis
* @type {?boolean}
*/
this.animatedEmojis = null;
/**
* From: Setting => APP SETTINGS => Accessibility => Text-to-speech => Allow playback
* @type {?boolean}
*/
this.allowTTS = null;
/**
* From: Setting => APP SETTINGS => Appearance => Message Display => Compact Mode
* @type {?boolean}
*/
this.compactMode = null;
/**
* From: Setting => APP SETTINGS => Text & Images => Emoji => Convert Emoticons
* @type {?boolean}
*/
this.convertEmoticons = null;
/**
* SAFE DIRECT MESSAGING
* @type {?DMScanLevel}
*/
this.DMScanLevel = null;
/**
* From: Setting => APP SETTINGS => Appearance => Theme
* @type {'dark' | 'light' | null}
*/
this.theme = '';
/**
* Developer Mode (Copy ID, etc.)
* @type {?boolean}
*/
this.developerMode = null;
/**
* AFK timeout (receives notifications)
* @type {?number}
*/
this.afkTimeout = null;
/**
* Sticker animation mode
* @type {?stickerAnimationMode}
*/
this.stickerAnimationMode = null;
/**
* WHO CAN ADD YOU AS A FRIEND ?
* @type {?object}
* @see {@link https://luna.gitlab.io/discord-unofficial-docs/user_settings.html#friend-source-flags-structure}
*/
this.addFriendFrom = {
all: null,
mutual_friends: null,
mutual_guilds: null,
};
/**
* From: Setting => APP SETTINGS => Text & Images => Emoji => Show emoji reactions
* @type {?boolean}
*/
this.showEmojiReactions = null;
/**
* Custom Stauts
* @type {?object}
* @see {@link https://luna.gitlab.io/discord-unofficial-docs/custom_status.html#customstatus-structure}
*/
this.customStatus = null;
/**
* Guild folder and position
* @type {GuildFolderManager}
*/
this.guildFolder = new GuildFolderManager(client);
// Todo: add new method from Discum
}
/**
* Patch data file
* https://github.com/Merubokkusu/Discord-S.C.U.M/blob/master/discum/user/user.py
* @private
* @param {Object} data Raw Data to patch
*/
_patch(data = {}) {
this.rawSetting = Object.assign(this.rawSetting, data);
if ('locale' in data) {
this.locale = localeSetting[data.locale];
}
if ('show_current_game' in data) {
this.activityDisplay = data.show_current_game;
}
if ('default_guilds_restricted' in data) {
this.DMfromServerMode = data.default_guilds_restricted;
}
if ('inline_attachment_media' in data) {
this.displayImage = data.inline_attachment_media;
}
if ('inline_embed_media' in data) {
this.linkedImageDisplay = data.inline_embed_media;
}
if ('gif_auto_play' in data) {
this.autoplayGIF = data.gif_auto_play;
}
if ('render_embeds' in data) {
this.previewLink = data.render_embeds;
}
if ('animate_emoji' in data) {
this.animatedEmojis = data.animate_emoji;
}
if ('enable_tts_command' in data) {
this.allowTTS = data.enable_tts_command;
}
if ('message_display_compact' in data) {
this.compactMode = data.message_display_compact;
}
if ('convert_emoticons' in data) {
this.convertEmoticons = data.convert_emoticons;
}
if ('explicit_content_filter' in data) {
this.DMScanLevel = DMScanLevel[data.explicit_content_filter];
}
if ('theme' in data) {
this.theme = data.theme;
}
if ('developer_mode' in data) {
this.developerMode = data.developer_mode;
}
if ('afk_timeout' in data) {
this.afkTimeout = data.afk_timeout * 1000; // Second => milisecond
}
if ('animate_stickers' in data) {
this.stickerAnimationMode = stickerAnimationMode[data.animate_stickers];
}
if ('render_reactions' in data) {
this.showEmojiReactions = data.render_reactions;
}
if ('custom_status' in data) {
this.customStatus = data.custom_status || {}; // Thanks PinkDuwc._#3443 reported this issue
this.customStatus.status = data.status;
}
if ('friend_source_flags' in data) {
this.addFriendFrom = {
all: data.friend_source_flags.all || false,
mutual_friends: data.friend_source_flags.all ? true : data.friend_source_flags.mutual_friends,
mutual_guilds: data.friend_source_flags.all ? true : data.friend_source_flags.mutual_guilds,
};
}
if ('guild_folders' in data) {
data.guild_folders.map((folder, index) =>
this.guildFolder.cache.set(index, new GuildFolder(this.client, folder)),
);
}
if ('restricted_guilds' in data) {
this.disableDMfromServer = new Collection(data.restricted_guilds.map(guildId => [guildId, true]));
}
}
async fetch() {
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
const data = await this.client.api.users('@me').settings.get();
this._patch(data);
return this;
}
/**
* Edit data
* @param {Object} data Data to edit
* @private
*/
async edit(data) {
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
const res = await this.client.api.users('@me').settings.patch({ data });
this._patch(res);
return this;
}
/**
* Set compact mode
* @param {boolean | null} value Compact mode enable or disable
* @returns {boolean}
*/
async setDisplayCompactMode(value) {
if (typeof value !== 'boolean' && value !== null) {
throw new TypeError('INVALID_TYPE', 'value', 'boolean | null', true);
}
if (!value) value = !this.compactMode;
if (value !== this.compactMode) {
await this.edit({ message_display_compact: value });
}
return this.compactMode;
}
/**
* Discord Theme
* @param {null |dark |light} value Theme to set
* @returns {theme}
*/
async setTheme(value) {
const validValues = ['dark', 'light'];
if (typeof value !== 'string' && value !== null) {
throw new TypeError('INVALID_TYPE', 'value', 'string | null', true);
}
if (!validValues.includes(value)) {
if (value == validValues[0]) value = validValues[1];
else value = validValues[0];
}
if (value !== this.theme) {
await this.edit({ theme: value });
}
return this.theme;
}
/**
* CustomStatus Object
* @typedef {Object} CustomStatusOption
* @property {string | null} text Text to set
* @property {string | null} status The status to set: 'online', 'idle', 'dnd', 'invisible' or null.
* @property {EmojiResolvable | null} emoji UnicodeEmoji, DiscordEmoji, or null.
* @property {number | null} expires The number of seconds until the status expires, or null.
*/
/**
* Set custom status
* @param {?CustomStatus | CustomStatusOption} options CustomStatus
*/
setCustomStatus(options) {
if (typeof options !== 'object') {
this.edit({ custom_status: null });
} else if (options instanceof CustomStatus) {
options = options.toJSON();
let data = {
emoji_name: null,
expires_at: null,
text: null,
};
if (typeof options.state === 'string') {
data.text = options.state;
}
if (options.emoji) {
if (options.emoji?.id) {
data.emoji_name = options.emoji?.name;
data.emoji_id = options.emoji?.id;
} else {
data.emoji_name = typeof options.emoji?.name === 'string' ? options.emoji?.name : null;
}
}
this.edit({ custom_status: data });
} else {
let data = {
emoji_name: null,
expires_at: null,
text: null,
};
if (typeof options.text === 'string') {
if (options.text.length > 128) {
throw new RangeError('[INVALID_VALUE] Custom status text must be less than 128 characters');
}
data.text = options.text;
}
if (options.emoji) {
const emoji = this.client.emojis.resolve(options.emoji);
if (emoji) {
data.emoji_name = emoji.name;
data.emoji_id = emoji.id;
} else {
data.emoji_name = typeof options.emoji === 'string' ? options.emoji : null;
}
}
if (typeof options.expires === 'number') {
if (options.expires < Date.now()) {
throw new RangeError(`[INVALID_VALUE] Custom status expiration must be greater than ${Date.now()}`);
}
data.expires_at = new Date(options.expires).toISOString();
}
if (['online', 'idle', 'dnd', 'invisible'].includes(options.status)) this.edit({ status: options.status });
this.edit({ custom_status: data });
}
}
/**
* * 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`
* @param {localeSetting} value Locale to set
* @returns {locale}
*/
async setLocale(value) {
if (typeof value !== 'string') {
throw new TypeError('INVALID_TYPE', 'value', 'string', true);
}
if (!localeSetting[value]) throw new Error('INVALID_LOCALE');
if (localeSetting[value] !== this.locale) {
await this.edit({ locale: localeSetting[value] });
}
return this.locale;
}
// TODO: Guild positions & folders
// Change Index in Array [Hidden]
/**
*
* @param {Array} array Array
* @param {number} from Index1
* @param {number} to Index2
* @returns {Array}
* @private
*/
_move(array, from, to) {
array.splice(to, 0, array.splice(from, 1)[0]);
return array;
}
// TODO: Move Guild
// folder to folder
// folder to home
// home to home
// home to folder
/**
* Change Guild Position (from * to Folder or Home)
* @param {GuildIDResolve} guildId guild.id
* @param {number} newPosition Guild Position
* * **WARNING**: Type = `FOLDER`, newPosition is the guild's index in the Folder.
* @param {number} type Move to folder or home
* * `FOLDER`: 1
* * `HOME`: 2
* @param {FolderID} folderId If you want to move to folder
* @private
*/
guildChangePosition(guildId, newPosition, type, folderId) {
// Get Guild default position
// Escape
const oldGuildFolderPosition = this.rawSetting.guild_folders.findIndex(value => value.guild_ids.includes(guildId));
const newGuildFolderPosition = this.rawSetting.guild_folders.findIndex(value =>
value.guild_ids.includes(this.rawSetting.guild_positions[newPosition]),
);
if (type == 2 || `${type}`.toUpperCase() == 'HOME') {
// Delete GuildID from Folder and create new Folder
// Check it is folder
const folder = this.rawSetting.guild_folders[oldGuildFolderPosition];
if (folder.id) {
this.rawSetting.guild_folders[oldGuildFolderPosition].guild_ids = this.rawSetting.guild_folders[
oldGuildFolderPosition
].guild_ids.filter(v => v !== guildId);
}
this.rawSetting.guild_folders = this._move(
this.rawSetting.guild_folders,
oldGuildFolderPosition,
newGuildFolderPosition,
);
this.rawSetting.guild_folders[newGuildFolderPosition].id = null;
} else if (type == 1 || `${type}`.toUpperCase() == 'FOLDER') {
// Delete GuildID from oldFolder
this.rawSetting.guild_folders[oldGuildFolderPosition].guild_ids = this.rawSetting.guild_folders[
oldGuildFolderPosition
].guild_ids.filter(v => v !== guildId);
// Index new Folder
const folderIndex = this.rawSetting.guild_folders.findIndex(value => value.id == folderId);
const folder = this.rawSetting.guild_folders[folderIndex];
folder.guild_ids.push(guildId);
folder.guild_ids = [...new Set(folder.guild_ids)];
folder.guild_ids = this._move(
folder.guild_ids,
folder.guild_ids.findIndex(v => v == guildId),
newPosition,
);
}
this.edit({ guild_folders: this.rawSetting.guild_folders });
}
/**
* Restricted guilds setting
* @param {boolean} status Restricted status
* @returns {Promise}
*/
restrictedGuilds(status) {
if (typeof status !== 'boolean') {
throw new TypeError('INVALID_TYPE', 'status', 'boolean', true);
}
return this.edit({
default_guilds_restricted: status,
restricted_guilds: status ? this.client.guilds.cache.map(v => v.id) : [],
});
}
/**
* Add a guild to the list of restricted guilds.
* @param {GuildIDResolve} guildId The guild to add
* @returns {Promise}
*/
addRestrictedGuild(guildId) {
const temp = Object.assign(
[],
this.disableDMfromServer.map((v, k) => k),
);
if (temp.includes(guildId)) throw new Error('Guild is already restricted');
temp.push(guildId);
return this.edit({ restricted_guilds: temp });
}
/**
* Remove a guild from the list of restricted guilds.
* @param {GuildIDResolve} guildId The guild to remove
* @returns {Promise}
*/
removeRestrictedGuild(guildId) {
if (!this.disableDMfromServer.delete(guildId)) throw new Error('Guild is already restricted');
return this.edit({ restricted_guilds: this.disableDMfromServer.map((v, k) => k) });
}
}
module.exports = ClientUserSettingManager;

View File

@ -1,104 +0,0 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const BaseManager = require('./BaseManager');
const DeveloperPortalApplication = require('../structures/DeveloperPortalApplication');
const Team = require('../structures/Team');
/**
* Manages API methods for users and stores their cache.
* @extends {BaseManager}
*/
class DeveloperPortalManager extends BaseManager {
constructor(client) {
super(client);
/**
* A collection of all the applications the client has.
* @type {Collection<Snowflake, DeveloperPortalApplication>}
* @readonly
*/
this.applications = new Collection();
/**
* A collection of all the teams the client has.
* @type {Collection<Snowflake, Team>}
* @readonly
*/
this.teams = new Collection(); // Collection<Snowflake, Team>
}
/**
* Fetches all the applications & teams the client has.
* @returns {Promise<DeveloperPortalManager>}
*/
async fetch() {
const promise1 = this.client.api.applications.get({
query: {
with_team_applications: true,
},
});
const promise2 = this.client.api.teams.get();
const [applications, teams] = await Promise.all([promise1, promise2]);
for (const team of teams) {
this.teams.set(team.id, new Team(this.client, team));
}
for (const application of applications) {
this.applications.set(application.id, new DeveloperPortalApplication(this.client, application));
}
return this;
}
/**
* Creates a new Team.
* @param {string} name Name of the team
* @returns {Promise<Team>}
*/
async createTeam(name) {
const team = await this.client.api.teams.post({
data: {
name: name,
},
});
this.teams.set(team.id, new Team(this.client, team));
return this.teams.get(team.id);
}
/**
* Creates a new application.
* @param {string} name Name of the application
* @param {?Snowflake | Team} teamId The team to create the application in
* @returns {Promise<DeveloperPortalApplication>}
*/
async createApplication(name, teamId = null) {
teamId = teamId instanceof Team ? teamId.id : teamId;
const application = await this.client.api.applications.post({
data: {
name,
team_id: teamId,
},
});
this.applications.set(application.id, new DeveloperPortalApplication(this.client, application));
return this.applications.get(application.id);
}
/**
* Deletes an application.
* @param {Snowflake} id Application ID
* @param {?number} MFACode 2FA code (if 2FA is enabled)
* @returns {Promise<void>}
*/
async deleteApplication(id, MFACode) {
if (MFACode) {
await this.client.api.applications[`${id}/delete`].post({
query: {
code: MFACode,
},
});
} else {
await this.client.api.applications[`${id}/delete`].post();
}
this.applications.delete(id);
return undefined;
}
}
module.exports = DeveloperPortalManager;

View File

@ -1,24 +0,0 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const BaseManager = require('./BaseManager');
/**
* Manages API methods for users and stores their cache.
* @extends {BaseManager}
*/
class GuildFolderManager extends BaseManager {
constructor(client) {
super(client);
/**
* The guild folder cache (Index, GuildFolder)
* @type {Collection<number, GuildFolder>}
*/
this.cache = new Collection();
}
_refresh() {
this.cache.clear();
}
}
module.exports = GuildFolderManager;

View File

@ -1,148 +0,0 @@
'use strict';
const BaseManager = require('./BaseManager');
/**
* Manages API methods for users and stores their cache.
* @extends {BaseManager}
* @see {@link https://luna.gitlab.io/discord-unofficial-docs/user_settings.html}
*/
class GuildSettingManager extends BaseManager {
constructor(client, guildId = null) {
super(client);
/**
* Raw data
* @type {Object}
*/
this.rawSetting = {};
/**
* Guild Id
* @type {?Snowflake}
*/
this.guildId = guildId;
}
/**
* Get the guild
* @type {?Guild}
* @readonly
*/
get guild() {
return this.client.guilds.cache.get(this.guildId);
}
/**
* Patch data file
* @private
* @param {Object} data Raw Data to patch
*/
_patch(data = {}) {
this.rawSetting = Object.assign(this.rawSetting, data);
if ('suppress_everyone' in data) {
/**
* Notification setting > Suppress `@everyone` and `@here`
* @type {?boolean}
*/
this.suppressEveryone = data.suppress_everyone;
}
if ('suppress_roles' in data) {
/**
* Notification setting > Suppress all role `@mention`
* @type {?boolean}
*/
this.suppressRoles = data.suppress_roles;
}
if ('mute_scheduled_events' in data) {
/**
* Notification setting > Mute new events
* @type {?boolean}
*/
this.muteScheduledEvents = data.mute_scheduled_events;
}
if ('message_notifications' in data) {
/**
* Notification setting > Message notifications
* * `0` = All messages
* * `1` = Only @mentions
* * `2` = Nothing
* @type {?number}
*/
this.messageNotifications = data.message_notifications;
}
if ('flags' in data) {
/**
* Flags (unknown)
* @type {?number}
*/
this.flags = data.flags;
}
if ('mobile_push' in data) {
/**
* Notification setting > Mobile push notifications
* @type {?boolean}
*/
this.mobilePush = data.mobile_push;
}
if ('muted' in data) {
/**
* Mute server
* @type {?boolean}
*/
this.muted = data.muted;
}
if ('mute_config' in data && data.mute_config !== null) {
/**
* Mute config (muted = true)
* * `muteConfig.endTime`: End time (Date)
* * `muteConfig.selectedTimeWindow`: Selected time window (seconds) (number)
* @type {?Object}
*/
this.muteConfig = {
endTime: new Date(data.mute_config.end_time),
selectedTimeWindow: data.mute_config.selected_time_window,
};
} else {
this.muteConfig = null;
}
if ('hide_muted_channels' in data) {
/**
* Hide muted channels
* @type {?boolean}
*/
this.hideMutedChannels = data.hide_muted_channels;
}
if ('channel_overrides' in data) {
/**
* Channel overrides (unknown)
* @type {?Array}
*/
this.channelOverrides = data.channel_overrides;
}
if ('notify_highlights' in data) {
/**
* Notification setting > Suppress highlights
* * `0` = ??? (unknown)
* * `1` = Enable
* * `2` = Disable
* @type {?number}
*/
this.notifyHighlights = data.notify_highlights;
}
if ('version' in data) {
/**
* Version (unknown)
* @type {?number}
*/
this.version = data.version;
}
}
/**
* Edit guild settings
* @param {Object} data Data to edit
* @returns {Promise<GuildSettingManager>}
*/
async edit(data) {
const data_ = await this.client.api.users('@me').settings.patch(data);
this._patch(data_);
return this;
}
}
module.exports = GuildSettingManager;

View File

@ -1,57 +0,0 @@
'use strict';
const CachedManager = require('./CachedManager');
const { Error } = require('../errors/DJSError');
const Session = require('../structures/Session');
/**
* Manages API methods for users and stores their cache.
* @extends {CachedManager}
*/
class SessionManager extends CachedManager {
constructor(client, iterable) {
super(client, Session, iterable);
}
/**
* The cache of Sessions
* @type {Collection<string, Session>}
* @name SessionManager#cache
*/
/**
* Fetch all sessions of the client.
* @returns {Promise<SessionManager>}
*/
fetch() {
return new Promise((resolve, reject) => {
this.client.api.auth.sessions
.get()
.then(data => {
const allData = data.user_sessions;
this.cache.clear();
for (const session of allData) {
this._add(new Session(this.client, session), true, { id: session.id_hash });
}
resolve(this);
})
.catch(reject);
});
}
/**
* Logout the client (remote).
* @param {string | null} mfaCode MFA code (if 2FA is enabled)
* @returns {Promise<undefined>}
*/
logoutAllDevices(mfaCode) {
if (typeof this.client.password !== 'string') throw new Error('REQUIRE_PASSWORD');
return this.client.api.auth.sessions.logout({
data: {
session_id_hashes: this.cache.map(session => session.id),
password: this.client.password,
code: typeof mfaCode === 'string' ? mfaCode : undefined,
},
});
}
}
module.exports = SessionManager;

View File

@ -1,520 +0,0 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { ApplicationRoleConnectionMetadata } = require('./ApplicationRoleConnectionMetadata');
const Base = require('./Base');
const ApplicationFlags = require('../util/ApplicationFlags');
const { ClientApplicationAssetTypes, Endpoints, ApplicationRoleConnectionMetadataTypes } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Permissions = require('../util/Permissions');
const SnowflakeUtil = require('../util/SnowflakeUtil');
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
/**
* Represents an OAuth2 Application.
* @extends {Base}
* @abstract
*/
class DeveloperPortalApplication extends Base {
constructor(client, data) {
super(client);
this._patch(data);
}
_patch(data) {
/**
* The application's id
* @type {Snowflake}
*/
this.id = data.id;
if ('name' in data) {
/**
* The name of the application
* @type {?string}
*/
this.name = data.name;
} else {
this.name ??= null;
}
if ('description' in data) {
/**
* The application's description
* @type {?string}
*/
this.description = data.description;
} else {
this.description ??= null;
}
if ('icon' in data) {
/**
* The application's icon hash
* @type {?string}
*/
this.icon = data.icon;
} else {
this.icon ??= null;
}
if ('bot' in data) {
/**
* Bot application
* @type {User}
*/
this.bot = this.client.users._add(data.bot);
}
/**
* The tags this application has (max of 5)
* @type {string[]}
*/
this.tags = data.tags ?? [];
if ('install_params' in data) {
/**
* Settings for this application's default in-app authorization
* @type {?ClientApplicationInstallParams}
*/
this.installParams = {
scopes: data.install_params.scopes,
permissions: new Permissions(data.install_params.permissions).freeze(),
};
} else {
this.installParams ??= null;
}
if ('custom_install_url' in data) {
/**
* This application's custom installation URL
* @type {?string}
*/
this.customInstallURL = data.custom_install_url;
} else {
this.customInstallURL = null;
}
if ('flags' in data) {
/**
* The flags this application has
* @type {ApplicationFlags}
*/
this.flags = new ApplicationFlags(data.flags).freeze();
}
if ('cover_image' in data) {
/**
* The hash of the application's cover image
* @type {?string}
*/
this.cover = data.cover_image;
} else {
this.cover ??= null;
}
if ('rpc_origins' in data) {
/**
* The application's RPC origins, if enabled
* @type {string[]}
*/
this.rpcOrigins = data.rpc_origins;
} else {
this.rpcOrigins ??= [];
}
if ('bot_require_code_grant' in data) {
/**
* If this application's bot requires a code grant when using the OAuth2 flow
* @type {?boolean}
*/
this.botRequireCodeGrant = data.bot_require_code_grant;
} else {
this.botRequireCodeGrant ??= null;
}
if ('bot_public' in data) {
/**
* If this application's bot is public
* @type {?boolean}
*/
this.botPublic = data.bot_public;
} else {
this.botPublic ??= null;
}
/**
* The owner of this OAuth application
* @type {?(User|Team)}
*/
this.owner = null;
if (data.owner.username == `team${data.owner.id}` && data.owner.discriminator == '0000') {
this.owner = this.client.developerPortal.teams.get(data.owner.id);
} else {
this.owner = data.owner ? this.client.users._add(data.owner) : this.owner ?? null;
}
/**
* Redirect URIs for this application
* @type {Array<string>}
*/
this.redirectURIs = data.redirect_uris ?? [];
/**
* BOT_HTTP_INTERACTIONS feature flag
* @type {?string}
*/
this.interactionEndpointURL = data.interactions_endpoint_url ?? null;
/**
* Public key
* @type {?string}
*/
this.publicKey = data.verify_key ?? null;
/**
* @typedef {Object} Tester
* @property {number} state The state of the tester (2: Accepted, 1: Pending)
* @property {User} user The user that the tester is
*/
/**
* User tester
* @type {Collection<Snowflake, Tester>}
*/
this.testers = new Collection(); // <Snowflake, User>
/**
* Terms of service URL
* @type {?string}
*/
this.TermsOfService = data.terms_of_service_url ?? null;
/**
* Privacy policy URL
* @type {?string}
*/
this.PrivacyPolicy = data.privacy_policy_url ?? null;
if ('role_connections_verification_url' in data) {
/**
* This application's role connection verification entry point URL
* @type {?string}
*/
this.roleConnectionsVerificationURL = data.role_connections_verification_url;
} else {
this.roleConnectionsVerificationURL ??= null;
}
}
/**
* The timestamp the application was created at
* @type {number}
* @readonly
*/
get createdTimestamp() {
return SnowflakeUtil.timestampFrom(this.id);
}
/**
* The time the application was created at
* @type {Date}
* @readonly
*/
get createdAt() {
return new Date(this.createdTimestamp);
}
/**
* A link to the application's icon.
* @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
iconURL({ format, size } = {}) {
if (!this.icon) return null;
return this.client.rest.cdn.AppIcon(this.id, this.icon, { format, size });
}
/**
* A link to this application's cover image.
* @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
coverURL({ format, size } = {}) {
if (!this.cover) return null;
return Endpoints.CDN(this.client.options.http.cdn).AppIcon(this.id, this.cover, { format, size });
}
/**
* Asset data.
* @typedef {Object} ApplicationAsset
* @property {Snowflake} id The asset's id
* @property {string} name The asset's name
* @property {string} type The asset's type
*/
/**
* Gets the application's rich presence assets.
* @returns {Promise<Array<ApplicationAsset>>}
* @deprecated This will be removed in the next major as it is unsupported functionality.
*/
async fetchAssets() {
const assets = await this.client.api.oauth2.applications(this.id).assets.get();
return assets.map(a => ({
id: a.id,
name: a.name,
type: AssetTypes[a.type - 1],
}));
}
/**
* Whether this application is partial
* @type {boolean}
* @readonly
*/
get partial() {
return !this.name;
}
/**
* Obtains this application from Discord.
* @returns {Promise<DeveloperPortalApplication>}
*/
async fetch() {
const app = await this.client.api.applications[this.id].get();
this._patch(app);
return this;
}
/**
* Gets all testers for this application.
* @returns {Promise<DeveloperPortalApplication>}
*/
async fetchTesters() {
const app = await this.client.api.applications[this.id].allowlist.get();
this.testers = new Collection();
for (const tester of app || []) {
this.testers.set(tester.user.id, {
state: tester.state,
user: this.client.users._add(tester.user),
});
}
return this;
}
/**
* Add user to this application's allowlist.
* @param {string} username Username of the user to add
* @param {string} discriminator Discriminator of the user to add
* @returns {Promise<DeveloperPortalApplication>}
*/
async addTester(username, discriminator) {
const app = await this.client.api.applications[this.id].allowlist.post({
data: {
username,
discriminator,
},
});
this.testers.set(app.user.id, {
state: app.state,
user: this.client.users._add(app.user),
});
return this;
}
/**
* Delete user from this application's allowlist.
* @param {UserResolvable} user User
* @returns {Promise<DeveloperPortalApplication>}
*/
async deleteTester(user) {
const userId = this.client.users.resolveId(user);
await this.client.api.applications[this.id].allowlist[userId].delete();
this.testers.delete(userId);
return this;
}
/**
* The data for editing a application.
* @typedef {Object} ApplicationEditData
* @property {string} [name] The name of the app
* @property {string} [description] The description of the app
* @property {?(BufferResolvable|Base64Resolvable)} [icon] The icon of the app
* @property {?(BufferResolvable|Base64Resolvable)} [cover] The application's default rich presence invite
* @property {boolean} [botPublic] When false only app owner can join the app's bot to guilds
* @property {boolean} [botRequireCodeGrant] When true the app's bot will only join upon completion of the full oauth2 code grant flow
* @property {?string} [TermsOfService] ToS URL
* @property {?string} [PrivacyPolicy] Privacy policy URL
* @property {number} [flags] The application's public flags
* @property {Array<string>} [redirectURIs] Redirect URIs (OAuth2 only)
* @property {Array<string>} [tags] Up to 5 tags describing the content and functionality of the application
*/
/**
* Edits this application.
* @param {ApplicationEditData} data Edit data for the application
* @returns {Promise<DeveloperPortalApplication>}
*/
async edit(data) {
const _data = {};
if (data.name) _data.name = data.name;
if (typeof data.icon !== 'undefined') {
_data.icon = await DataResolver.resolveImage(data.icon);
}
if (data.description) _data.description = data.description;
if (typeof data.cover !== 'undefined') {
_data.cover = await DataResolver.resolveImage(data.cover);
}
if (data.botPublic) _data.bot_public = data.botPublic;
if (data.botRequireCodeGrant) _data.bot_require_code_grant = data.botRequireCodeGrant;
if (data.TermsOfService) _data.terms_of_service_url = data.TermsOfService;
if (data.PrivacyPolicy) _data.privacy_policy_url = data.PrivacyPolicy;
if (data.flags) _data.flags = data.flags;
if (data.redirectURIs) _data.redirect_uris = data.redirectURIs;
if (data.tags) _data.tags = data.tags;
//
const app = await this.client.api.applications[this.id].patch({ data: _data });
this._patch(app);
return this;
}
/**
* Creates a new bot for this application.
* @returns {Promise<DeveloperPortalApplication>}
*/
async createBot() {
if (this.bot) throw new Error('Application already has a bot.');
await this.client.api.applications[this.id].bot.post();
const app = await this.fetch();
return app;
}
/**
* Reset CLient Secret for this application.
* @param {number} MFACode The MFA code (if required)
* @returns {Promise<string>}
*/
async resetClientSecret(MFACode) {
const app = MFACode
? await this.client.api.applications[this.id].reset.post({
data: {
code: MFACode,
},
})
: await this.client.api.applications[this.id].reset.post();
return app.secret;
}
/**
* Reset Bot Token for this application.
* @param {number} MFACode The MFA code (if required)
* @returns {Promise<string>}
*/
async resetBotToken(MFACode) {
const app = MFACode
? await this.client.api.applications[this.id].bot.reset.post({
data: {
code: MFACode,
},
})
: await this.client.api.applications[this.id].bot.reset.post();
return app.token;
}
/**
* Deletes this application.
* @param {number} MFACode The MFA code (if required)
* @returns {Promise<void>}
*/
delete(MFACode) {
return this.client.developerPortal.deleteApplication(this.id, MFACode);
}
/**
* Add new image to this application. (RPC)
* @param {BufferResolvable|Base64Resolvable} image Image Resolvable
* @param {string} name Name of the image
* @returns {ApplicationAsset}
*/
async addAsset(image, name) {
const data = await DataResolver.resolveImage(image);
const asset = await this.client.api.applications[this.id].assets.post({
data: {
type: 1,
name,
image: data,
},
});
return {
id: asset.id,
name: asset.name,
type: AssetTypes[asset.type - 1],
};
}
/**
* Delete an image from this application. (RPC)
* @param {Snowflake} id ID of the image
* @returns {Promise<undefined>}
*/
async deleteAsset(id) {
await this.client.api.applications[this.id].assets[id].delete();
return undefined;
}
/**
* Gets this application's role connection metadata records
* @returns {Promise<ApplicationRoleConnectionMetadata[]>}
*/
async fetchRoleConnectionMetadataRecords() {
const metadata = await this.client.api.applications(this.id)('role-connections').metadata.get();
return metadata.map(data => new ApplicationRoleConnectionMetadata(data));
}
/**
* Data for creating or editing an application role connection metadata.
* @typedef {Object} ApplicationRoleConnectionMetadataEditOptions
* @property {string} name The name of the metadata field
* @property {?Object<Locale, string>} [nameLocalizations] The name localizations for the metadata field
* @property {string} description The description of the metadata field
* @property {?Object<Locale, string>} [descriptionLocalizations] The description localizations for the metadata field
* @property {string} key The dictionary key of the metadata field
* @property {ApplicationRoleConnectionMetadataType} type The type of the metadata field
*/
/**
* Updates this application's role connection metadata records
* @param {ApplicationRoleConnectionMetadataEditOptions[]} records The new role connection metadata records
* @returns {Promise<ApplicationRoleConnectionMetadata[]>}
*/
async editRoleConnectionMetadataRecords(records) {
const newRecords = await this.client.api
.applications(this.client.user.id)('role-connections')
.metadata.put({
data: records.map(record => ({
type: typeof record.type === 'string' ? ApplicationRoleConnectionMetadataTypes[record.type] : record.type,
key: record.key,
name: record.name,
name_localizations: record.nameLocalizations,
description: record.description,
description_localizations: record.descriptionLocalizations,
})),
});
return newRecords.map(data => new ApplicationRoleConnectionMetadata(data));
}
/**
* When concatenated with a string, this automatically returns the application's name instead of the
* Application object.
* @returns {?string}
* @example
* // Logs: Application name: My App
* console.log(`Application name: ${application}`);
*/
toString() {
return this.name;
}
toJSON() {
return super.toJSON({ createdTimestamp: true });
}
}
module.exports = DeveloperPortalApplication;

View File

@ -1,75 +0,0 @@
'use strict';
const Base = require('./Base');
/**
* Guild Folder.
* @abstract
*/
class GuildFolder extends Base {
constructor(client, data) {
super(client);
this._patch(data);
}
_patch(data) {
if ('id' in data) {
/**
* The guild folder's id
* @type {Snowflake}
*/
this.id = data.id;
}
if ('name' in data) {
/**
* The guild folder's name
* @type {string}
*/
this.name = data.name;
}
if ('color' in data) {
/**
* The base 10 color of the folder
* @type {number}
*/
this.color = data.color;
}
if ('guild_ids' in data) {
/**
* The guild folder's guild ids
* @type {Snowflake[]}
*/
this.guild_ids = data.guild_ids;
}
}
/**
* The hexadecimal version of the folder color, with a leading hashtag
* @type {string}
* @readonly
*/
get hexColor() {
return `#${this.color.toString(16).padStart(6, '0')}`;
}
/**
* Guilds in the folder
* @type {Collection<Snowflake, Guild>}
* @readonly
*/
get guilds() {
return this.client.guilds.cache.filter(guild => this.guild_ids.includes(guild.id));
}
toJSON() {
return {
id: this.id,
name: this.name,
color: this.color,
guild_ids: this.guild_ids,
};
}
}
module.exports = GuildFolder;

View File

@ -1,81 +0,0 @@
'use strict';
const Base = require('./Base');
/**
* @typedef {Object} SessionClientInfo
* @property {string} location Location of the client (using IP address)
* @property {string} platform Platform of the client
* @property {string} os Operating system of the client
*/
/**
* Represents a Client OAuth2 Application Team.
* @extends {Base}
*/
class Session extends Base {
constructor(client, data) {
super(client);
this._patch(data);
}
_patch(data) {
if ('id_hash' in data) {
/**
* The session hash id
* @type {string}
*/
this.id = data.id_hash;
}
if ('approx_last_used_time' in data) {
this.approxLastUsedTime = data.approx_last_used_time;
}
if ('client_info' in data) {
/**
* The client info
* @type {SessionClientInfo}
*/
this.clientInfo = data.client_info;
}
}
/**
* The timestamp the client was last used at.
* @type {number}
* @readonly
*/
get createdTimestamp() {
return this.createdAt.getTime();
}
/**
* The time the client was last used at.
* @type {Date}
* @readonly
*/
get createdAt() {
return new Date(this.approxLastUsedTime);
}
/**
* Logout the client (remote).
* @param {string | null} mfaCode MFA code (if 2FA is enabled)
* @returns {Promise<undefined>}
*/
logout(mfaCode) {
if (typeof this.client.password !== 'string') throw new Error('REQUIRE_PASSWORD', 'You must provide a password.');
return this.client.api.auth.sessions.logout({
data: {
session_id_hashes: [this.id],
password: this.client.password,
code: typeof mfaCode === 'string' ? mfaCode : undefined,
},
});
}
toJSON() {
return super.toJSON();
}
}
module.exports = Session;

View File

@ -1,14 +1,13 @@
'use strict'; 'use strict';
const { Buffer } = require('buffer');
const crypto = require('crypto'); const { Buffer } = require('node:buffer');
const crypto = require('node:crypto');
const EventEmitter = require('node:events'); const EventEmitter = require('node:events');
const { StringDecoder } = require('node:string_decoder');
const { setTimeout } = require('node:timers'); const { setTimeout } = require('node:timers');
const { StringDecoder } = require('string_decoder');
const chalk = require('chalk');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const { encode: urlsafe_b64encode } = require('safe-base64');
const WebSocket = require('ws'); const WebSocket = require('ws');
const { defaultUA } = require('./Constants'); const { UserAgent } = require('./Constants');
const Options = require('./Options'); const Options = require('./Options');
const defaultClientOptions = Options.createDefault(); const defaultClientOptions = Options.createDefault();
@ -22,9 +21,9 @@ const receiveEvent = {
NONCE_PROOF: 'nonce_proof', NONCE_PROOF: 'nonce_proof',
PENDING_REMOTE_INIT: 'pending_remote_init', PENDING_REMOTE_INIT: 'pending_remote_init',
HEARTBEAT_ACK: 'heartbeat_ack', HEARTBEAT_ACK: 'heartbeat_ack',
PENDING_LOGIN: 'pending_ticket', PENDING_TICKET: 'pending_ticket',
CANCEL: 'cancel', CANCEL: 'cancel',
SUCCESS: 'pending_login', PENDING_LOGIN: 'pending_login',
}; };
const sendEvent = { const sendEvent = {
@ -37,266 +36,167 @@ const Event = {
READY: 'ready', READY: 'ready',
ERROR: 'error', ERROR: 'error',
CANCEL: 'cancel', CANCEL: 'cancel',
WAIT: 'pending', WAIT_SCAN: 'pending',
SUCCESS: 'success',
FINISH: 'finish', FINISH: 'finish',
CLOSED: 'closed', CLOSED: 'closed',
DEBUG: 'debug',
}; };
/** /**
* @typedef {Object} DiscordAuthWebsocketOptions * Discord Auth QR
* @property {?boolean} [debug=false] Log debug info
* @property {?boolean} [hiddenLog=false] Hide log ?
* @property {?boolean} [autoLogin=false] Automatically login (DiscordJS.Client Login) ?
* @property {?boolean} [failIfError=true] Throw error ?
* @property {?boolean} [generateQR=true] Create QR Code ?
* @property {?number} [apiVersion=9] API Version
* @property {?string} [userAgent] User Agent
* @property {?Object.<string,string>} [wsProperties] Web Socket Properties
*/
/**
* Discord Auth QR (Discord.RemoteAuth will be removed in the future, v13.9.0 release)
* @extends {EventEmitter} * @extends {EventEmitter}
* @abstract * @abstract
*/ */
class DiscordAuthWebsocket extends EventEmitter { class DiscordAuthWebsocket extends EventEmitter {
#ws = null;
#heartbeatInterval = null;
#expire = null;
#publicKey = null;
#privateKey = null;
#ticket = null;
#fingerprint = '';
#userDecryptString = '';
/** /**
* Creates a new DiscordAuthWebsocket instance. * Creates a new DiscordAuthWebsocket instance.
* @param {?DiscordAuthWebsocketOptions} options Options
*/ */
constructor(options) { constructor() {
super(); super();
/** this.token = '';
* WebSocket
* @type {?WebSocket}
*/
this.ws = null;
/**
* Heartbeat Interval
* @type {?number}
*/
this.heartbeatInterval = NaN;
this._expire = NaN;
this.key = null;
/**
* User (Scan QR Code)
* @type {?Object}
*/
this.user = null;
/**
* Temporary Token (Scan QR Code)
* @type {?string}
*/
this.token = undefined;
/**
* Real Token (Login)
* @type {?string}
*/
this.realToken = undefined;
/**
* Fingerprint (QR Code)
* @type {?string}
*/
this.fingerprint = null;
/**
* Captcha Handler
* @type {Function}
* @param {Captcha} data hcaptcha data
* @returns {Promise<string>} Captcha token
*/
// eslint-disable-next-line no-unused-vars
this.captchaSolver = data =>
new Promise((resolve, reject) => {
reject(
new Error(`
Captcha Handler not found - Please set captchaSolver option
Example captchaSolver function:
new DiscordAuthWebsocket({
captchaSolver: async (data) => {
const token = await hcaptchaSolver(data.captcha_sitekey, 'discord.com');
return token;
} }
});
`),
);
});
/** /**
* Captcha Cache * @type {string}
* @type {?Captcha}
*/ */
this.captchaCache = null; get AuthURL() {
return baseURL + this.#fingerprint;
}
this._validateOptions(options); /**
* @type {Date}
*/
get exprire() {
return this.#expire;
}
this.callFindRealTokenCount = 0;
}
/** /**
* Get expire time * @type {UserRaw}
* @type {string} Expire time
* @readonly
*/ */
get exprireTime() { get user() {
return this._expire.toLocaleString('en-US'); return DiscordAuthWebsocket.decryptUser(this.#userDecryptString);
} }
_validateOptions(options = {}) {
/** #createWebSocket(url) {
* Options this.#ws = new WebSocket(url, {
* @type {?DiscordAuthWebsocketOptions}
*/
this.options = {
debug: false,
hiddenLog: false,
autoLogin: false,
failIfError: true,
generateQR: true,
apiVersion: 9,
userAgent: defaultUA,
wsProperties: defaultClientOptions.ws.properties,
captchaSolver: () => new Error('Captcha Handler not found. Please set captchaSolver option.'),
};
if (typeof options == 'object') {
if (typeof options.debug == 'boolean') this.options.debug = options.debug;
if (typeof options.hiddenLog == 'boolean') this.options.hiddenLog = options.hiddenLog;
if (typeof options.autoLogin == 'boolean') this.options.autoLogin = options.autoLogin;
if (typeof options.failIfError == 'boolean') this.options.failIfError = options.failIfError;
if (typeof options.generateQR == 'boolean') this.options.generateQR = options.generateQR;
if (typeof options.apiVersion == 'number') this.options.apiVersion = options.apiVersion;
if (typeof options.userAgent == 'string') this.options.userAgent = options.userAgent;
if (typeof options.wsProperties == 'object') this.options.wsProperties = options.wsProperties;
if (typeof options.captchaSolver == 'function') this.captchaSolver = options.captchaSolver;
}
}
_createWebSocket(url) {
this.ws = new WebSocket(url, {
headers: { headers: {
Origin: 'https://discord.com', Origin: 'https://discord.com',
'User-Agent': this.options.userAgent, 'User-Agent': UserAgent,
}, },
}); });
this._handleWebSocket(); this.#handleWebSocket();
} }
_handleWebSocket() {
this.ws.on('error', error => { #handleWebSocket() {
this._logger('error', error); this.#ws.on('error', error => {
/**
* WS Error
* @event DiscordAuthWebsocket#error
* @param {Error} error Error
*/
this.emit(Event.ERROR, error);
}); });
this.ws.on('open', () => { this.#ws.on('open', () => {
this._logger('debug', 'Client Connected'); /**
* Debug Event
* @event DiscordAuthWebsocket#debug
* @param {string} msg Debug msg
*/
this.emit(Event.DEBUG, '[WS] Client Connected');
}); });
this.ws.on('close', () => { this.#ws.on('close', () => {
this._logger('debug', 'Connection closed.'); this.emit(Event.DEBUG, '[WS] Connection closed');
});
this.ws.on('message', message => {
this._handleMessage(JSON.parse(message));
}); });
this.#ws.on('message', this.#handleMessage.bind(this));
} }
_handleMessage(message) {
#handleMessage(message) {
message = JSON.parse(message);
switch (message.op) { switch (message.op) {
case receiveEvent.HELLO: { case receiveEvent.HELLO: {
this._ready(message); this.#ready(message);
break; break;
} }
case receiveEvent.NONCE_PROOF: { case receiveEvent.NONCE_PROOF: {
this._receiveNonceProof(message); this.#receiveNonceProof(message);
break; break;
} }
case receiveEvent.PENDING_REMOTE_INIT: { case receiveEvent.PENDING_REMOTE_INIT: {
this._pendingRemoteInit(message); this.#fingerprint = message.fingerprint;
break;
}
case receiveEvent.HEARTBEAT_ACK: {
this._logger('debug', 'Heartbeat acknowledged.');
this._heartbeatAck();
break;
}
case receiveEvent.PENDING_LOGIN: {
this._pendingLogin(message);
break;
}
case receiveEvent.CANCEL: {
this._logger('debug', 'Cancel login.');
/** /**
* Emitted whenever a user cancels the login process. * Ready Event
* @event DiscordAuthWebsocket#cancel * @event DiscordAuthWebsocket#ready
* @param {object} user User (Raw) * @param {DiscordAuthWebsocket} client WS
*/ */
this.emit(Event.CANCEL, this.user); this.emit(Event.READY, this);
break;
}
case receiveEvent.HEARTBEAT_ACK: {
this.emit(Event.DEBUG, `Heartbeat acknowledged.`);
this.#heartbeatAck();
break;
}
case receiveEvent.PENDING_TICKET: {
this.#pendingLogin(message);
break;
}
case receiveEvent.CANCEL: {
/**
* Cancel
* @event DiscordAuthWebsocket#cancel
* @param {DiscordAuthWebsocket} client WS
*/
this.emit(Event.CANCEL, this);
this.destroy(); this.destroy();
break; break;
} }
case receiveEvent.SUCCESS: {
this._logger('debug', 'Receive Token - Login Success.', message.ticket); case receiveEvent.PENDING_LOGIN: {
/** this.#ticket = message.ticket;
* Emitted whenever a token is created. (Fake token) this.#findRealToken();
* @event DiscordAuthWebsocket#success
* @param {object} user Discord User
* @param {string} token Discord Token (Fake)
*/
this.emit(Event.SUCCESS, this.user, message.ticket);
this.token = message.ticket;
this._findRealToken();
this._logger('default', 'Get token success.');
break;
}
default: {
this._logger('debug', `Unknown op: ${message.op}`, message);
}
}
}
_logger(type = 'default', ...message) {
if (this.options.hiddenLog) return;
switch (type.toLowerCase()) {
case 'error': {
// eslint-disable-next-line no-unused-expressions
this.options.failIfError
? this._throwError(new Error(message[0]))
: console.error(chalk.red(`[DiscordRemoteAuth] ERROR`), ...message);
break;
}
case 'default': {
console.log(chalk.green(`[DiscordRemoteAuth]`), ...message);
break;
}
case 'debug': {
if (this.options.debug) console.log(chalk.yellow(`[DiscordRemoteAuth] DEBUG`), ...message);
break; break;
} }
} }
} }
_throwError(error) {
console.log(chalk.red(`[DiscordRemoteAuth] ERROR`), error); #send(op, data) {
throw error; if (!this.#ws) return;
}
_send(op, data) {
if (!this.ws) this._throwError(new Error('WebSocket is not connected.'));
let payload = { op: op }; let payload = { op: op };
if (data !== null) payload = { ...payload, ...data }; if (data !== null) payload = { ...payload, ...data };
this._logger('debug', `Send Data:`, payload); this.#ws.send(JSON.stringify(payload));
this.ws.send(JSON.stringify(payload));
} }
_heartbeat() {
this._send(sendEvent.HEARTBEAT); #heartbeatAck() {
}
_heartbeatAck() {
setTimeout(() => { setTimeout(() => {
this._heartbeat(); this.#send(sendEvent.HEARTBEAT);
}, this.heartbeatInterval).unref(); }, this.#heartbeatInterval).unref();
} }
_ready(data) {
this._logger('debug', 'Attempting server handshake...'); #ready(data) {
this._expire = new Date(Date.now() + data.timeout_ms); this.emit(Event.DEBUG, 'Attempting server handshake...');
this.heartbeatInterval = data.heartbeat_interval; this.#expire = new Date(Date.now() + data.timeout_ms);
this._createKey(); this.#heartbeatInterval = data.heartbeat_interval;
this._heartbeatAck(); this.#createKey();
this._init(); this.#heartbeatAck();
this.#init();
} }
_createKey() {
if (this.key) this._throwError(new Error('Key is already created.')); #createKey() {
this.key = crypto.generateKeyPairSync('rsa', { const key = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048, modulusLength: 2048,
publicKeyEncoding: { publicKeyEncoding: {
type: 'spki', type: 'spki',
@ -307,35 +207,41 @@ new DiscordAuthWebsocket({
format: 'pem', format: 'pem',
}, },
}); });
this.#privateKey = key.privateKey;
this.#publicKey = key.publicKey;
} }
_createPublicKey() {
if (!this.key) this._throwError(new Error('Key is not created.')); #encodePublicKey() {
this._logger('debug', 'Generating public key...');
const decoder = new StringDecoder('utf-8'); const decoder = new StringDecoder('utf-8');
let pub_key = decoder.write(this.key.publicKey); let pub_key = decoder.write(this.#publicKey);
pub_key = pub_key.split('\n').slice(1, -2).join(''); pub_key = pub_key.split('\n').slice(1, -2).join('');
this._logger('debug', 'Public key generated.', pub_key);
return pub_key; return pub_key;
} }
_init() {
const public_key = this._createPublicKey(); #init() {
this._send(sendEvent.INIT, { encoded_public_key: public_key }); const public_key = this.#encodePublicKey();
this.#send(sendEvent.INIT, { encoded_public_key: public_key });
} }
_receiveNonceProof(data) {
#receiveNonceProof(data) {
const nonce = data.encrypted_nonce; const nonce = data.encrypted_nonce;
const decrypted_nonce = this._decryptPayload(nonce); const decrypted_nonce = this.#decryptPayload(nonce);
let proof = crypto.createHash('sha256').update(decrypted_nonce).digest(); const proof = crypto
proof = urlsafe_b64encode(proof); .createHash('sha256')
proof = proof.replace(/\s+$/, ''); .update(decrypted_nonce)
this._send(sendEvent.NONCE_PROOF, { proof: proof }); .digest()
this._logger('debug', `Nonce proof decrypted:`, proof); .toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+/, '')
.replace(/\s+$/, '');
this.#send(sendEvent.NONCE_PROOF, { proof: proof });
} }
_decryptPayload(encrypted_payload) {
if (!this.key) this._throwError(new Error('Key is not created.')); #decryptPayload(encrypted_payload) {
const payload = Buffer.from(encrypted_payload, 'base64'); const payload = Buffer.from(encrypted_payload, 'base64');
this._logger('debug', `Encrypted Payload (Buffer):`, payload);
const decoder = new StringDecoder('utf-8'); const decoder = new StringDecoder('utf-8');
const private_key = decoder.write(this.key.privateKey); const private_key = decoder.write(this.#privateKey);
const data = crypto.privateDecrypt( const data = crypto.privateDecrypt(
{ {
key: private_key, key: private_key,
@ -344,91 +250,79 @@ new DiscordAuthWebsocket({
}, },
payload, payload,
); );
this._logger('debug', `Decrypted Payload:`, data.toString());
return data; return data;
} }
_pendingLogin(data) {
const user_data = this._decryptPayload(data.encrypted_user_payload); #pendingLogin(data) {
const user = new User(user_data.toString()); const user_data = this.#decryptPayload(data.encrypted_user_payload);
this.user = user; this.#userDecryptString = user_data.toString();
/**
* @typedef {Object} UserRaw
* @property {Snowflake} id
* @property {string} username
* @property {number} discriminator
* @property {string} avatar
*/
/** /**
* Emitted whenever a user is scan QR Code. * Emitted whenever a user is scan QR Code.
* @event DiscordAuthWebsocket#pending * @event DiscordAuthWebsocket#pending
* @param {object} user Discord User Raw * @param {UserRaw} user Discord User Raw
*/ */
this.emit(Event.WAIT, user); this.emit(Event.WAIT_SCAN, this.user);
this._logger('debug', 'Waiting for user to finish login...');
this.user.prettyPrint(this);
this._logger('default', 'Please check your phone again to confirm login.');
} }
_pendingRemoteInit(data) {
this._logger('debug', `Pending Remote Init:`, data); #awaitLogin(client) {
/** return new Promise(r => {
* Emitted whenever a url is created. this.once(Event.FINISH, token => {
* @event DiscordAuthWebsocket#ready r(client.login(token));
* @param {string} fingerprint Fingerprint });
* @param {string} url DiscordAuthWebsocket
*/
this.emit(Event.READY, data.fingerprint, `${baseURL}${data.fingerprint}`);
this.fingerprint = data.fingerprint;
if (this.options.generateQR) this.generateQR();
}
_awaitLogin(client) {
this.once(Event.FINISH, (user, token) => {
this._logger('debug', 'Create login state...', user, token);
client.login(token);
}); });
} }
/** /**
* Connect to DiscordAuthWebsocket. * Connect WS
* @param {?Client} client Using only for auto login. * @param {Client} [client] DiscordJS Client
* @returns {undefined} * @returns {Promise<void>}
*/ */
connect(client) { connect(client) {
this._createWebSocket(wsURL); this.#createWebSocket(wsURL);
if (client && this.options.autoLogin) this._awaitLogin(client); if (client) {
return this.#awaitLogin(client);
} else {
return Promise.resolve();
} }
}
/** /**
* Disconnect from DiscordAuthWebsocket. * Destroy client
* @returns {undefined} * @returns {void}
*/ */
destroy() { destroy() {
if (!this.ws) this._throwError(new Error('WebSocket is not connected.')); if (!this.ws) return;
this.ws.close(); this.ws.close();
this.emit(Event.DEBUG, 'WebSocket closed.');
/** /**
* Emitted whenever a connection is closed. * Emitted whenever a connection is closed.
* @event DiscordAuthWebsocket#closed * @event DiscordAuthWebsocket#closed
* @param {boolean} loginState Login state
*/ */
this.emit(Event.CLOSED, this.token); this.emit(Event.CLOSED);
this._logger('debug', 'WebSocket closed.');
}
/**
* Generate QR code for user to scan (Terminal)
* @returns {undefined}
*/
generateQR() {
if (!this.fingerprint) this._throwError(new Error('Fingerprint is not created.'));
require('@aikochan2k6/qrcode-terminal').generate(`${baseURL}${this.fingerprint}`, {
small: true,
});
this._logger('default', `Please scan the QR code to continue.\nQR Code will expire in ${this.exprireTime}`);
} }
async _findRealToken(captchaSolveData) { /**
this.callFindRealTokenCount++; * Generate QR code for user to scan (Terminal)
if (!this.token) return this._throwError(new Error('Token is not created.')); * @returns {void}
if (!captchaSolveData && this.captchaCache) return this._throwError(new Error('Captcha is not solved.')); */
if (this.callFindRealTokenCount > 5) { generateQR() {
return this._throwError( if (!this.#fingerprint) return;
new Error( require('@aikochan2k6/qrcode-terminal').generate(this.AuthURL, {
`Failed to find real token (${this.callFindRealTokenCount} times) ${this.captchaCache ? '[Captcha]' : ''}`, small: true,
), });
);
} }
this._logger('debug', 'Find real token...');
const res = await ( #findRealToken() {
await fetch(`https://discord.com/api/v${this.options.apiVersion}/users/@me/remote-auth/login`, { return fetch(`https://discord.com/api/v9/users/@me/remote-auth/login`, {
method: 'POST', method: 'POST',
headers: { headers: {
Accept: '*/*', Accept: '*/*',
@ -438,76 +332,47 @@ new DiscordAuthWebsocket({
'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Site': 'same-origin',
'X-Debug-Options': 'bugReporterEnabled', 'X-Debug-Options': 'bugReporterEnabled',
'X-Super-Properties': `${Buffer.from(JSON.stringify(this.options.wsProperties), 'ascii').toString('base64')}`, 'X-Super-Properties': `${Buffer.from(JSON.stringify(defaultClientOptions.ws.properties), 'ascii').toString(
'base64',
)}`,
'X-Discord-Locale': 'en-US', 'X-Discord-Locale': 'en-US',
'User-Agent': this.options.userAgent, 'User-Agent': UserAgent,
Referer: 'https://discord.com/channels/@me', Referer: 'https://discord.com/channels/@me',
Connection: 'keep-alive', Connection: 'keep-alive',
Origin: 'https://discord.com', Origin: 'https://discord.com',
}, },
body: JSON.stringify( body: JSON.stringify({
captchaSolveData ticket: this.#ticket,
? { }),
ticket: this.token,
captcha_rqtoken: this.captchaCache.captcha_rqtoken,
captcha_key: captchaSolveData,
}
: {
ticket: this.token,
},
),
}) })
).json(); .then(r => r.json())
if (res?.captcha_key) { .then(res => {
this.captchaCache = res; if (res.encrypted_token) {
} else if (!res.encrypted_token) { this.token = this.#decryptPayload(res.encrypted_token).toString();
this._throwError(new Error('Request failed. Please try again.', res));
this.captchaCache = null;
} }
if (!res && this.captchaCache) {
this._logger('default', 'Captcha is detected. Please solve the captcha to continue.');
this._logger('debug', 'Try call captchaSolver()', this.captchaCache);
const token = await this.options.captchaSolver(this.captchaCache);
return this._findRealToken(token);
}
this.realToken = this._decryptPayload(res.encrypted_token).toString();
/** /**
* Emitted whenever a real token is found. * Emitted whenever a real token is found.
* @event DiscordAuthWebsocket#finish * @event DiscordAuthWebsocket#finish
* @param {object} user User * @param {string} token Discord Token
* @param {string} token Real token
*/ */
this.emit(Event.FINISH, this.user, this.realToken); this.emit(Event.FINISH, this.token);
return this; this.destroy();
} })
.catch(() => false);
} }
class User { static decryptUser(payload) {
constructor(payload) {
const values = payload.split(':'); const values = payload.split(':');
this.id = values[0]; const id = values[0];
this.username = values[3]; const username = values[3];
this.discriminator = values[1]; const discriminator = values[1];
this.avatar = values[2]; const avatar = values[2];
return this; return {
} id,
get avatarURL() { username,
return `https://cdn.discordapp.com/avatars/${this.id}/${this.avatar}.${ discriminator,
this.avatar.startsWith('a_') ? 'gif' : 'png' avatar,
}`; };
}
get tag() {
return `${this.username}#${this.discriminator}`;
}
prettyPrint(RemoteAuth) {
let string = `\n`;
string += ` ${chalk.bgBlue('User:')} `;
string += `${this.tag} (${this.id})\n`;
string += ` ${chalk.bgGreen('Avatar URL:')} `;
string += chalk.cyan(`${this.avatarURL}\n`);
string += ` ${chalk.bgMagenta('Token:')} `;
string += chalk.red(`${this.token ? this.token : 'Unknown'}`);
RemoteAuth._logger('default', string);
} }
} }

View File

@ -5,12 +5,13 @@ const process = require('node:process');
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const { Colors } = require('./Constants'); const { Colors } = require('./Constants');
const { RangeError, TypeError, Error: DJSError } = require('../errors'); const { Error: DiscordError, RangeError, TypeError } = require('../errors');
const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k); const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
const isObject = d => typeof d === 'object' && d !== null; const isObject = d => typeof d === 'object' && d !== null;
let deprecationEmittedForSplitMessage = false; let deprecationEmittedForSplitMessage = false;
let deprecationEmittedForRemoveMentions = false; let deprecationEmittedForRemoveMentions = false;
let deprecationEmittedForResolveAutoArchiveMaxLimit = false;
const TextSortableGroupTypes = ['GUILD_TEXT', 'GUILD_ANNOUCMENT', 'GUILD_FORUM']; const TextSortableGroupTypes = ['GUILD_TEXT', 'GUILD_ANNOUCMENT', 'GUILD_FORUM'];
const VoiceSortableGroupTypes = ['GUILD_VOICE', 'GUILD_STAGE_VOICE']; const VoiceSortableGroupTypes = ['GUILD_VOICE', 'GUILD_STAGE_VOICE'];
@ -138,6 +139,7 @@ class Util extends null {
* @property {boolean} [numberedList=false] Whether to escape numbered lists * @property {boolean} [numberedList=false] Whether to escape numbered lists
* @property {boolean} [maskedLink=false] Whether to escape masked links * @property {boolean} [maskedLink=false] Whether to escape masked links
*/ */
/** /**
* Escapes any Discord-flavour markdown in a string. * Escapes any Discord-flavour markdown in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -220,6 +222,7 @@ class Util extends null {
if (maskedLink) text = Util.escapeMaskedLink(text); if (maskedLink) text = Util.escapeMaskedLink(text);
return text; return text;
} }
/** /**
* Escapes code block markdown in a string. * Escapes code block markdown in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -228,6 +231,7 @@ class Util extends null {
static escapeCodeBlock(text) { static escapeCodeBlock(text) {
return text.replaceAll('```', '\\`\\`\\`'); return text.replaceAll('```', '\\`\\`\\`');
} }
/** /**
* Escapes inline code markdown in a string. * Escapes inline code markdown in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -236,6 +240,7 @@ class Util extends null {
static escapeInlineCode(text) { static escapeInlineCode(text) {
return text.replace(/(?<=^|[^`])``?(?=[^`]|$)/g, match => (match.length === 2 ? '\\`\\`' : '\\`')); return text.replace(/(?<=^|[^`])``?(?=[^`]|$)/g, match => (match.length === 2 ? '\\`\\`' : '\\`'));
} }
/** /**
* Escapes italic markdown in a string. * Escapes italic markdown in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -253,6 +258,7 @@ class Util extends null {
return `\\_${match}`; return `\\_${match}`;
}); });
} }
/** /**
* Escapes bold markdown in a string. * Escapes bold markdown in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -265,6 +271,7 @@ class Util extends null {
return '\\*\\*'; return '\\*\\*';
}); });
} }
/** /**
* Escapes underline markdown in a string. * Escapes underline markdown in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -277,6 +284,7 @@ class Util extends null {
return '\\_\\_'; return '\\_\\_';
}); });
} }
/** /**
* Escapes strikethrough markdown in a string. * Escapes strikethrough markdown in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -285,6 +293,7 @@ class Util extends null {
static escapeStrikethrough(text) { static escapeStrikethrough(text) {
return text.replaceAll('~~', '\\~\\~'); return text.replaceAll('~~', '\\~\\~');
} }
/** /**
* Escapes spoiler markdown in a string. * Escapes spoiler markdown in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -293,6 +302,7 @@ class Util extends null {
static escapeSpoiler(text) { static escapeSpoiler(text) {
return text.replaceAll('||', '\\|\\|'); return text.replaceAll('||', '\\|\\|');
} }
/** /**
* Escapes escape characters in a string. * Escapes escape characters in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -301,6 +311,7 @@ class Util extends null {
static escapeEscape(text) { static escapeEscape(text) {
return text.replaceAll('\\', '\\\\'); return text.replaceAll('\\', '\\\\');
} }
/** /**
* Escapes heading characters in a string. * Escapes heading characters in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -309,6 +320,7 @@ class Util extends null {
static escapeHeading(text) { static escapeHeading(text) {
return text.replaceAll(/^( {0,2}[*-] +)?(#{1,3} )/gm, '$1\\$2'); return text.replaceAll(/^( {0,2}[*-] +)?(#{1,3} )/gm, '$1\\$2');
} }
/** /**
* Escapes bulleted list characters in a string. * Escapes bulleted list characters in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -317,6 +329,7 @@ class Util extends null {
static escapeBulletedList(text) { static escapeBulletedList(text) {
return text.replaceAll(/^( *)[*-]( +)/gm, '$1\\-$2'); return text.replaceAll(/^( *)[*-]( +)/gm, '$1\\-$2');
} }
/** /**
* Escapes numbered list characters in a string. * Escapes numbered list characters in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -325,6 +338,7 @@ class Util extends null {
static escapeNumberedList(text) { static escapeNumberedList(text) {
return text.replaceAll(/^( *\d+)\./gm, '$1\\.'); return text.replaceAll(/^( *\d+)\./gm, '$1\\.');
} }
/** /**
* Escapes masked link characters in a string. * Escapes masked link characters in a string.
* @param {string} text Content to escape * @param {string} text Content to escape
@ -334,6 +348,16 @@ class Util extends null {
return text.replaceAll(/\[.+\]\(.+\)/gm, '\\$&'); return text.replaceAll(/\[.+\]\(.+\)/gm, '\\$&');
} }
/**
* @typedef {Object} FetchRecommendedShardsOptions
* @property {number} [guildsPerShard=1000] Number of guilds assigned per shard
* @property {number} [multipleOf=1] The multiple the shard count should round up to. (16 for large bot sharding)
*/
static fetchRecommendedShards() {
throw new DiscordError('INVALID_USER_API');
}
/** /**
* Parses emoji info out of a string. The string must be one of: * Parses emoji info out of a string. The string must be one of:
* * A UTF-8 emoji (no id) * * A UTF-8 emoji (no id)
@ -623,24 +647,20 @@ class Util extends null {
/** /**
* Resolves the maximum time a guild's thread channels should automatically archive in case of no recent activity. * Resolves the maximum time a guild's thread channels should automatically archive in case of no recent activity.
* @deprecated * @param {Guild} guild The guild to resolve this limit from.
* @deprecated This will be removed in the next major version.
* @returns {number} * @returns {number}
*/ */
static resolveAutoArchiveMaxLimit() { static resolveAutoArchiveMaxLimit() {
return 10080; if (!deprecationEmittedForResolveAutoArchiveMaxLimit) {
process.emitWarning(
// eslint-disable-next-line max-len
"The Util.resolveAutoArchiveMaxLimit method and the 'MAX' option are deprecated and will be removed in the next major version.",
'DeprecationWarning',
);
deprecationEmittedForResolveAutoArchiveMaxLimit = true;
} }
return 10080;
/**
* Lazily evaluates a callback function (yea it's v14 :yay:)
* @param {Function} cb The callback to lazily evaluate
* @returns {Function}
* @example
* const User = lazy(() => require('./User'));
* const user = new (User())(client, data);
*/
static lazy(cb) {
let defaultValue;
return () => (defaultValue ??= cb());
} }
/** /**
@ -708,95 +728,6 @@ class Util extends null {
}; };
} }
static async getAttachments(client, channelId, ...files) {
files = files.flat(2);
if (!files.length) return [];
files = files.map((file, i) => ({
filename: file.name ?? file.attachment?.name ?? file.attachment?.filename ?? 'file.jpg',
// 25MB = 26_214_400bytes
file_size: Math.floor((26_214_400 / 10) * Math.random()),
id: `${i}`,
}));
const { attachments } = await client.api.channels[channelId].attachments.post({
data: {
files,
},
});
return attachments;
}
static uploadFile(data, url) {
return new Promise((resolve, reject) => {
fetch(url, {
method: 'PUT',
body: data,
})
.then(res => {
if (res.ok) {
resolve(res);
} else {
reject(res);
}
})
.catch(reject);
});
}
static testImportModule(name) {
try {
require.resolve(name);
return true;
} catch {
return false;
}
}
static getProxyObject(proxy) {
const protocol = new URL(proxy).protocol.slice(0, -1);
const mapObject = {
http: 'https', // Cuz we can't use http for discord
https: 'https',
socks4: 'socks',
socks5: 'socks',
'pac+http': 'pac',
'pac+https': 'pac',
};
const proxyType = mapObject[protocol];
switch (proxyType) {
case 'https': {
if (!Util.testImportModule('https-proxy-agent')) {
throw new DJSError('MISSING_MODULE', 'https-proxy-agent', 'npm install https-proxy-agent');
}
const httpsProxyAgent = require('https-proxy-agent');
return new httpsProxyAgent.HttpsProxyAgent(proxy);
}
case 'socks': {
if (!Util.testImportModule('socks-proxy-agent')) {
throw new DJSError('MISSING_MODULE', 'socks-proxy-agent', 'npm install socks-proxy-agent');
}
const socksProxyAgent = require('socks-proxy-agent');
return new socksProxyAgent.SocksProxyAgent(proxy);
}
case 'pac': {
if (!Util.testImportModule('pac-proxy-agent')) {
throw new DJSError('MISSING_MODULE', 'pac-proxy-agent', 'npm install pac-proxy-agent');
}
const pacProxyAgent = require('pac-proxy-agent');
return new pacProxyAgent.PacProxyAgent(proxy);
}
default: {
if (!Util.testImportModule('proxy-agent')) {
throw new DJSError('MISSING_MODULE', 'proxy-agent', 'npm install proxy-agent@5');
}
const proxyAgent = require('proxy-agent');
return new proxyAgent(proxy);
}
}
}
/** /**
* Gets an array of the channel types that can be moved in the channel group. For example, a GuildText channel would * Gets an array of the channel types that can be moved in the channel group. For example, a GuildText channel would
* return an array containing the types that can be ordered within the text channels (always at the top), and a voice * return an array containing the types that can be ordered within the text channels (always at the top), and a voice
@ -831,94 +762,38 @@ class Util extends null {
return Number(BigInt(userId) >> 22n) % 6; return Number(BigInt(userId) >> 22n) % 6;
} }
static clientRequiredAction(client, code) { static async getUploadURL(client, channelId, files) {
let msg = ''; if (!files.length) return [];
let stopClient = false; files = files.map((file, i) => ({
switch (code) { filename: file.name,
case null: { // 25MB = 26_214_400bytes
msg = 'All required actions have been completed.'; file_size: Math.floor((26_214_400 / 10) * Math.random()),
break; id: `${i}`,
} }));
case 'AGREEMENTS': { const { attachments } = await client.api.channels[channelId].attachments.post({
msg = 'You need to accept the new Terms of Service and Privacy Policy.';
// https://discord.com/api/v9/users/@me/agreements
client.api
.users('@me')
.agreements.patch({
data: { data: {
terms: true, files,
privacy: true,
}, },
})
.then(() => {
client.emit(
'debug',
'[USER_REQUIRED_ACTION] Successfully accepted the new Terms of Service and Privacy Policy.',
);
})
.catch(e => {
client.emit(
'debug',
`[USER_REQUIRED_ACTION] Failed to accept the new Terms of Service and Privacy Policy: ${e}`,
);
}); });
break; return attachments;
} }
case 'REQUIRE_CAPTCHA': {
msg = 'You need to complete a captcha.'; static uploadFile(data, url) {
stopClient = true; return new Promise((resolve, reject) => {
break; fetch(url, {
} method: 'PUT',
case 'REQUIRE_VERIFIED_EMAIL': { body: data,
msg = 'You need to verify your email.'; duplex: 'half', // Node.js v20
stopClient = true; })
break; .then(res => {
} if (res.ok) {
case 'REQUIRE_REVERIFIED_EMAIL': { resolve(res);
msg = 'You need to reverify your email.';
stopClient = true;
break;
}
case 'REQUIRE_VERIFIED_PHONE': {
msg = 'You need to verify your phone number.';
stopClient = true;
break;
}
case 'REQUIRE_REVERIFIED_PHONE': {
msg = 'You need to reverify your phone number.';
stopClient = true;
break;
}
case 'REQUIRE_VERIFIED_EMAIL_OR_VERIFIED_PHONE': {
msg = 'You need to verify your email or verify your phone number.';
stopClient = true; // Maybe not
break;
}
case 'REQUIRE_REVERIFIED_EMAIL_OR_VERIFIED_PHONE': {
msg = 'You need to reverify your email or verify your phone number.';
stopClient = true;
break;
}
case 'REQUIRE_VERIFIED_EMAIL_OR_REVERIFIED_PHONE': {
msg = 'You need to verify your email or reverify your phone number.';
stopClient = true;
break;
}
case 'REQUIRE_REVERIFIED_EMAIL_OR_REVERIFIED_PHONE': {
msg = 'You need to reverify your email or reverify your phone number.';
stopClient = true;
break;
}
default: {
msg = `Unknown required action: ${code}`;
break;
}
}
if (stopClient) {
client.emit('error', new Error(`[USER_REQUIRED_ACTION] ${msg}`));
} else { } else {
client.emit('debug', `[USER_REQUIRED_ACTION] ${msg}`); reject(res);
} }
})
.catch(reject);
});
} }
} }

81
typings/enums.d.ts vendored
View File

@ -10,67 +10,6 @@ export const enum ActivityTypes {
COMPETING = 5, COMPETING = 5,
} }
export const enum DMScanLevel {
NOT_SCAN = 0,
NOT_FRIEND = 1,
EVERYONE = 2,
}
export const enum stickerAnimationMode {
ALWAYS = 0,
INTERACTION = 1,
NEVER = 2,
}
export const enum NitroType {
NONE = 0,
NITRO_CLASSIC = 1,
NITRO_BOOST = 2,
NITRO_BASIC = 3,
}
export const enum RelationshipTypes {
NONE = 0,
FRIEND = 1,
BLOCKED = 2,
PENDING_INCOMING = 3,
PENDING_OUTGOING = 4,
IMPLICIT = 5,
}
export const enum localeSetting {
DANISH = 'da',
GERMAN = 'de',
ENGLISH_UK = 'en-GB',
ENGLISH_US = 'en-US',
SPANISH = 'es-ES',
FRENCH = 'fr',
CROATIAN = 'hr',
ITALIAN = 'it',
LITHUANIAN = 'lt',
HUNGARIAN = 'hu',
DUTCH = 'nl',
NORWEGIAN = 'no',
POLISH = 'pl',
BRAZILIAN_PORTUGUESE = 'pt-BR',
ROMANIA_ROMANIAN = 'ro',
FINNISH = 'fi',
SWEDISH = 'sv-SE',
VIETNAMESE = 'vi',
TURKISH = 'tr',
CZECH = 'cs',
GREEK = 'el',
BULGARIAN = 'bg',
RUSSIAN = 'ru',
UKRAINIAN = 'uk',
HINDI = 'hi',
THAI = 'th',
CHINA_CHINESE = 'zh-CN',
JAPANESE = 'ja',
TAIWAN_CHINESE = 'zh-TW',
KOREAN = 'ko',
}
export const enum ApplicationCommandTypes { export const enum ApplicationCommandTypes {
CHAT_INPUT = 1, CHAT_INPUT = 1,
USER = 2, USER = 2,
@ -203,13 +142,6 @@ export const enum GuildScheduledEventStatuses {
CANCELED = 4, CANCELED = 4,
} }
export const enum HypeSquadType {
LEAVE = 0,
HOUSE_BRAVERY = 1,
HOUSE_BRILLIANCE = 2,
HOUSE_BALANCE = 3,
}
export const enum InteractionResponseTypes { export const enum InteractionResponseTypes {
PONG = 1, PONG = 1,
CHANNEL_MESSAGE_WITH_SOURCE = 4, CHANNEL_MESSAGE_WITH_SOURCE = 4,
@ -249,8 +181,6 @@ export const enum MessageButtonStyles {
export const enum MessageComponentTypes { export const enum MessageComponentTypes {
ACTION_ROW = 1, ACTION_ROW = 1,
BUTTON = 2, BUTTON = 2,
/** @deprecated Use `STRING_SELECT` instead */
SELECT_MENU = 3,
STRING_SELECT = 3, STRING_SELECT = 3,
TEXT_INPUT = 4, TEXT_INPUT = 4,
USER_SELECT = 5, USER_SELECT = 5,
@ -260,8 +190,6 @@ export const enum MessageComponentTypes {
} }
export const enum SelectMenuComponentTypes { export const enum SelectMenuComponentTypes {
/** @deprecated Use `STRING_SELECT` instead */
SELECT_MENU = 3,
STRING_SELECT = 3, STRING_SELECT = 3,
USER_SELECT = 5, USER_SELECT = 5,
ROLE_SELECT = 6, ROLE_SELECT = 6,
@ -344,3 +272,12 @@ export enum ApplicationRoleConnectionMetadataTypes {
BOOLEAN_EQUAL, BOOLEAN_EQUAL,
BOOLEAN_NOT_EQUAL, BOOLEAN_NOT_EQUAL,
} }
export const enum RelationshipTypes {
NONE = 0,
FRIEND = 1,
BLOCKED = 2,
PENDING_INCOMING = 3,
PENDING_OUTGOING = 4,
IMPLICIT = 5,
}

1927
typings/index.d.ts vendored

File diff suppressed because it is too large Load Diff