Merge pull request #978 from aiko-chan-ai/rewrite-base-v13.17.1
refactor: Rewrite base v13.17.1 - Updated v3
This commit is contained in:
commit
6f3340513c
@ -7,7 +7,7 @@
|
||||
"import"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021
|
||||
"ecmaVersion": 13
|
||||
},
|
||||
"env": {
|
||||
"es2021": true,
|
||||
|
@ -1,93 +0,0 @@
|
||||
# Extending Discord.js-selfbot-v13
|
||||
> `credit: Discum`
|
||||
|
||||
How to add extra API wraps to Discord.js-selfbot-v13?
|
||||
# Table of Contents
|
||||
- [HTTP APIs](#HTTP-APIs)
|
||||
- [Gateway APIs](#Gateway-APIs)
|
||||
|
||||
### HTTP APIs:
|
||||
|
||||
```js
|
||||
URL example:
|
||||
'https://discord.com/api/v9/users/@me'
|
||||
const url = client.api.users['@me'];
|
||||
/* Method: GET | POST | PUT | PATCH | DELETE */
|
||||
Option:
|
||||
#1
|
||||
query: Object
|
||||
|
||||
- example: https://discord.com/api/v9/users/@me?abc=123&xyz=ok (GET)
|
||||
|
||||
client.api.users['@me'].get({
|
||||
query: {
|
||||
abc: 123,
|
||||
xyz: 'ok',
|
||||
}
|
||||
});
|
||||
|
||||
#2
|
||||
|
||||
body + files: Object + Array
|
||||
-> 'content-type': FormData (POST)
|
||||
|
||||
#3
|
||||
|
||||
data: Object
|
||||
-> 'content-type': 'application/json'
|
||||
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
|
||||
###### GET:
|
||||
```js
|
||||
await client.api.users['@me'].get({ versioned: true });
|
||||
/* Request: https://discord.com/api/v9/users/@me */
|
||||
await client.api.users['@me'].get({ versioned: false });
|
||||
/* Request: https://discord.com/api/users/@me */
|
||||
```
|
||||
###### POST:
|
||||
```js
|
||||
await client.api.channels[channel.id].messages.post({ versioned: true, data: {}, files: [] });
|
||||
/* Request: https://discord.com/api/v9/channels/{channel.id}/messages */
|
||||
```
|
||||
###### PUT:
|
||||
```js
|
||||
await client.api
|
||||
.guilds(guild.id)
|
||||
.bans(user.id)
|
||||
.put({
|
||||
versioned: true,
|
||||
data: {},
|
||||
});
|
||||
/* Request: https://discord.com/api/guilds/{guild.id}/bans/{user.id} */
|
||||
```
|
||||
###### PATCH:
|
||||
```js
|
||||
await client.api.users['@me'].patch({ versioned: true, data: {} });
|
||||
/* Request: https://discord.com/api/v9/users/@me */
|
||||
```
|
||||
###### DELETE:
|
||||
```js
|
||||
await client.api.hypesquad.online.delete({ versioned: true });
|
||||
/* Request: https://discord.com/api/v9/hypesquad/online */
|
||||
```
|
||||
### Gateway APIs
|
||||
You need to send data to the port and listen for an event. This is quite complicated but if you want to use an existing event, here are the instructions
|
||||
|
||||
###### SEND:
|
||||
```js
|
||||
const { Constants } = require('discord.js-selfbot-v13');
|
||||
// Global gateway (example update presence)
|
||||
client.ws.broadcast({
|
||||
op: Constants.Opcodes.STATUS_UPDATE,
|
||||
d: {},
|
||||
});
|
||||
// Guild gateway (example get all members)
|
||||
guild.shard.send({
|
||||
op: Constants.Opcodes.REQUEST_GUILD_MEMBERS,
|
||||
d: {},
|
||||
});
|
||||
```
|
@ -1,18 +0,0 @@
|
||||
## Discord's local RPC servers (~ discord app)
|
||||
|
||||
```js
|
||||
const { Client, RichPresence, DiscordRPCServer } = require('discord.js-selfbot-v13');
|
||||
|
||||
const client = new Client();
|
||||
|
||||
client.once('ready', async () => {
|
||||
const server = await new DiscordRPCServer(client, false)
|
||||
server.on('activity', async data => {
|
||||
if (!data.activity) return;
|
||||
const activity = new RichPresence(client, data.activity);
|
||||
client.user.setActivity(activity);
|
||||
});
|
||||
});
|
||||
|
||||
client.login('token');
|
||||
```
|
@ -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 | no overlap |
|
||||
|------|---------|------------|
|
||||
| 2.1k | | |
|
||||
| 128k | | |
|
||||
|
||||
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:
|
@ -1,87 +0,0 @@
|
||||
# Quick Links:
|
||||
- [Set Guild Folder and Position](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/Guild.md#discord-guild-set-position)
|
||||
- [DM group](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/Guild.md#group-dm)
|
||||
- [Join Guild](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/Guild.md#join-guild-using-invite)
|
||||
- [Community](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/Guild.md#set-community)
|
||||
|
||||
## Discord Guild set position
|
||||
<details open>
|
||||
<summary><strong>Click to show</strong></summary>
|
||||
|
||||
Code:
|
||||
```js
|
||||
guild.setPosition(position, type, folderID);
|
||||
// Position: The guild's index in the directory or out of the directory
|
||||
// Type:
|
||||
// + 'FOLDER': Move guild to folder
|
||||
// + 'HOME': Move the guild out of the directory
|
||||
// FolderID: The folder's ID , if you want to move the guild to a folder
|
||||
```
|
||||
Response
|
||||
```js
|
||||
Guild {}
|
||||
```
|
||||
</details>
|
||||
|
||||
## Group DM
|
||||
<details open>
|
||||
<summary><strong>Click to show</strong></summary>
|
||||
|
||||
Code:
|
||||
```js
|
||||
/* Create */
|
||||
const memberAdd = [
|
||||
client.users.cache.get('id1'),
|
||||
client.users.cache.get('id2'),
|
||||
...
|
||||
client.users.cache.get('id9')
|
||||
]
|
||||
// Max member add to Group: 9, Min: 2
|
||||
await client.channels.createGroupDM(memberAdd);
|
||||
/* Edit */
|
||||
const groupDM = client.channels.cache.get('id');
|
||||
await groupDM.setName('New Name');
|
||||
await groupDM.setIcon('iconURL');
|
||||
await groupDM.getInvite();
|
||||
await groupDM.fetchInvite();
|
||||
await groupDM.removeInvite(invite);
|
||||
await groupDM.addMember(user);
|
||||
await groupDM.removeMember(user);
|
||||
/* Text Channel not Bulk delete */
|
||||
await groupDM.send('Hello World');
|
||||
await groupDM.delete(); // Leave
|
||||
/* Voice Channel */
|
||||
await groupDM.call()
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## Join Guild using Invite
|
||||
<details open>
|
||||
<summary><strong>Click to show</strong></summary>
|
||||
|
||||
```js
|
||||
await client.fetchInvite('code').then(async invite => {
|
||||
await invite.acceptInvite(true);
|
||||
});
|
||||
```
|
||||
`invite.acceptInvite(true);` => Auto skip verify screen
|
||||
|
||||
<img src= 'https://cdn.discordapp.com/attachments/820557032016969751/957247688666132520/unknown.png'>
|
||||
|
||||
<strong>But if you are blocked by HCaptcha, this will not work</strong>
|
||||
</details>
|
||||
|
||||
## Set Community
|
||||
<details open>
|
||||
<summary><strong>Click to show</strong></summary>
|
||||
|
||||
```js
|
||||
await guild.setCommunity(stats: boolean, publicUpdatesChannel: TextChannelResolvable, rulesChannel: TextChannelResolvable, reason?: string): Promise<Guild>;
|
||||
// Enable with default
|
||||
await guild.setCommunity(true);
|
||||
// Disable
|
||||
await guild.setCommunity(false);
|
||||
```
|
||||
</details>
|
@ -17,40 +17,10 @@
|
||||
|
||||
## Interaction
|
||||
<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>
|
||||
|
||||
### [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 open>
|
||||
|
||||
@ -64,13 +34,7 @@ await message.contextMenu(botID, commandName);
|
||||
Code:
|
||||
```js
|
||||
const Discord = require('discord.js-selfbot-v13');
|
||||
// Selfhost WebEmbed: https://github.com/aiko-chan-ai/WebEmbed
|
||||
const w = new Discord.WebEmbed({
|
||||
shorten: true,
|
||||
hidden: false, // if you send this embed with MessagePayload.options.embeds, it must set to false
|
||||
baseURL: '', // if you want self-host API, else skip :v
|
||||
shortenAPI: '', // if you want Custom shortenAPI (Method: Get, response: Text => URL), else skip :v
|
||||
})
|
||||
const w = new Discord.WebEmbed()
|
||||
.setAuthor({ name: 'hello', url: 'https://google.com' })
|
||||
.setColor('RED')
|
||||
.setDescription('description uh')
|
||||
@ -83,7 +47,7 @@ const w = new Discord.WebEmbed({
|
||||
.setVideo(
|
||||
'https://cdn.discordapp.com/attachments/877060758092021801/957691816143097936/The_Quintessential_Quintuplets_And_Rick_Astley_Autotune_Remix.mp4',
|
||||
);
|
||||
message.channel.send({ content: `Hello world`, embeds: [w] }) // Patched :)
|
||||
message.channel.send({ content: `Hello world ${Discord.WebEmbed.hiddenEmbed} ${w}` });
|
||||
|
||||
```
|
||||
### Features & Issues
|
||||
|
@ -1,10 +1,3 @@
|
||||
## Setup
|
||||
```js
|
||||
const client = new Client({
|
||||
syncStatus: false,
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Status and RPC
|
||||
|
||||
<strong>Custom Status</strong>
|
||||
@ -68,57 +61,7 @@ client.user.setActivity(r);
|
||||
|
||||
> Tutorial:
|
||||
|
||||
## Method 1:
|
||||
|
||||
+ 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+)
|
||||
## Method 1: (Discord URL, v2.3.78+)
|
||||
|
||||
```js
|
||||
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'>
|
||||
|
||||
## Method 3 (Custom URL, 2.3.78+)
|
||||
## Method 2 (Custom URL, 2.3.78+)
|
||||
|
||||
```js
|
||||
const Discord = require('discord.js-selfbot-v13');
|
||||
|
@ -42,10 +42,3 @@ await message.channel.sendSlash('718642000898818048', 'sauce', a)
|
||||
### Result
|
||||
|
||||

|
||||
|
||||
# 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)
|
||||
|
170
Document/User.md
170
Document/User.md
@ -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>
|
@ -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;
|
||||
*/
|
||||
```
|
@ -14,7 +14,7 @@
|
||||
|
||||
## 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.
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
|
||||
**Node.js 16.6.0 or newer is required**
|
||||
|
||||
> Recommended Node.js version: 18 (LTS)
|
||||
> Recommended Node.js version: 18+ (LTS)
|
||||
|
||||
```sh-session
|
||||
npm install discord.js-selfbot-v13@latest
|
||||
@ -106,9 +106,6 @@ console.log('%cWorked!', 'font-size: 50px');
|
||||
console.log(`%cYou now have your token in the clipboard!`, 'font-size: 16px');
|
||||
```
|
||||
|
||||
Credit: <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
|
||||
|
||||
- Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
|
||||
@ -127,7 +124,6 @@ Github Discussion: [Here](https://github.com/aiko-chan-ai/discord.js-selfbot-v13
|
||||
A patched version of discord, with bot login support
|
||||
- 📕 [***aiko-chan-ai/Discord-Markdown***](https://github.com/aiko-chan-ai/Discord-Markdown) <br/>
|
||||
Better Markdown to text chat Discord.
|
||||
- 📗 ...
|
||||
|
||||
## Star History
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "discord.js-selfbot-v13",
|
||||
"version": "2.15.1",
|
||||
"version": "3.0.0",
|
||||
"description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]",
|
||||
"main": "./src/index.js",
|
||||
"types": "./typings/index.d.ts",
|
||||
@ -54,21 +54,14 @@
|
||||
"@aikochan2k6/qrcode-terminal": "^0.12.1",
|
||||
"@discordjs/builders": "^1.6.3",
|
||||
"@discordjs/collection": "^1.5.3",
|
||||
"@discordjs/voice": "^0.16.0",
|
||||
"@sapphire/async-queue": "^1.5.0",
|
||||
"@sapphire/shapeshift": "^3.9.3",
|
||||
"@types/node-fetch": "^2.6.7",
|
||||
"@types/ws": "^8.5.8",
|
||||
"chalk": "^4.1.2",
|
||||
"discord-api-types": "^0.37.61",
|
||||
"fetch-cookie": "^2.1.0",
|
||||
"form-data": "^4.0.0",
|
||||
"json-bigint": "^1.0.0",
|
||||
"lodash.permutations": "^1.0.0",
|
||||
"node-fetch": "^2.6.9",
|
||||
"safe-base64": "^2.0.1-0",
|
||||
"string-similarity": "^4.0.4",
|
||||
"string_decoder": "^1.3.0",
|
||||
"tough-cookie": "^4.1.3",
|
||||
"ws": "^8.14.2"
|
||||
},
|
||||
|
@ -13,12 +13,11 @@ const Util = require('../util/Util');
|
||||
class BaseClient extends EventEmitter {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
if (options.intents) {
|
||||
process.emitWarning('Intents is not available.', 'DeprecationWarning');
|
||||
}
|
||||
if (typeof options.captchaSolver === 'function') {
|
||||
options.captchaService = 'custom';
|
||||
}
|
||||
|
||||
/**
|
||||
* The options the client was instantiated with
|
||||
* @type {ClientOptions}
|
||||
|
@ -1,46 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
const process = require('node:process');
|
||||
const { setInterval, setTimeout } = require('node:timers');
|
||||
const { setInterval } = require('node:timers');
|
||||
const { setTimeout } = require('node:timers');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { getVoiceConnection } = require('@discordjs/voice');
|
||||
const chalk = require('chalk');
|
||||
const fetch = require('node-fetch');
|
||||
const BaseClient = require('./BaseClient');
|
||||
const ActionsManager = require('./actions/ActionsManager');
|
||||
const ClientVoiceManager = require('./voice/ClientVoiceManager');
|
||||
const WebSocketManager = require('./websocket/WebSocketManager');
|
||||
const { Error, TypeError, RangeError } = require('../errors');
|
||||
const Discord = require('../index');
|
||||
const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager');
|
||||
const BillingManager = require('../managers/BillingManager');
|
||||
const ChannelManager = require('../managers/ChannelManager');
|
||||
const ClientUserSettingManager = require('../managers/ClientUserSettingManager');
|
||||
const DeveloperPortalManager = require('../managers/DeveloperPortalManager');
|
||||
const GuildManager = require('../managers/GuildManager');
|
||||
const PresenceManager = require('../managers/PresenceManager');
|
||||
const RelationshipManager = require('../managers/RelationshipManager');
|
||||
const SessionManager = require('../managers/SessionManager');
|
||||
const UserManager = require('../managers/UserManager');
|
||||
const UserNoteManager = require('../managers/UserNoteManager');
|
||||
const VoiceStateManager = require('../managers/VoiceStateManager');
|
||||
const ShardClientUtil = require('../sharding/ShardClientUtil');
|
||||
const ClientPresence = require('../structures/ClientPresence');
|
||||
const GuildPreview = require('../structures/GuildPreview');
|
||||
const GuildTemplate = require('../structures/GuildTemplate');
|
||||
const Invite = require('../structures/Invite');
|
||||
const { CustomStatus } = require('../structures/RichPresence');
|
||||
const { Sticker } = require('../structures/Sticker');
|
||||
const StickerPack = require('../structures/StickerPack');
|
||||
const VoiceRegion = require('../structures/VoiceRegion');
|
||||
const Webhook = require('../structures/Webhook');
|
||||
const Widget = require('../structures/Widget');
|
||||
const { Events, InviteScopes, Status, captchaServices } = require('../util/Constants');
|
||||
const { Events, Status } = require('../util/Constants');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
const Intents = require('../util/Intents');
|
||||
const Options = require('../util/Options');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const DiscordAuthWebsocket = require('../util/RemoteAuth');
|
||||
const Sweepers = require('../util/Sweepers');
|
||||
const { getProxyObject } = require('../util/Util');
|
||||
|
||||
/**
|
||||
* The main hub for interacting with the Discord API, and the starting point for any bot.
|
||||
@ -50,7 +44,7 @@ class Client extends BaseClient {
|
||||
/**
|
||||
* @param {ClientOptions} options Options for the client
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
const data = require('node:worker_threads').workerData ?? process.env;
|
||||
@ -141,17 +135,6 @@ class Client extends BaseClient {
|
||||
*/
|
||||
this.users = new UserManager(this);
|
||||
|
||||
// Patch
|
||||
/**
|
||||
* All of the relationships {@link User}
|
||||
* @type {RelationshipManager}
|
||||
*/
|
||||
this.relationships = new RelationshipManager(this);
|
||||
/**
|
||||
* All of the settings {@link Object}
|
||||
* @type {ClientUserSettingManager}
|
||||
*/
|
||||
this.settings = new ClientUserSettingManager(this);
|
||||
/**
|
||||
* All of the guilds the client is currently handling, mapped by their ids -
|
||||
* as long as sharding isn't being used, this will be *every* guild the bot is a member of
|
||||
@ -159,18 +142,6 @@ class Client extends BaseClient {
|
||||
*/
|
||||
this.guilds = new GuildManager(this);
|
||||
|
||||
/**
|
||||
* Manages the API methods
|
||||
* @type {BillingManager}
|
||||
*/
|
||||
this.billing = new BillingManager(this);
|
||||
|
||||
/**
|
||||
* All of the sessions of the client
|
||||
* @type {SessionManager}
|
||||
*/
|
||||
this.sessions = new SessionManager(this);
|
||||
|
||||
/**
|
||||
* All of the {@link Channel}s that the client is currently handling, mapped by their ids -
|
||||
* as long as sharding isn't being used, this will be *every* channel in *every* guild the bot
|
||||
@ -186,12 +157,6 @@ class Client extends BaseClient {
|
||||
*/
|
||||
this.sweepers = new Sweepers(this, this.options.sweepers);
|
||||
|
||||
/**
|
||||
* The developer portal manager of the client
|
||||
* @type {DeveloperPortalManager}
|
||||
*/
|
||||
this.developerPortal = new DeveloperPortalManager(this);
|
||||
|
||||
/**
|
||||
* The presence of the Client
|
||||
* @private
|
||||
@ -199,6 +164,30 @@ class Client extends BaseClient {
|
||||
*/
|
||||
this.presence = new ClientPresence(this, this.options.presence);
|
||||
|
||||
/**
|
||||
* A manager of the presences belonging to this client
|
||||
* @type {PresenceManager}
|
||||
*/
|
||||
this.presences = new PresenceManager(this);
|
||||
|
||||
/**
|
||||
* All of the note that have been cached at any point, mapped by their ids
|
||||
* @type {UserManager}
|
||||
*/
|
||||
this.notes = new UserNoteManager(this);
|
||||
|
||||
/**
|
||||
* All of the relationships {@link User}
|
||||
* @type {RelationshipManager}
|
||||
*/
|
||||
this.relationships = new RelationshipManager(this);
|
||||
|
||||
/**
|
||||
* Manages the API methods
|
||||
* @type {BillingManager}
|
||||
*/
|
||||
this.billing = new BillingManager(this);
|
||||
|
||||
Object.defineProperty(this, 'token', { writable: true });
|
||||
if (!this.token && 'DISCORD_TOKEN' in process.env) {
|
||||
/**
|
||||
@ -212,20 +201,12 @@ class Client extends BaseClient {
|
||||
this.token = null;
|
||||
}
|
||||
|
||||
this._interactionCache = new Collection();
|
||||
|
||||
/**
|
||||
* User that the client is logged in as
|
||||
* @type {?ClientUser}
|
||||
*/
|
||||
this.user = null;
|
||||
|
||||
/**
|
||||
* The application of this bot
|
||||
* @type {?ClientApplication}
|
||||
*/
|
||||
this.application = null;
|
||||
|
||||
/**
|
||||
* Time at which the client was last regarded as being in the `READY` state
|
||||
* (each time the client disconnects and successfully reconnects, this will be overwritten)
|
||||
@ -233,12 +214,6 @@ class Client extends BaseClient {
|
||||
*/
|
||||
this.readyAt = null;
|
||||
|
||||
/**
|
||||
* Password cache
|
||||
* @type {?string}
|
||||
*/
|
||||
this.password = this.options.password;
|
||||
|
||||
if (this.options.messageSweepInterval > 0) {
|
||||
process.emitWarning(
|
||||
'The message sweeping client options are deprecated, use the global sweepers instead.',
|
||||
@ -251,15 +226,6 @@ class Client extends BaseClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Session ID
|
||||
* @type {?string}
|
||||
* @readonly
|
||||
*/
|
||||
get sessionId() {
|
||||
return this.ws.shards.first()?.sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* All custom emojis that the client has access to, mapped by their ids
|
||||
* @type {BaseGuildEmojiManager}
|
||||
@ -291,19 +257,6 @@ class Client extends BaseClient {
|
||||
return this.readyAt ? Date.now() - this.readyAt : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @external VoiceConnection
|
||||
* @see {@link https://discord.js.org/#/docs/voice/main/class/VoiceConnection}
|
||||
*/
|
||||
/**
|
||||
* Get connection to current call
|
||||
* @type {?VoiceConnection}
|
||||
* @readonly
|
||||
*/
|
||||
get callVoice() {
|
||||
return getVoiceConnection(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the client in, establishing a WebSocket connection to Discord.
|
||||
* @param {string} [token=this.token] Token of the account to log in with
|
||||
@ -320,8 +273,7 @@ class Client extends BaseClient {
|
||||
Logging on with a user token is unfortunately against the Discord
|
||||
\`Terms of Service\` <https://support.discord.com/hc/en-us/articles/115002192352>
|
||||
and doing so might potentially get your account banned.
|
||||
Use this at your own risk.
|
||||
`,
|
||||
Use this at your own risk.`,
|
||||
);
|
||||
this.emit(
|
||||
Events.DEBUG,
|
||||
@ -346,178 +298,10 @@ class Client extends BaseClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login Discord with Username and Password
|
||||
* @param {string} username Email or Phone Number
|
||||
* @param {?string} password Password
|
||||
* @param {?string} mfaCode 2FA Code / Backup Code
|
||||
* @returns {Promise<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;
|
||||
QRLogin() {
|
||||
const ws = new DiscordAuthWebsocket();
|
||||
ws.once('ready', () => ws.generateQR());
|
||||
return ws.connect(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -544,7 +328,6 @@ class Client extends BaseClient {
|
||||
this.sweepers.destroy();
|
||||
this.ws.destroy();
|
||||
this.token = null;
|
||||
this.password = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -558,7 +341,7 @@ class Client extends BaseClient {
|
||||
voip_provider: null,
|
||||
},
|
||||
});
|
||||
await this.destroy();
|
||||
return this.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -586,51 +369,6 @@ class Client extends BaseClient {
|
||||
return new Invite(this, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join this Guild using this invite (fast)
|
||||
* @param {InviteResolvable} invite Invite code or URL
|
||||
* @returns {Promise<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.
|
||||
* @param {GuildTemplateResolvable} template Template code or URL
|
||||
@ -721,16 +459,6 @@ class Client extends BaseClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a cache
|
||||
* @param {Collection} cache The cache to clear
|
||||
* @returns {number} The number of removed entries
|
||||
* @private
|
||||
*/
|
||||
_clearCache(cache) {
|
||||
return cache.sweep(() => true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sweeps all text-based channels' messages and removes the ones older than the max message lifetime.
|
||||
* If the message has been edited, the time of the edit is used rather than the time of the original message.
|
||||
@ -791,65 +519,13 @@ class Client extends BaseClient {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generates a link that can be used to invite the bot to a guild.
|
||||
* @param {InviteGenerationOptions} [options={}] Options for the invite
|
||||
* @returns {string}
|
||||
* @example
|
||||
* const link = client.generateInvite({
|
||||
* scopes: ['applications.commands'],
|
||||
* });
|
||||
* console.log(`Generated application invite link: ${link}`);
|
||||
* @example
|
||||
* const link = client.generateInvite({
|
||||
* permissions: [
|
||||
* Permissions.FLAGS.SEND_MESSAGES,
|
||||
* Permissions.FLAGS.MANAGE_GUILD,
|
||||
* Permissions.FLAGS.MENTION_EVERYONE,
|
||||
* ],
|
||||
* scopes: ['bot'],
|
||||
* });
|
||||
* console.log(`Generated bot invite link: ${link}`);
|
||||
* The sleep function in JavaScript returns a promise that resolves after a specified timeout.
|
||||
* @param {number} timeout - The timeout parameter is the amount of time, in milliseconds, that the sleep
|
||||
* function will wait before resolving the promise and continuing execution.
|
||||
* @returns {void} The `sleep` function is returning a Promise.
|
||||
*/
|
||||
generateInvite(options = {}) {
|
||||
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true);
|
||||
if (!this.application) throw new Error('CLIENT_NOT_READY', 'generate an invite link');
|
||||
|
||||
const query = new URLSearchParams({
|
||||
client_id: this.application.id,
|
||||
});
|
||||
|
||||
const { scopes } = options;
|
||||
if (typeof scopes === 'undefined') {
|
||||
throw new TypeError('INVITE_MISSING_SCOPES');
|
||||
}
|
||||
if (!Array.isArray(scopes)) {
|
||||
throw new TypeError('INVALID_TYPE', 'scopes', 'Array of Invite Scopes', true);
|
||||
}
|
||||
if (!scopes.some(scope => ['bot', 'applications.commands'].includes(scope))) {
|
||||
throw new TypeError('INVITE_MISSING_SCOPES');
|
||||
}
|
||||
const invalidScope = scopes.find(scope => !InviteScopes.includes(scope));
|
||||
if (invalidScope) {
|
||||
throw new TypeError('INVALID_ELEMENT', 'Array', 'scopes', invalidScope);
|
||||
}
|
||||
query.set('scope', scopes.join(' '));
|
||||
|
||||
if (options.permissions) {
|
||||
const permissions = Permissions.resolve(options.permissions);
|
||||
if (permissions) query.set('permissions', permissions);
|
||||
}
|
||||
|
||||
if (options.disableGuildSelect) {
|
||||
query.set('disable_guild_select', true);
|
||||
}
|
||||
|
||||
if (options.guild) {
|
||||
const guildId = this.guilds.resolveId(options.guild);
|
||||
if (!guildId) throw new TypeError('INVALID_TYPE', 'options.guild', 'GuildResolvable');
|
||||
query.set('guild_id', guildId);
|
||||
}
|
||||
|
||||
return `${this.options.http.api}${this.api.oauth2.authorize}?${query}`;
|
||||
sleep(timeout) {
|
||||
return new Promise(r => setTimeout(r, timeout));
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
@ -859,41 +535,46 @@ class Client extends BaseClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script
|
||||
* with the client as `this`.
|
||||
* @param {string} script Script to eval
|
||||
* @returns {*}
|
||||
* @private
|
||||
* Join this Guild using this invite (fast)
|
||||
* @param {InviteResolvable} invite Invite code or URL
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* await client.acceptInvite('https://discord.gg/genshinimpact')
|
||||
*/
|
||||
_eval(script) {
|
||||
return eval(script);
|
||||
async acceptInvite(invite) {
|
||||
const code = DataResolver.resolveInviteCode(invite);
|
||||
if (!code) throw new Error('INVITE_RESOLVE_CODE');
|
||||
if (invite instanceof Invite) {
|
||||
await invite.acceptInvite();
|
||||
} else {
|
||||
await this.api.invites(code).post({
|
||||
DiscordContext: { location: 'Markdown Link' },
|
||||
data: {
|
||||
session_id: this.ws.shards.first()?.sessionId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client's presence. (Sync Setting).
|
||||
* @param {Client} client Discord Client
|
||||
* @private
|
||||
* Redeem nitro from code or url.
|
||||
* @param {string} nitro Nitro url or code
|
||||
* @param {TextChannelResolvable} [channel] Channel that the code was sent in
|
||||
* @param {Snowflake} [paymentSourceId] Payment source id
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
customStatusAuto(client) {
|
||||
client = client ?? this;
|
||||
if (!client.user) return;
|
||||
const custom_status = new CustomStatus();
|
||||
if (!client.settings.rawSetting.custom_status?.text && !client.settings.rawSetting.custom_status?.emoji_name) {
|
||||
client.user.setPresence({
|
||||
activities: this.presence.activities.filter(a => a.type !== 'CUSTOM'),
|
||||
status: client.settings.rawSetting.status ?? 'invisible',
|
||||
});
|
||||
} else {
|
||||
custom_status.setEmoji({
|
||||
name: client.settings.rawSetting.custom_status?.emoji_name,
|
||||
id: client.settings.rawSetting.custom_status?.emoji_id,
|
||||
});
|
||||
custom_status.setState(client.settings.rawSetting.custom_status?.text);
|
||||
client.user.setPresence({
|
||||
activities: [custom_status.toJSON(), ...this.presence.activities.filter(a => a.type !== 'CUSTOM')],
|
||||
status: client.settings.rawSetting.status ?? 'invisible',
|
||||
});
|
||||
}
|
||||
redeemNitro(nitro, channel, paymentSourceId) {
|
||||
if (typeof nitro !== 'string') throw new Error('INVALID_NITRO');
|
||||
const nitroCode =
|
||||
nitro.match(/(discord.gift|discord.com|discordapp.com\/gifts)\/(\w{16,25})/) ||
|
||||
nitro.match(/(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)(\w+)/);
|
||||
if (!nitroCode) return false;
|
||||
const code = nitroCode[2];
|
||||
channel = this.channels.resolveId(channel);
|
||||
return this.api.entitlements['gift-codes'](code).redeem.post({
|
||||
auth: true,
|
||||
data: { channel_id: channel || null, payment_source_id: paymentSourceId || null },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -909,7 +590,7 @@ class Client extends BaseClient {
|
||||
* Authorize an application.
|
||||
* @param {string} url Discord Auth URL
|
||||
* @param {OAuth2AuthorizeOptions} options Oauth2 options
|
||||
* @returns {Promise<Object>}
|
||||
* @returns {Promise<any>}
|
||||
* @example
|
||||
* client.authorizeURL(`https://discord.com/api/oauth2/authorize?client_id=botID&permissions=8&scope=applications.commands%20bot`, {
|
||||
guild_id: "guildID",
|
||||
@ -937,12 +618,14 @@ class Client extends BaseClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes waiting time for Client.
|
||||
* @param {number} miliseconds Sleeping time as milliseconds.
|
||||
* @returns {Promise<void> | null}
|
||||
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script
|
||||
* with the client as `this`.
|
||||
* @param {string} script Script to eval
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
sleep(miliseconds) {
|
||||
return typeof miliseconds === 'number' ? new Promise(r => setTimeout(r, miliseconds).unref()) : null;
|
||||
_eval(script) {
|
||||
return eval(script);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -956,73 +639,8 @@ class Client extends BaseClient {
|
||||
} else {
|
||||
options.intents = Intents.resolve(options.intents);
|
||||
}
|
||||
if (options && typeof options.checkUpdate !== 'boolean') {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'checkUpdate', 'a boolean');
|
||||
}
|
||||
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 (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount !== 1) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number equal to 1');
|
||||
}
|
||||
if (options.shards && !(options.shards === 'auto' || Array.isArray(options.shards))) {
|
||||
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)) {
|
||||
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)) {
|
||||
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)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number');
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
class ActionsManager {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
|
||||
// These symbols represent fully built data that we inject at times when calling actions manually.
|
||||
// Action#getUser for example, will return the injected data (which is assumed to be a built structure)
|
||||
// instead of trying to make it from provided data
|
||||
@ -44,7 +45,6 @@ class ActionsManager {
|
||||
this.register(require('./GuildStickerUpdate'));
|
||||
this.register(require('./GuildStickersUpdate'));
|
||||
this.register(require('./GuildUpdate'));
|
||||
this.register(require('./InteractionCreate'));
|
||||
this.register(require('./InviteCreate'));
|
||||
this.register(require('./InviteDelete'));
|
||||
this.register(require('./MessageCreate'));
|
||||
|
@ -1,115 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const process = require('node:process');
|
||||
const Action = require('./Action');
|
||||
const AutocompleteInteraction = require('../../structures/AutocompleteInteraction');
|
||||
const ButtonInteraction = require('../../structures/ButtonInteraction');
|
||||
const CommandInteraction = require('../../structures/CommandInteraction');
|
||||
const MessageContextMenuInteraction = require('../../structures/MessageContextMenuInteraction');
|
||||
const ModalSubmitInteraction = require('../../structures/ModalSubmitInteraction');
|
||||
const {
|
||||
ChannelSelectInteraction,
|
||||
MentionableSelectInteraction,
|
||||
RoleSelectInteraction,
|
||||
SelectMenuInteraction,
|
||||
UserSelectInteraction,
|
||||
} = require('../../structures/SelectMenuInteraction');
|
||||
const UserContextMenuInteraction = require('../../structures/UserContextMenuInteraction');
|
||||
const { Events, InteractionTypes, MessageComponentTypes, ApplicationCommandTypes } = require('../../util/Constants');
|
||||
|
||||
let deprecationEmitted = false;
|
||||
|
||||
class InteractionCreateAction extends Action {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
|
||||
// Resolve and cache partial channels for Interaction#channel getter
|
||||
const channel = this.getChannel(data);
|
||||
// Do not emit this for interactions that cache messages that are non-text-based.
|
||||
let InteractionType;
|
||||
switch (data.type) {
|
||||
case InteractionTypes.APPLICATION_COMMAND:
|
||||
switch (data.data.type) {
|
||||
case ApplicationCommandTypes.CHAT_INPUT:
|
||||
InteractionType = CommandInteraction;
|
||||
break;
|
||||
case ApplicationCommandTypes.USER:
|
||||
InteractionType = UserContextMenuInteraction;
|
||||
break;
|
||||
case ApplicationCommandTypes.MESSAGE:
|
||||
InteractionType = MessageContextMenuInteraction;
|
||||
break;
|
||||
default:
|
||||
client.emit(
|
||||
Events.DEBUG,
|
||||
`[INTERACTION] Received application command interaction with unknown type: ${data.data.type}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case InteractionTypes.MESSAGE_COMPONENT:
|
||||
if (channel && !channel.isText()) return;
|
||||
switch (data.data.component_type) {
|
||||
case MessageComponentTypes.BUTTON:
|
||||
InteractionType = ButtonInteraction;
|
||||
break;
|
||||
case MessageComponentTypes.STRING_SELECT:
|
||||
InteractionType = SelectMenuInteraction;
|
||||
break;
|
||||
case MessageComponentTypes.CHANNEL_SELECT:
|
||||
InteractionType = ChannelSelectInteraction;
|
||||
break;
|
||||
case MessageComponentTypes.MENTIONABLE_SELECT:
|
||||
InteractionType = MentionableSelectInteraction;
|
||||
break;
|
||||
case MessageComponentTypes.ROLE_SELECT:
|
||||
InteractionType = RoleSelectInteraction;
|
||||
break;
|
||||
case MessageComponentTypes.USER_SELECT:
|
||||
InteractionType = UserSelectInteraction;
|
||||
break;
|
||||
default:
|
||||
client.emit(
|
||||
Events.DEBUG,
|
||||
`[INTERACTION] Received component interaction with unknown type: ${data.data.component_type}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE:
|
||||
InteractionType = AutocompleteInteraction;
|
||||
break;
|
||||
case InteractionTypes.MODAL_SUBMIT:
|
||||
InteractionType = ModalSubmitInteraction;
|
||||
break;
|
||||
default:
|
||||
client.emit(
|
||||
Events.DEBUG,
|
||||
`[INTERACTION] Received [BOT] / Send (Selfbot) interactionID ${data.id} with unknown type: ${data.type}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const interaction = new InteractionType(client, data);
|
||||
|
||||
/**
|
||||
* Emitted when an interaction is created.
|
||||
* @event Client#interactionCreate
|
||||
* @param {InteractionResponseBody | Interaction} interaction The interaction which was created.
|
||||
*/
|
||||
client.emit(Events.INTERACTION_CREATE, interaction);
|
||||
|
||||
/**
|
||||
* Emitted when an interaction is created.
|
||||
* @event Client#interaction
|
||||
* @param {Interaction} interaction The interaction which was created
|
||||
* @deprecated Use {@link Client#event:interactionCreate} instead
|
||||
*/
|
||||
if (client.emit('interaction', interaction) && !deprecationEmitted) {
|
||||
deprecationEmitted = true;
|
||||
process.emitWarning('The interaction event is deprecated. Use interactionCreate instead', 'DeprecationWarning');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InteractionCreateAction;
|
@ -6,17 +6,6 @@ const { Events } = require('../../util/Constants');
|
||||
|
||||
let deprecationEmitted = false;
|
||||
|
||||
async function autoRedeemNitro(client, message) {
|
||||
if (!message.content) return;
|
||||
const allLinks =
|
||||
message.content.match(/(discord.gift|discord.com|discordapp.com\/gifts)\/(\w{16,25})/gm) ||
|
||||
message.content.match(/(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)(\w+)/gm);
|
||||
if (!allLinks) return;
|
||||
for (const link of allLinks) {
|
||||
await client.redeemNitro(link, message.channel);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageCreateAction extends Action {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
@ -29,10 +18,6 @@ class MessageCreateAction extends Action {
|
||||
const message = channel.messages._add(data);
|
||||
channel.lastMessageId = data.id;
|
||||
|
||||
if (client.options.autoRedeemNitro) {
|
||||
autoRedeemNitro(client, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a message is created.
|
||||
* @event Client#messageCreate
|
||||
|
@ -13,11 +13,9 @@ class PresenceUpdateAction extends Action {
|
||||
if (!user._equals(data.user)) this.client.actions.UserUpdate.handle(data.user);
|
||||
}
|
||||
|
||||
// Shet, user not received guild_id
|
||||
let emited = false;
|
||||
for (const guild of this.client.guilds.cache.map(g => g)) {
|
||||
if (!guild.members.cache.get(user.id)) continue;
|
||||
const oldPresence = guild.presences.cache.get(user.id)?._clone() ?? null;
|
||||
const guild = this.client.guilds.cache.get(data.guild_id);
|
||||
|
||||
if (guild) {
|
||||
let member = guild.members.cache.get(user.id);
|
||||
if (!member && data.status !== 'offline') {
|
||||
member = guild.members._add({
|
||||
@ -27,17 +25,20 @@ class PresenceUpdateAction extends Action {
|
||||
});
|
||||
this.client.emit(Events.GUILD_MEMBER_AVAILABLE, member);
|
||||
}
|
||||
const newPresence = guild.presences._add(Object.assign(data, { guild }));
|
||||
if (this.client.listenerCount(Events.PRESENCE_UPDATE) && !newPresence.equals(oldPresence) && !emited) {
|
||||
/**
|
||||
* Emitted whenever a guild member's presence (e.g. status, activity) is changed.
|
||||
* @event Client#presenceUpdate
|
||||
* @param {?Presence} oldPresence The presence before the update, if one at all
|
||||
* @param {Presence} newPresence The presence after the update
|
||||
*/
|
||||
this.client.emit(Events.PRESENCE_UPDATE, oldPresence, newPresence);
|
||||
emited = true;
|
||||
}
|
||||
}
|
||||
|
||||
const oldPresence = (guild || this.client).presences.cache.get(user.id)?._clone() ?? null;
|
||||
|
||||
const newPresence = (guild || this.client).presences._add(Object.assign(data, { guild }));
|
||||
|
||||
if (this.client.listenerCount(Events.PRESENCE_UPDATE) && !newPresence.equals(oldPresence)) {
|
||||
/**
|
||||
* Emitted whenever a guild member's presence (e.g. status, activity) is changed.
|
||||
* @event Client#presenceUpdate
|
||||
* @param {?Presence} oldPresence The presence before the update, if one at all
|
||||
* @param {Presence} newPresence The presence after the update
|
||||
*/
|
||||
this.client.emit(Events.PRESENCE_UPDATE, oldPresence, newPresence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,36 +126,15 @@ class WebSocketManager extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
async connect() {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const invalidToken = new Error(WSCodes[4004]);
|
||||
/*
|
||||
BOT
|
||||
const {
|
||||
url: gatewayURL,
|
||||
shards: recommendedShards,
|
||||
session_start_limit: sessionStartLimit,
|
||||
} = await this.client.api.gateway.bot.get().catch(error => {
|
||||
throw error.httpStatus === 401 ? invalidToken : error;
|
||||
});
|
||||
*/
|
||||
|
||||
let gatewayURL = 'wss://gateway.discord.gg';
|
||||
const { url } = await this.client.api.gateway.get({ auth: false }).catch(() => ({ url: gatewayURL }));
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
/*
|
||||
.catch(error => {
|
||||
// Never throw error :v
|
||||
// throw error.httpStatus === 401 ? invalidToken : error;
|
||||
});
|
||||
*/
|
||||
if (url) gatewayURL = url;
|
||||
const recommendedShards = 1;
|
||||
const sessionStartLimit = {
|
||||
total: Infinity,
|
||||
remaining: Infinity,
|
||||
};
|
||||
await this.client.api.gateway
|
||||
.get({ auth: false })
|
||||
.then(r => (gatewayURL = r.url))
|
||||
.catch(() => {});
|
||||
|
||||
const { total, remaining } = sessionStartLimit;
|
||||
const total = Infinity;
|
||||
const remaining = Infinity;
|
||||
const recommendedShards = 1;
|
||||
|
||||
this.debug(`Fetched Gateway Information
|
||||
URL: ${gatewayURL}
|
||||
@ -294,7 +273,7 @@ class WebSocketManager extends EventEmitter {
|
||||
} catch (error) {
|
||||
this.debug(`Couldn't reconnect or fetch information about the gateway. ${error}`);
|
||||
if (error.httpStatus !== 401) {
|
||||
this.debug('Possible network error occurred. Retrying in 5s...');
|
||||
this.debug(`Possible network error occurred. Retrying in 5s...`);
|
||||
await sleep(5_000);
|
||||
this.reconnecting = false;
|
||||
return this.reconnect();
|
||||
@ -368,11 +347,12 @@ class WebSocketManager extends EventEmitter {
|
||||
/**
|
||||
* Emitted whenever a packet isn't handled.
|
||||
* @event Client#unhandledPacket
|
||||
* @param {Object} packet The packet (t: Event name, d: Data)
|
||||
* @param {Number} shard The shard that received the packet (Auto = 0)
|
||||
* @param {Object} packet The packet (t: EVENT_NAME, d: any)
|
||||
* @param {Number} shard The shard that received the packet (Shard 0)
|
||||
*/
|
||||
this.client.emit(Events.UNHANDLED_PACKET, packet, shard);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('node:events');
|
||||
const http = require('node:http');
|
||||
const { setTimeout, setInterval, clearTimeout } = require('node:timers');
|
||||
const WebSocket = require('../../WebSocket');
|
||||
const { Status, Events, ShardEvents, Opcodes, WSEvents, WSCodes } = require('../../util/Constants');
|
||||
const Intents = require('../../util/Intents');
|
||||
const { getProxyObject } = require('../../util/Util');
|
||||
|
||||
const STATUS_KEYS = Object.keys(Status);
|
||||
const CONNECTION_STATE = Object.keys(WebSocket.WebSocket);
|
||||
@ -18,6 +18,7 @@ try {
|
||||
|
||||
/**
|
||||
* Represents a Shard's WebSocket connection
|
||||
* @extends {EventEmitter}
|
||||
*/
|
||||
class WebSocketShard extends EventEmitter {
|
||||
constructor(manager, id) {
|
||||
@ -35,6 +36,13 @@ class WebSocketShard extends EventEmitter {
|
||||
*/
|
||||
this.id = id;
|
||||
|
||||
/**
|
||||
* The resume URL for this shard
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.resumeURL = null;
|
||||
|
||||
/**
|
||||
* The current status of the shard
|
||||
* @type {Status}
|
||||
@ -62,13 +70,6 @@ class WebSocketShard extends EventEmitter {
|
||||
*/
|
||||
this.sessionId = null;
|
||||
|
||||
/**
|
||||
* URL to use when resuming
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.resumeURL = null;
|
||||
|
||||
/**
|
||||
* The previous heartbeat ping of the shard
|
||||
* @type {number}
|
||||
@ -200,12 +201,12 @@ class WebSocketShard extends EventEmitter {
|
||||
connect() {
|
||||
const { client } = this.manager;
|
||||
|
||||
const gateway = this.resumeURL ?? this.manager.gateway;
|
||||
|
||||
if (this.connection?.readyState === WebSocket.OPEN && this.status === Status.READY) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const gateway = this.resumeURL ?? this.manager.gateway;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const cleanup = () => {
|
||||
this.removeListener(ShardEvents.CLOSE, onClose);
|
||||
@ -271,7 +272,7 @@ class WebSocketShard extends EventEmitter {
|
||||
Version : ${client.options.ws.version}
|
||||
Encoding : ${WebSocket.encoding}
|
||||
Compression: ${zlib ? 'zlib-stream' : 'none'}
|
||||
Proxy : ${client.options.proxy || 'none'}`,
|
||||
Agent : ${client.options.ws.agent instanceof http.Agent}`,
|
||||
);
|
||||
|
||||
this.status = this.status === Status.DISCONNECTED ? Status.RECONNECTING : Status.CONNECTING;
|
||||
@ -279,13 +280,11 @@ class WebSocketShard extends EventEmitter {
|
||||
this.setWsCloseTimeout(-1);
|
||||
this.connectedAt = Date.now();
|
||||
|
||||
let args = { handshakeTimeout: 30_000 };
|
||||
if (client.options.proxy.length > 0) {
|
||||
args.agent = getProxyObject(client.options.proxy);
|
||||
this.debug(`Using proxy ${client.options.proxy}`, args);
|
||||
}
|
||||
// Adding a handshake timeout to just make sure no zombie connection appears.
|
||||
const ws = (this.connection = WebSocket.create(gateway, wsQuery, args));
|
||||
const ws = (this.connection = WebSocket.create(gateway, wsQuery, {
|
||||
handshakeTimeout: 30_000,
|
||||
agent: client.options.ws.agent instanceof http.Agent ? client.options.ws.agent : undefined,
|
||||
}));
|
||||
ws.onopen = this.onOpen.bind(this);
|
||||
ws.onmessage = this.onMessage.bind(this);
|
||||
ws.onerror = this.onError.bind(this);
|
||||
@ -433,11 +432,11 @@ class WebSocketShard extends EventEmitter {
|
||||
*/
|
||||
this.emit(ShardEvents.READY);
|
||||
|
||||
this.sessionId = packet.d.session_id;
|
||||
this.resumeURL = packet.d.resume_gateway_url;
|
||||
this.sessionId = packet.d.session_id;
|
||||
this.expectedGuilds = new Set(packet.d.guilds.filter(d => d?.unavailable == true).map(d => d.id));
|
||||
this.status = Status.WAITING_FOR_GUILDS;
|
||||
this.debug(`[READY] Session ${this.sessionId} | ResumeURL ${this.resumeURL}`);
|
||||
this.debug(`[READY] Session ${this.sessionId} | Resume url ${this.resumeURL}.`);
|
||||
this.lastHeartbeatAcked = true;
|
||||
this.sendHeartbeat('ReadyHeartbeat');
|
||||
break;
|
||||
@ -538,21 +537,23 @@ class WebSocketShard extends EventEmitter {
|
||||
|
||||
const { waitGuildTimeout } = this.manager.client.options;
|
||||
|
||||
this.readyTimeout = setTimeout(() => {
|
||||
this.debug(
|
||||
`Shard ${hasGuildsIntent ? 'did' : 'will'} not receive any more guild packets` +
|
||||
`${hasGuildsIntent ? ` in ${waitGuildTimeout} ms` : ''}.\nUnavailable guild count: ${
|
||||
this.expectedGuilds.size
|
||||
}`,
|
||||
);
|
||||
this.readyTimeout = setTimeout(
|
||||
() => {
|
||||
this.debug(
|
||||
`Shard ${hasGuildsIntent ? 'did' : 'will'} not receive any more guild packets` +
|
||||
`${hasGuildsIntent ? ` in ${waitGuildTimeout} ms` : ''}.\nUnavailable guild count: ${
|
||||
this.expectedGuilds.size
|
||||
}`,
|
||||
);
|
||||
|
||||
this.readyTimeout = null;
|
||||
this.readyTimeout = null;
|
||||
|
||||
this.status = Status.READY;
|
||||
this.status = Status.READY;
|
||||
|
||||
this.emit(ShardEvents.ALL_READY, this.expectedGuilds);
|
||||
// }, hasGuildsIntent && waitGuildTimeout).unref();
|
||||
}, waitGuildTimeout).unref();
|
||||
this.emit(ShardEvents.ALL_READY, this.expectedGuilds);
|
||||
},
|
||||
hasGuildsIntent ? waitGuildTimeout : 0,
|
||||
).unref();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -593,6 +594,7 @@ class WebSocketShard extends EventEmitter {
|
||||
}
|
||||
this.wsCloseTimeout = setTimeout(() => {
|
||||
this.setWsCloseTimeout(-1);
|
||||
|
||||
// Check if close event was emitted.
|
||||
if (this.closeEmitted) {
|
||||
this.debug(`[WebSocket] close was already emitted, assuming the connection was closed properly.`);
|
||||
@ -656,7 +658,6 @@ class WebSocketShard extends EventEmitter {
|
||||
Sequence : ${this.sequence}
|
||||
Connection State: ${this.connection ? CONNECTION_STATE[this.connection.readyState] : 'No Connection??'}`,
|
||||
);
|
||||
|
||||
this.destroy({ reset: true, closeCode: 4009 });
|
||||
return;
|
||||
}
|
||||
@ -700,25 +701,24 @@ class WebSocketShard extends EventEmitter {
|
||||
|
||||
this.status = Status.IDENTIFYING;
|
||||
|
||||
// Patch something
|
||||
Object.keys(client.options.ws.properties)
|
||||
.filter(k => k.startsWith('$'))
|
||||
.forEach(k => {
|
||||
client.options.ws.properties[k.slice(1)] = client.options.ws.properties[k];
|
||||
delete client.options.ws.properties[k];
|
||||
});
|
||||
|
||||
// Clone the identify payload and assign the token and shard info
|
||||
const d = {
|
||||
presence: client.options.presence,
|
||||
...client.options.ws,
|
||||
token: client.token,
|
||||
};
|
||||
|
||||
delete d.version;
|
||||
delete d.agent;
|
||||
|
||||
this.debug(
|
||||
`[IDENTIFY] Shard ${this.id}/${client.options.shardCount} with intents: ${Intents.resolve(
|
||||
client.options.intents,
|
||||
)} 😊`,
|
||||
);
|
||||
this.debug(`[IDENTIFY] Shard ${this.id}/${client.options.shardCount} with intents: ${d.intents}`);
|
||||
this.send({ op: Opcodes.IDENTIFY, d }, true);
|
||||
}
|
||||
|
||||
@ -815,6 +815,7 @@ class WebSocketShard extends EventEmitter {
|
||||
// Step 0: Remove all timers
|
||||
this.setHeartbeatTimer(-1);
|
||||
this.setHelloTimeout(-1);
|
||||
|
||||
this.debug(
|
||||
`[WebSocket] Destroy: Attempting to close the WebSocket. | WS State: ${
|
||||
CONNECTION_STATE[this.connection?.readyState ?? WebSocket.CLOSED]
|
||||
|
@ -1,23 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
/**
|
||||
* @typedef {object} AutocompleteResponseChoice
|
||||
* @property {string} name The name of the choice
|
||||
* @property {string} value The value of the choice
|
||||
*/
|
||||
/**
|
||||
* @typedef {object} AutocompleteResponse
|
||||
* @property {Snowflake} [nonce] Snowflake of the data
|
||||
* @property {Array<AutocompleteResponseChoice>} [choices] Array of choices
|
||||
*/
|
||||
/**
|
||||
* Emitted when receiving a response from Discord
|
||||
* @event Client#applicationCommandAutocompleteResponse
|
||||
* @param {AutocompleteResponse} data Data
|
||||
* @deprecated Test only
|
||||
*/
|
||||
client.emit(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, data);
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
const Call = require('../../../structures/Call');
|
||||
const CallState = require('../../../structures/CallState');
|
||||
const { Events } = require('../../../util/Constants');
|
||||
module.exports = (client, packet) => {
|
||||
for (const voice of packet.d.voice_states) {
|
||||
@ -8,7 +8,7 @@ module.exports = (client, packet) => {
|
||||
/**
|
||||
* Emitted whenever received a call
|
||||
* @event Client#callCreate
|
||||
* @param {Call} call Call
|
||||
* @param {CallState} call Call
|
||||
*/
|
||||
client.emit(Events.CALL_CREATE, new Call(client, packet.d));
|
||||
client.emit(Events.CALL_CREATE, new CallState(client, packet.d));
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
const Call = require('../../../structures/Call');
|
||||
const CallState = require('../../../structures/CallState');
|
||||
const { Events } = require('../../../util/Constants');
|
||||
module.exports = (client, packet) => {
|
||||
/**
|
||||
@ -7,5 +7,5 @@ module.exports = (client, packet) => {
|
||||
* @event Client#callDelete
|
||||
* @param {Call} call Call
|
||||
*/
|
||||
client.emit(Events.CALL_DELETE, new Call(client, packet.d));
|
||||
client.emit(Events.CALL_DELETE, new CallState(client, packet.d));
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
const Call = require('../../../structures/Call');
|
||||
const CallState = require('../../../structures/CallState');
|
||||
const { Events } = require('../../../util/Constants');
|
||||
module.exports = (client, packet) => {
|
||||
/**
|
||||
@ -7,5 +7,5 @@ module.exports = (client, packet) => {
|
||||
* @event Client#callUpdate
|
||||
* @param {Call} call Call
|
||||
*/
|
||||
client.emit(Events.CALL_UPDATE, new Call(client, packet.d));
|
||||
client.emit(Events.CALL_UPDATE, new CallState(client, packet.d));
|
||||
};
|
||||
|
@ -1,16 +1,19 @@
|
||||
'use strict';
|
||||
const { Events } = require('../../../util/Constants');
|
||||
module.exports = (client, packet) => {
|
||||
/**
|
||||
* Emitted whenever a recipient is added from a group DM.
|
||||
* @event Client#channelRecipientAdd
|
||||
* @param {PartialGroupDMChannel} channel Group DM channel
|
||||
* @param {User} user User
|
||||
*/
|
||||
const { Events, Status } = require('../../../util/Constants');
|
||||
module.exports = (client, packet, shard) => {
|
||||
const channel = client.channels.cache.get(packet.d.channel_id);
|
||||
if (!channel) return;
|
||||
if (!channel._recipients) channel._recipients = [];
|
||||
channel._recipients.push(packet.d.user);
|
||||
const user = client.users._add(packet.d.user);
|
||||
client.emit(Events.CHANNEL_RECIPIENT_ADD, channel, user);
|
||||
if (channel) {
|
||||
if (!channel._recipients) channel._recipients = [];
|
||||
channel._recipients.push(packet.d.user);
|
||||
const user = client.users._add(packet.d.user);
|
||||
if (shard.status == Status.READY) {
|
||||
/**
|
||||
* Emitted whenever a recipient is added from a group DM.
|
||||
* @event Client#channelRecipientAdd
|
||||
* @param {GroupDMChannel} channel Group DM channel
|
||||
* @param {User} user User
|
||||
*/
|
||||
client.emit(Events.CHANNEL_RECIPIENT_ADD, channel, user);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,16 +1,16 @@
|
||||
'use strict';
|
||||
const { Events } = require('../../../util/Constants');
|
||||
module.exports = (client, packet) => {
|
||||
/**
|
||||
* Emitted whenever a recipient is removed from a group DM.
|
||||
* @event Client#channelRecipientRemove
|
||||
* @param {PartialGroupDMChannel} channel Group DM channel
|
||||
* @param {User} user User
|
||||
*/
|
||||
const channel = client.channels.cache.get(packet.d.channel_id);
|
||||
if (!channel) return;
|
||||
if (!channel._recipients) channel._recipients = [];
|
||||
channel._recipients = channel._recipients.filter(r => r !== packet.d.user.id);
|
||||
const user = client.users._add(packet.d.user);
|
||||
client.emit(Events.CHANNEL_RECIPIENT_REMOVE, channel, user);
|
||||
if (channel) {
|
||||
if (!channel._recipients) channel._recipients = [];
|
||||
channel._recipients = channel._recipients.filter(u => u.id !== packet.d.user.id);
|
||||
/**
|
||||
* Emitted whenever a recipient is removed from a group DM.
|
||||
* @event Client#channelRecipientRemove
|
||||
* @param {GroupDMChannel} channel Group DM channel
|
||||
* @param {User} user User
|
||||
*/
|
||||
client.emit(Events.CHANNEL_RECIPIENT_REMOVE, channel, client.users._add(packet.d.user));
|
||||
}
|
||||
};
|
||||
|
@ -1,11 +0,0 @@
|
||||
'use strict';
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
for (const command of data.application_commands) {
|
||||
const user = client.users.cache.get(command.application_id);
|
||||
if (!user || !user.bot) continue;
|
||||
user.application?.commands?._add(command, true);
|
||||
}
|
||||
client.emit(Events.GUILD_APPLICATION_COMMANDS_UPDATE, data);
|
||||
};
|
@ -1,55 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const guild = client.guilds.cache.get(data.guild_id);
|
||||
if (!guild) return;
|
||||
const members = new Collection();
|
||||
// Get Member from side Discord Channel (online counting if large server)
|
||||
for (const object of data.ops) {
|
||||
switch (object.op) {
|
||||
case 'SYNC': {
|
||||
for (const member_ of object.items) {
|
||||
const member = member_.member;
|
||||
if (!member) continue;
|
||||
members.set(member.user.id, guild.members._add(member));
|
||||
if (member.presence) {
|
||||
guild.presences._add(Object.assign(member.presence, { guild }));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'INVALIDATE': {
|
||||
client.emit(
|
||||
Events.DEBUG,
|
||||
`Invalidate [${object.range[0]}, ${object.range[1]}], Fetching GuildId: ${data.guild_id}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'UPDATE':
|
||||
case 'INSERT': {
|
||||
const member = object.item.member;
|
||||
if (!member) continue;
|
||||
members.set(member.user.id, guild.members._add(member));
|
||||
if (member.presence) {
|
||||
guild.presences._add(Object.assign(member.presence, { guild }));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'DELETE': {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Emitted whenever a guild member list (sidebar) is updated.
|
||||
* @event Client#guildMemberListUpdate
|
||||
* @param {Collection<Snowflake, GuildMember>} members Members that were updated
|
||||
* @param {Guild} guild Guild
|
||||
* @param {string} type Type of update (INVALIDATE | UPDATE | INSERT | DELETE | SYNC)
|
||||
* @param {data} raw Raw data
|
||||
*/
|
||||
client.emit(Events.GUILD_MEMBER_LIST_UPDATE, members, guild, data.ops[0].op, data);
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
'use strict';
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
/**
|
||||
* @typedef {Object} InteractionResponseBody
|
||||
* @property {Snowflake} id Interaction ID
|
||||
* @property {Snowflake} nonce nonce in POST /interactions
|
||||
*/
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
if (client.user.bot) {
|
||||
client.actions.InteractionCreate.handle(data);
|
||||
} else {
|
||||
client.emit(Events.INTERACTION_CREATE, data);
|
||||
}
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
'use strict';
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
/**
|
||||
* Emitted whenever client user send interaction and error
|
||||
* @event Client#interactionFailure
|
||||
* @param {InteractionResponseBody} data data
|
||||
*/
|
||||
client.emit(Events.INTERACTION_FAILURE, data);
|
||||
client.emit('interactionResponse', {
|
||||
status: false,
|
||||
metadata: client._interactionCache.get(data.nonce),
|
||||
error: 'No response from bot',
|
||||
});
|
||||
// Delete cache
|
||||
client._interactionCache.delete(data.nonce);
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
const Modal = require('../../../structures/Modal');
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
/**
|
||||
* Emitted whenever client user receive interaction.showModal()
|
||||
|
@ -1,30 +0,0 @@
|
||||
'use strict';
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
/**
|
||||
* Emitted whenever client user send interaction and success
|
||||
* @event Client#interactionSuccess
|
||||
* @param {InteractionResponseBody} data data
|
||||
*/
|
||||
client.emit(Events.INTERACTION_SUCCESS, data);
|
||||
// Get channel data
|
||||
const cache = client._interactionCache.get(data.nonce);
|
||||
if (!cache) return;
|
||||
const channel = cache.guildId
|
||||
? client.guilds.cache.get(cache.guildId)?.channels.cache.get(cache.channelId)
|
||||
: client.channels.cache.get(cache.channelId);
|
||||
// Set data
|
||||
const interaction = {
|
||||
...cache,
|
||||
...data,
|
||||
};
|
||||
const data_ = channel.interactions._add(interaction);
|
||||
client.emit('interactionResponse', {
|
||||
status: true,
|
||||
metadata: data_,
|
||||
error: 'Success',
|
||||
});
|
||||
// Delete cache
|
||||
// client._interactionCache.delete(data.nonce);
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const channel = client.channels.cache.get(data.channel_id);
|
||||
/**
|
||||
* Emitted whenever message is acknowledged (mark read / unread)
|
||||
* @event Client#messageAck
|
||||
* @param {TextChannel} channel Channel
|
||||
* @param {Snowflake} message_id Message ID
|
||||
* @param {boolean} isRead Whether the message is read
|
||||
* @param {Object} raw Raw data
|
||||
*/
|
||||
client.emit(Events.MESSAGE_ACK, channel, data.message_id, !data.manual, data);
|
||||
};
|
@ -1,105 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const USER_REQUIRED_ACTION = require('./USER_REQUIRED_ACTION_UPDATE');
|
||||
const { Opcodes } = require('../../../util/Constants');
|
||||
|
||||
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) {
|
||||
try {
|
||||
/* eslint-disable */
|
||||
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;
|
||||
}
|
||||
module.exports = (client, { d: data }, shard) => {
|
||||
// Check
|
||||
USER_REQUIRED_ACTION(client, { d: data });
|
||||
|
||||
if (client.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.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) {
|
||||
const 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`,
|
||||
);
|
||||
client.channels._add(private_channel);
|
||||
}
|
||||
|
||||
for (const guild of data.guilds) {
|
||||
@ -141,31 +26,41 @@ module.exports = async (client, { d: data }, shard) => {
|
||||
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);
|
||||
|
||||
client.emit('debug', `[READY] Received ${data.guilds.length} guilds, ${largeGuilds.length} large guilds`);
|
||||
|
||||
// Receive messages in large guilds
|
||||
for (const guild of largeGuilds) {
|
||||
await client.sleep(client.options.messageCreateEventGuildTimeout);
|
||||
client.ws.broadcast({
|
||||
op: Opcodes.GUILD_SUBSCRIPTIONS,
|
||||
d: {
|
||||
guild_id: guild.id,
|
||||
typing: true,
|
||||
threads: true,
|
||||
activities: true,
|
||||
thread_member_lists: [],
|
||||
members: [],
|
||||
channels: {},
|
||||
},
|
||||
});
|
||||
}
|
||||
// User Notes
|
||||
client.notes._reload(data.notes);
|
||||
|
||||
shard.checkReady();
|
||||
// Relationship
|
||||
client.relationships._setup(data.relationships);
|
||||
|
||||
Promise.all(
|
||||
largeGuilds.map(async (guild, index) => {
|
||||
client.ws.broadcast({
|
||||
op: Opcodes.GUILD_SUBSCRIPTIONS,
|
||||
d: {
|
||||
guild_id: guild.id,
|
||||
typing: true,
|
||||
threads: true,
|
||||
activities: true,
|
||||
thread_member_lists: [],
|
||||
members: [],
|
||||
channels: {},
|
||||
},
|
||||
});
|
||||
client.emit('debug', `[READY] Register guild ${guild.id}`);
|
||||
await client.sleep(client.options.messageCreateEventGuildTimeout * index);
|
||||
}),
|
||||
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());
|
||||
};
|
||||
|
@ -1,17 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const { Events, RelationshipTypes } = require('../../../util/Constants');
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
if (data.user) {
|
||||
client.users._add(data.user);
|
||||
}
|
||||
client.relationships.cache.set(data.id, data.type);
|
||||
client.relationships.friendNicknames.set(data.id, data.nickname);
|
||||
client.relationships.sinceCache.set(data.id, new Date(data.since || 0));
|
||||
/**
|
||||
* Emitted whenever a relationship is updated.
|
||||
* Emitted when a relationship is created, relevant to the current user.
|
||||
* @event Client#relationshipAdd
|
||||
* @param {Snowflake} user The userID that was updated
|
||||
* @param {RelationshipTypes} type The new relationship type
|
||||
* @param {Snowflake} user Target userId
|
||||
* @param {boolean} shouldNotify Whether the client should notify the user of this relationship's creation
|
||||
*/
|
||||
client.emit(Events.RELATIONSHIP_ADD, data.id, RelationshipTypes[data.type]);
|
||||
client.emit(Events.RELATIONSHIP_ADD, data.id, Boolean(data.should_notify));
|
||||
};
|
||||
|
@ -1,15 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const { Events, RelationshipTypes } = require('../../../util/Constants');
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
client.relationships.cache.delete(data.id);
|
||||
client.user.friendNicknames.delete(data.id);
|
||||
client.relationships.friendNicknames.delete(data.id);
|
||||
client.relationships.sinceCache.delete(data.id);
|
||||
/**
|
||||
* Emitted whenever a relationship is delete.
|
||||
* Emitted when a relationship is removed, relevant to the current user.
|
||||
* @event Client#relationshipRemove
|
||||
* @param {Snowflake} user The userID that was updated
|
||||
* @param {RelationshipTypes} type The type of the old relationship
|
||||
* @param {string | null} nickname The nickname of the user in this relationship (1-32 characters)
|
||||
*/
|
||||
client.emit(Events.RELATIONSHIP_REMOVE, data.id, RelationshipTypes[data.type]);
|
||||
client.emit(Events.RELATIONSHIP_REMOVE, data.id, data.type, data.nickname);
|
||||
};
|
||||
|
@ -1,18 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
const { Events, RelationshipTypes } = require('../../../util/Constants');
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
client.relationships.cache.set(data.id, data.type);
|
||||
/**
|
||||
* Emitted whenever a relationship is updated.
|
||||
* @typedef {Object} RelationshipUpdateObject
|
||||
* @property {RelationshipTypes} type The type of relationship
|
||||
* @property {Date} since When the user requested a relationship
|
||||
* @property {string | null} nickname The nickname of the user in this relationship (1-32 characters)
|
||||
*/
|
||||
/**
|
||||
* Emitted when a relationship is updated, relevant to the current user (e.g. friend nickname changed).
|
||||
* <info>This is not sent when the type of a relationship changes; see {@link Client#relationshipAdd} and {@link Client#relationshipRemove} for that.</info>
|
||||
* @event Client#relationshipUpdate
|
||||
* @param {Snowflake} user The userID that was updated
|
||||
* @param {RelationshipTypes} type The new relationship type
|
||||
* @param {Object} data The raw data
|
||||
* @param {RelationshipUpdateObject} oldData Old data
|
||||
* @param {RelationshipUpdateObject} newData New data
|
||||
*/
|
||||
if ('nickname' in data) {
|
||||
client.user.friendNicknames.set(data.id, data.nickname);
|
||||
}
|
||||
client.emit(Events.RELATIONSHIP_UPDATE, data.id, RelationshipTypes[data.type], data);
|
||||
const oldType = client.relationships.cache.get(data.id);
|
||||
const oldSince = client.relationships.sinceCache.get(data.id);
|
||||
const oldNickname = client.relationships.friendNicknames.get(data.id);
|
||||
// Update
|
||||
if (data.type) client.relationships.cache.set(data.id, data.type);
|
||||
if (data.nickname) client.relationships.friendNicknames.set(data.id, data.nickname);
|
||||
if (data.since) client.relationships.sinceCache.set(data.id, new Date(data.since || 0));
|
||||
client.emit(
|
||||
Events.RELATIONSHIP_UPDATE,
|
||||
data.id,
|
||||
{
|
||||
type: oldType,
|
||||
nickname: oldNickname,
|
||||
since: oldSince,
|
||||
},
|
||||
{
|
||||
type: data.type,
|
||||
nickname: data.nickname,
|
||||
since: new Date(data.since || 0),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
@ -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);
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
client.user.notes.set(data.id, data.note);
|
||||
client.notes.cache.set(data.id, data.note);
|
||||
};
|
||||
|
@ -1,5 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const Util = require('../../../util/Util');
|
||||
|
||||
module.exports = (client, { d: data }) => Util.clientRequiredAction(client, data.required_action);
|
||||
module.exports = (client, { d: data }) => {
|
||||
let msg;
|
||||
switch (data.required_action) {
|
||||
case undefined:
|
||||
case null: {
|
||||
msg = 'All required actions have been completed.';
|
||||
break;
|
||||
}
|
||||
case 'AGREEMENTS': {
|
||||
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: {
|
||||
terms: true,
|
||||
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;
|
||||
}
|
||||
case 'REQUIRE_CAPTCHA': {
|
||||
msg = 'You need to complete a captcha.';
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_VERIFIED_EMAIL': {
|
||||
msg = 'You need to verify your email.';
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_REVERIFIED_EMAIL': {
|
||||
msg = 'You need to reverify your email.';
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_VERIFIED_PHONE': {
|
||||
msg = 'You need to verify your phone number.';
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_REVERIFIED_PHONE': {
|
||||
msg = 'You need to reverify your phone number.';
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_VERIFIED_EMAIL_OR_VERIFIED_PHONE': {
|
||||
msg = 'You need to verify your email or verify your phone number.';
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_REVERIFIED_EMAIL_OR_VERIFIED_PHONE': {
|
||||
msg = 'You need to reverify your email or verify your phone number.';
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_VERIFIED_EMAIL_OR_REVERIFIED_PHONE': {
|
||||
msg = 'You need to verify your email or reverify your phone number.';
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_REVERIFIED_EMAIL_OR_REVERIFIED_PHONE': {
|
||||
msg = 'You need to reverify your email or reverify your phone number.';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
msg = `Unknown required action: ${data.required_action}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
client.emit('debug', `[USER_REQUIRED_ACTION] ${msg}`);
|
||||
};
|
||||
|
@ -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);
|
||||
};
|
@ -3,10 +3,6 @@
|
||||
const handlers = Object.fromEntries([
|
||||
['READY', require('./READY')],
|
||||
['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_DELETE', require('./APPLICATION_COMMAND_DELETE')],
|
||||
['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_DELETE', require('./AUTO_MODERATION_RULE_DELETE')],
|
||||
['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_DELETE', require('./GUILD_DELETE')],
|
||||
['GUILD_UPDATE', require('./GUILD_UPDATE')],
|
||||
@ -27,8 +20,6 @@ const handlers = Object.fromEntries([
|
||||
['GUILD_MEMBER_REMOVE', require('./GUILD_MEMBER_REMOVE')],
|
||||
['GUILD_MEMBER_UPDATE', require('./GUILD_MEMBER_UPDATE')],
|
||||
['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_ROLE_CREATE', require('./GUILD_ROLE_CREATE')],
|
||||
['GUILD_ROLE_DELETE', require('./GUILD_ROLE_DELETE')],
|
||||
@ -40,9 +31,6 @@ const handlers = Object.fromEntries([
|
||||
['CHANNEL_DELETE', require('./CHANNEL_DELETE')],
|
||||
['CHANNEL_UPDATE', require('./CHANNEL_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_DELETE', require('./MESSAGE_DELETE')],
|
||||
['MESSAGE_UPDATE', require('./MESSAGE_UPDATE')],
|
||||
@ -57,21 +45,12 @@ const handlers = Object.fromEntries([
|
||||
['THREAD_LIST_SYNC', require('./THREAD_LIST_SYNC')],
|
||||
['THREAD_MEMBER_UPDATE', require('./THREAD_MEMBER_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')],
|
||||
['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')],
|
||||
['TYPING_START', require('./TYPING_START')],
|
||||
['VOICE_STATE_UPDATE', require('./VOICE_STATE_UPDATE')],
|
||||
['VOICE_SERVER_UPDATE', require('./VOICE_SERVER_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_UPDATE', require('./STAGE_INSTANCE_UPDATE')],
|
||||
['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_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')],
|
||||
['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;
|
||||
|
@ -11,8 +11,6 @@ const Messages = {
|
||||
TOKEN_INVALID: 'An invalid token was provided.',
|
||||
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_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
|
||||
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}.`,
|
||||
|
||||
SHARDING_INVALID: 'Invalid shard settings were provided.',
|
||||
SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.',
|
||||
SHARDING_INVALID: '[Bot Token] Invalid shard settings were provided.',
|
||||
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.',
|
||||
DISALLOWED_INTENTS: '[Bot Token] Privileged intent provided is not enabled or whitelisted.',
|
||||
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_DESCRIPTION: 'MessageEmbed description 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_URL: 'MessageButton URL must be a string',
|
||||
@ -66,16 +58,22 @@ const Messages = {
|
||||
SELECT_OPTION_VALUE: 'MessageSelectOption value 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}`,
|
||||
|
||||
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_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_IN_GUILD: 'You are only allowed to do this in guild channels.',
|
||||
|
||||
VOICE_STATE_NOT_OWN:
|
||||
'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_OWNED: 'Guild is owned by the client.',
|
||||
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.',
|
||||
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.',
|
||||
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_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.',
|
||||
|
||||
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",
|
||||
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.',
|
||||
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',
|
||||
|
||||
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',
|
||||
|
||||
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',
|
||||
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',
|
||||
|
||||
MISSING_MODULE: (name, installCommand) =>
|
||||
`The module "${name}" is missing. Please install it with "${installCommand}" and try again.`,
|
||||
// Selfbot
|
||||
INVALID_USER_API: 'User accounts cannot use this endpoint',
|
||||
INVALID_COMMAND_NAME: allCMD => `Could not parse subGroupCommand and subCommand due to too long: ${allCMD.join(' ')}`,
|
||||
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);
|
||||
|
53
src/index.js
53
src/index.js
@ -1,11 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const tls = require('tls');
|
||||
// Cipher
|
||||
tls.DEFAULT_CIPHERS = tls.DEFAULT_CIPHERS.split(':')
|
||||
.sort(() => Math.random() - 0.5)
|
||||
.join(':');
|
||||
|
||||
// "Root" classes (starting points)
|
||||
exports.BaseClient = require('./client/BaseClient');
|
||||
exports.Client = require('./client/Client');
|
||||
@ -15,7 +9,6 @@ exports.ShardingManager = require('./sharding/ShardingManager');
|
||||
exports.WebhookClient = require('./client/WebhookClient');
|
||||
|
||||
// Utilities
|
||||
exports.DiscordRPCServer = require('./util/arRPC/index');
|
||||
exports.ActivityFlags = require('./util/ActivityFlags');
|
||||
exports.ApplicationFlags = require('./util/ApplicationFlags');
|
||||
exports.AttachmentFlags = require('./util/AttachmentFlags');
|
||||
@ -42,8 +35,6 @@ exports.ThreadMemberFlags = require('./util/ThreadMemberFlags');
|
||||
exports.UserFlags = require('./util/UserFlags');
|
||||
exports.Util = require('./util/Util');
|
||||
exports.version = require('../package.json').version;
|
||||
exports.DiscordAuthWebsocket = require('./util/RemoteAuth');
|
||||
exports.PurchasedFlags = require('./util/PurchasedFlags');
|
||||
|
||||
// Managers
|
||||
exports.ApplicationCommandManager = require('./managers/ApplicationCommandManager');
|
||||
@ -71,7 +62,6 @@ exports.PresenceManager = require('./managers/PresenceManager');
|
||||
exports.ReactionManager = require('./managers/ReactionManager');
|
||||
exports.ReactionUserManager = require('./managers/ReactionUserManager');
|
||||
exports.RoleManager = require('./managers/RoleManager');
|
||||
exports.SessionManager = require('./managers/SessionManager');
|
||||
exports.StageInstanceManager = require('./managers/StageInstanceManager');
|
||||
exports.ThreadManager = require('./managers/ThreadManager');
|
||||
exports.ThreadMemberManager = require('./managers/ThreadMemberManager');
|
||||
@ -80,6 +70,7 @@ exports.VoiceStateManager = require('./managers/VoiceStateManager');
|
||||
exports.WebSocketManager = require('./client/websocket/WebSocketManager');
|
||||
exports.WebSocketShard = require('./client/websocket/WebSocketShard');
|
||||
exports.RelationshipManager = require('./managers/RelationshipManager');
|
||||
exports.UserNoteManager = require('./managers/UserNoteManager');
|
||||
|
||||
// Structures
|
||||
exports.Activity = require('./structures/Presence').Activity;
|
||||
@ -88,26 +79,18 @@ exports.Application = require('./structures/interfaces/Application');
|
||||
exports.ApplicationCommand = require('./structures/ApplicationCommand');
|
||||
exports.ApplicationRoleConnectionMetadata =
|
||||
require('./structures/ApplicationRoleConnectionMetadata').ApplicationRoleConnectionMetadata;
|
||||
exports.AutocompleteInteraction = require('./structures/AutocompleteInteraction');
|
||||
exports.AutoModerationActionExecution = require('./structures/AutoModerationActionExecution');
|
||||
exports.AutoModerationRule = require('./structures/AutoModerationRule');
|
||||
exports.Base = require('./structures/Base');
|
||||
exports.BaseCommandInteraction = require('./structures/BaseCommandInteraction');
|
||||
exports.BaseGuild = require('./structures/BaseGuild');
|
||||
exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji');
|
||||
exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel');
|
||||
exports.BaseGuildVoiceChannel = require('./structures/BaseGuildVoiceChannel');
|
||||
exports.BaseMessageComponent = require('./structures/BaseMessageComponent');
|
||||
exports.ButtonInteraction = require('./structures/ButtonInteraction');
|
||||
exports.CategoryChannel = require('./structures/CategoryChannel');
|
||||
exports.Channel = require('./structures/Channel').Channel;
|
||||
exports.ClientApplication = require('./structures/ClientApplication');
|
||||
exports.ClientPresence = require('./structures/ClientPresence');
|
||||
exports.ClientUser = require('./structures/ClientUser');
|
||||
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.Emoji = require('./structures/Emoji').Emoji;
|
||||
exports.Guild = require('./structures/Guild').Guild;
|
||||
@ -123,49 +106,27 @@ exports.GuildScheduledEvent = require('./structures/GuildScheduledEvent').GuildS
|
||||
exports.GuildTemplate = require('./structures/GuildTemplate');
|
||||
exports.Integration = require('./structures/Integration');
|
||||
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.InviteStageInstance = require('./structures/InviteStageInstance');
|
||||
exports.InviteGuild = require('./structures/InviteGuild');
|
||||
exports.Message = require('./structures/Message').Message;
|
||||
exports.MessageActionRow = require('./structures/MessageActionRow');
|
||||
exports.MessageAttachment = require('./structures/MessageAttachment');
|
||||
exports.MessageButton = require('./structures/MessageButton');
|
||||
exports.MessageCollector = require('./structures/MessageCollector');
|
||||
exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction');
|
||||
exports.MessageContextMenuInteraction = require('./structures/MessageContextMenuInteraction');
|
||||
exports.MessageEmbed = require('./structures/MessageEmbed');
|
||||
exports.WebEmbed = require('./structures/WebEmbed');
|
||||
exports.MessageMentions = require('./structures/MessageMentions');
|
||||
exports.MessagePayload = require('./structures/MessagePayload');
|
||||
exports.MessageReaction = require('./structures/MessageReaction');
|
||||
exports.MessageSelectMenu = require('./structures/MessageSelectMenu');
|
||||
exports.Modal = require('./structures/Modal');
|
||||
exports.ModalSubmitInteraction = require('./structures/ModalSubmitInteraction');
|
||||
exports.NewsChannel = require('./structures/NewsChannel');
|
||||
exports.OAuth2Guild = require('./structures/OAuth2Guild');
|
||||
exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel');
|
||||
exports.GroupDMChannel = require('./structures/GroupDMChannel');
|
||||
exports.PermissionOverwrites = require('./structures/PermissionOverwrites');
|
||||
exports.Presence = require('./structures/Presence').Presence;
|
||||
exports.ReactionCollector = require('./structures/ReactionCollector');
|
||||
exports.ReactionEmoji = require('./structures/ReactionEmoji');
|
||||
exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets;
|
||||
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.StageInstance = require('./structures/StageInstance').StageInstance;
|
||||
exports.Sticker = require('./structures/Sticker').Sticker;
|
||||
@ -179,7 +140,6 @@ exports.ThreadChannel = require('./structures/ThreadChannel');
|
||||
exports.ThreadMember = require('./structures/ThreadMember');
|
||||
exports.Typing = require('./structures/Typing');
|
||||
exports.User = require('./structures/User');
|
||||
exports.UserContextMenuInteraction = require('./structures/UserContextMenuInteraction');
|
||||
exports.VoiceChannel = require('./structures/VoiceChannel');
|
||||
exports.VoiceRegion = require('./structures/VoiceRegion');
|
||||
exports.VoiceState = require('./structures/VoiceState');
|
||||
@ -188,7 +148,12 @@ exports.Widget = require('./structures/Widget');
|
||||
exports.WidgetMember = require('./structures/WidgetMember');
|
||||
exports.WelcomeChannel = require('./structures/WelcomeChannel');
|
||||
exports.WelcomeScreen = require('./structures/WelcomeScreen');
|
||||
|
||||
exports.WebSocket = require('./WebSocket');
|
||||
|
||||
// DiscordJSVoice Patch
|
||||
exports.DiscordJSVoice = require('./util/Voice');
|
||||
exports.CustomStatus = require('./structures/RichPresence').CustomStatus;
|
||||
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');
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const { isJSONEncodable } = require('@discordjs/builders');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
|
||||
const CachedManager = require('./CachedManager');
|
||||
@ -13,15 +14,14 @@ const Permissions = require('../util/Permissions');
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class ApplicationCommandManager extends CachedManager {
|
||||
constructor(client, iterable, user) {
|
||||
constructor(client, iterable) {
|
||||
super(client, ApplicationCommand, iterable);
|
||||
|
||||
/**
|
||||
* The manager for permissions of arbitrary commands on arbitrary guilds
|
||||
* @type {ApplicationCommandPermissionsManager}
|
||||
*/
|
||||
this.permissions = new ApplicationCommandPermissionsManager(this, user);
|
||||
this.user = user;
|
||||
this.permissions = new ApplicationCommandPermissionsManager(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,7 +43,7 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* @private
|
||||
*/
|
||||
commandPath({ id, guildId } = {}) {
|
||||
let path = this.client.api.applications(this.user.id);
|
||||
let path = this.client.api.applications(this.client.application.id);
|
||||
if (this.guild ?? guildId) path = path.guilds(this.guild?.id ?? guildId);
|
||||
return id ? path.commands(id) : path.commands;
|
||||
}
|
||||
@ -58,7 +58,7 @@ class ApplicationCommandManager extends CachedManager {
|
||||
/* eslint-disable max-len */
|
||||
/**
|
||||
* Data that resolves to the data of an ApplicationCommand
|
||||
* @typedef {ApplicationCommandDataResolvable|SlashCommandBuilder|ContextMenuCommandBuilder} ApplicationCommandDataResolvable
|
||||
* @typedef {ApplicationCommandData|APIApplicationCommand|SlashCommandBuilder|ContextMenuCommandBuilder} ApplicationCommandDataResolvable
|
||||
*/
|
||||
/* eslint-enable max-len */
|
||||
|
||||
@ -94,7 +94,6 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async fetch(id, { guildId, cache = true, force = false, locale, withLocalizations } = {}) {
|
||||
// Change from user.createDM to opcode (risky action)
|
||||
if (typeof id === 'object') {
|
||||
({ guildId, cache = true, locale, withLocalizations } = id);
|
||||
} else if (id) {
|
||||
@ -102,11 +101,10 @@ class ApplicationCommandManager extends CachedManager {
|
||||
const existing = this.cache.get(id);
|
||||
if (existing) return existing;
|
||||
}
|
||||
await this.user.createDM().catch(() => {});
|
||||
const command = await this.commandPath({ id, guildId }).get();
|
||||
return this._add(command, cache);
|
||||
}
|
||||
await this.user.createDM().catch(() => {});
|
||||
|
||||
const data = await this.commandPath({ guildId }).get({
|
||||
headers: {
|
||||
'X-Discord-Locale': locale,
|
||||
@ -132,7 +130,6 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async create(command, guildId) {
|
||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const data = await this.commandPath({ guildId }).post({
|
||||
data: this.constructor.transformCommand(command),
|
||||
});
|
||||
@ -162,7 +159,6 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async set(commands, guildId) {
|
||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const data = await this.commandPath({ guildId }).put({
|
||||
data: commands.map(c => this.constructor.transformCommand(c)),
|
||||
});
|
||||
@ -185,7 +181,6 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async edit(command, data, guildId) {
|
||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const id = this.resolveId(command);
|
||||
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
@ -208,7 +203,6 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async delete(command, guildId) {
|
||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const id = this.resolveId(command);
|
||||
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
@ -226,6 +220,8 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* @private
|
||||
*/
|
||||
static transformCommand(command) {
|
||||
if (isJSONEncodable(command)) return command.toJSON();
|
||||
|
||||
let default_member_permissions;
|
||||
|
||||
if ('default_member_permissions' in command) {
|
||||
@ -240,6 +236,7 @@ class ApplicationCommandManager extends CachedManager {
|
||||
? new Permissions(command.defaultMemberPermissions).bitfield.toString()
|
||||
: command.defaultMemberPermissions;
|
||||
}
|
||||
|
||||
return {
|
||||
name: command.name,
|
||||
name_localizations: command.nameLocalizations ?? command.name_localizations,
|
||||
|
@ -10,7 +10,7 @@ const { ApplicationCommandPermissionTypes, APIErrors } = require('../util/Consta
|
||||
* @extends {BaseManager}
|
||||
*/
|
||||
class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
constructor(manager, user) {
|
||||
constructor(manager) {
|
||||
super(manager.client);
|
||||
|
||||
/**
|
||||
@ -37,8 +37,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.commandId = manager.id ?? null;
|
||||
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,10 +47,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
* @private
|
||||
*/
|
||||
permissionsPath(guildId, commandId) {
|
||||
return this.client.api
|
||||
.applications(typeof this.user === 'string' ? this.user : this.user.id)
|
||||
.guilds(guildId)
|
||||
.commands(commandId).permissions;
|
||||
return this.client.api.applications(this.client.application.id).guilds(guildId).commands(commandId).permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,7 +159,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async set({ guild, command, permissions, fullPermissions } = {}) {
|
||||
if (!this.manager.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
|
||||
if (commandId) {
|
||||
@ -226,7 +220,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async add({ guild, command, permissions }) {
|
||||
if (!this.manager.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
if (!Array.isArray(permissions)) {
|
||||
@ -278,13 +271,12 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async remove({ guild, command, users, roles }) {
|
||||
if (!this.manager.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
if (!users && !roles) throw new TypeError('INVALID_TYPE', 'users OR roles', 'Array or Resolvable', true);
|
||||
|
||||
const resolvedIds = [];
|
||||
let resolvedIds = [];
|
||||
if (Array.isArray(users)) {
|
||||
users.forEach(user => {
|
||||
const userId = this.client.users.resolveId(user);
|
||||
|
@ -113,14 +113,13 @@ class ChannelManager extends CachedManager {
|
||||
}
|
||||
|
||||
const data = await this.client.api.channels(id).get();
|
||||
// Delete in cache
|
||||
this._remove(id);
|
||||
return this._add(data, null, { cache, allowUnknownGuild });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Group DM
|
||||
* @param {UserResolvable[]} recipients Array of recipients
|
||||
* @returns {Promise<PartialGroupDMChannel>} Channel
|
||||
* @returns {Promise<GroupDMChannel>} Channel
|
||||
*/
|
||||
async createGroupDM(recipients) {
|
||||
// Check
|
||||
|
@ -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;
|
@ -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;
|
@ -130,7 +130,7 @@ class GuildBanManager extends CachedManager {
|
||||
* @typedef {Object} BanOptions
|
||||
* @property {number} [days=0] Number of days of messages to delete, must be between 0 and 7, inclusive
|
||||
* <warn>This property is deprecated. Use `deleteMessageSeconds` instead.</warn>
|
||||
* @property {number} [deleteMessageSeconds=0] Number of seconds of messages to delete,
|
||||
* @property {number} [deleteMessageSeconds] Number of seconds of messages to delete,
|
||||
* must be between 0 and 604800 (7 days), inclusive
|
||||
* @property {string} [reason] The reason for the ban
|
||||
*/
|
||||
|
@ -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;
|
@ -3,7 +3,7 @@
|
||||
const ThreadManager = require('./ThreadManager');
|
||||
const { TypeError } = require('../errors');
|
||||
const MessagePayload = require('../structures/MessagePayload');
|
||||
const { resolveAutoArchiveMaxLimit, getAttachments, uploadFile } = require('../util/Util');
|
||||
const { resolveAutoArchiveMaxLimit, getUploadURL, uploadFile } = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Manages API methods for threads in forum channels and stores their cache.
|
||||
@ -20,7 +20,7 @@ class GuildForumThreadManager extends ThreadManager {
|
||||
* @typedef {BaseMessageOptions} GuildForumThreadMessageCreateOptions
|
||||
* @property {StickerResolvable} [stickers] The stickers to send with the message
|
||||
* @property {BitFieldResolvable} [flags] The flags to send with the message.
|
||||
* Only `SUPPRESS_EMBEDS`, `SUPPRESS_NOTIFICATIONS` and `IS_VOICE_MESSAGE` can be set.
|
||||
* Only `SUPPRESS_EMBEDS` and `SUPPRESS_NOTIFICATIONS` can be set.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -63,35 +63,29 @@ class GuildForumThreadManager extends ThreadManager {
|
||||
let messagePayload;
|
||||
|
||||
if (message instanceof MessagePayload) {
|
||||
messagePayload = await message.resolveData();
|
||||
messagePayload = message.resolveData();
|
||||
} else {
|
||||
messagePayload = await MessagePayload.create(this, message).resolveData();
|
||||
messagePayload = MessagePayload.create(this, message).resolveData();
|
||||
}
|
||||
|
||||
let { data: body, files } = await messagePayload.resolveFiles();
|
||||
const { data: body, files } = await messagePayload.resolveFiles();
|
||||
|
||||
if (typeof message == 'object' && typeof message.usingNewAttachmentAPI !== 'boolean') {
|
||||
message.usingNewAttachmentAPI = this.client.options.usingNewAttachmentAPI;
|
||||
}
|
||||
|
||||
if (message?.usingNewAttachmentAPI === true) {
|
||||
const attachments = await getAttachments(this.client, this.channel.id, ...files);
|
||||
const requestPromises = attachments.map(async attachment => {
|
||||
await uploadFile(files[attachment.id].file, attachment.upload_url);
|
||||
return {
|
||||
id: attachment.id,
|
||||
filename: files[attachment.id].name,
|
||||
uploaded_filename: attachment.upload_filename,
|
||||
description: files[attachment.id].description,
|
||||
duration_secs: files[attachment.id].duration_secs,
|
||||
waveform: files[attachment.id].waveform,
|
||||
};
|
||||
});
|
||||
const attachmentsData = await Promise.all(requestPromises);
|
||||
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
|
||||
body.attachments = attachmentsData;
|
||||
files = [];
|
||||
}
|
||||
// New API
|
||||
const attachments = await getUploadURL(this.client, this.channel.id, files);
|
||||
const requestPromises = attachments.map(async attachment => {
|
||||
await uploadFile(files[attachment.id].file, attachment.upload_url);
|
||||
return {
|
||||
id: attachment.id,
|
||||
filename: files[attachment.id].name,
|
||||
uploaded_filename: attachment.upload_filename,
|
||||
description: files[attachment.id].description,
|
||||
duration_secs: files[attachment.id].duration_secs,
|
||||
waveform: files[attachment.id].waveform,
|
||||
};
|
||||
});
|
||||
const attachmentsData = await Promise.all(requestPromises);
|
||||
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
|
||||
data.attachments = attachmentsData;
|
||||
|
||||
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
|
||||
|
||||
@ -103,7 +97,7 @@ class GuildForumThreadManager extends ThreadManager {
|
||||
applied_tags: appliedTags,
|
||||
message: body,
|
||||
},
|
||||
files,
|
||||
files: [],
|
||||
reason,
|
||||
});
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
/* eslint-disable newline-per-chained-call */
|
||||
'use strict';
|
||||
|
||||
const { Buffer } = require('node:buffer');
|
||||
const { setTimeout } = require('node:timers');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
require('lodash.permutations');
|
||||
const _ = require('lodash');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { Error, TypeError, RangeError } = require('../errors');
|
||||
const BaseGuildVoiceChannel = require('../structures/BaseGuildVoiceChannel');
|
||||
@ -191,25 +190,17 @@ class GuildMemberManager extends CachedManager {
|
||||
* guild.members.fetch({ query: 'hydra', limit: 1 })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @see {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/FetchGuildMember.md}
|
||||
*/
|
||||
fetch(options) {
|
||||
if (!options || (typeof options === 'object' && !('user' in options) && !('query' in options))) {
|
||||
if (!options) {
|
||||
if (
|
||||
this.guild.members.me.permissions.has('KICK_MEMBERS') ||
|
||||
this.guild.members.me.permissions.has('BAN_MEMBERS') ||
|
||||
this.guild.members.me.permissions.has('MANAGE_ROLES')
|
||||
this.me.permissions.has('KICK_MEMBERS') ||
|
||||
this.me.permissions.has('BAN_MEMBERS') ||
|
||||
this.me.permissions.has('MANAGE_ROLES')
|
||||
) {
|
||||
return this._fetchMany();
|
||||
} else if (this.guild.memberCount <= 10000) {
|
||||
return this.fetchByMemberSafety();
|
||||
} else {
|
||||
// NOTE: This is a very slow method, and can take up to 999+ minutes to complete.
|
||||
return this.fetchBruteforce({
|
||||
delay: 50,
|
||||
skipWarn: true,
|
||||
depth: 1,
|
||||
});
|
||||
return this.fetchByMemberSafety();
|
||||
}
|
||||
}
|
||||
const user = this.client.users.resolveId(options);
|
||||
@ -471,221 +462,6 @@ class GuildMemberManager extends CachedManager {
|
||||
return this._add(data, cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options used to fetch multiple members from a guild.
|
||||
* @typedef {Object} BruteforceOptions
|
||||
* @property {number} [limit=100] Maximum number of members per request
|
||||
* @property {number} [delay=500] Timeout for new requests in ms
|
||||
* @property {number} [depth=1] Permutations length
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetches multiple members from the guild.
|
||||
* @param {BruteforceOptions} options Options for the bruteforce
|
||||
* @returns {Collection<Snowflake, GuildMember>} (All) members in the guild
|
||||
* @see https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/FetchGuildMember.md
|
||||
* @example
|
||||
* guild.members.fetchBruteforce()
|
||||
* .then(members => console.log(`Fetched ${members.size} members`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
fetchBruteforce(options = {}) {
|
||||
const defaultQuery = 'abcdefghijklmnopqrstuvwxyz0123456789!"#$%&\'()*+,-./:;<=>?@[]^_`{|}~ ';
|
||||
let dictionary;
|
||||
let limit = 100;
|
||||
let delay = 500;
|
||||
let depth = 1;
|
||||
if (options?.limit) limit = options?.limit;
|
||||
if (options?.delay) delay = options?.delay;
|
||||
if (options?.depth) depth = options?.depth;
|
||||
if (typeof limit !== 'number') throw new TypeError('INVALID_TYPE', 'limit', 'Number');
|
||||
if (limit < 1 || limit > 100) throw new RangeError('INVALID_RANGE_QUERY_MEMBER');
|
||||
if (typeof delay !== 'number') throw new TypeError('INVALID_TYPE', 'delay', 'Number');
|
||||
if (typeof depth !== 'number') throw new TypeError('INVALID_TYPE', 'depth', 'Number');
|
||||
if (depth < 1) throw new RangeError('INVALID_RANGE_QUERY_MEMBER');
|
||||
if (depth > 2) {
|
||||
console.warn(`[WARNING] GuildMemberManager#fetchBruteforce: depth greater than 2, can lead to very slow speeds`);
|
||||
}
|
||||
if (delay < 500 && !options?.skipWarn) {
|
||||
console.warn(
|
||||
`[WARNING] GuildMemberManager#fetchBruteforce: delay is less than 500ms, this may cause rate limits.`,
|
||||
);
|
||||
}
|
||||
let skipValues = [];
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve, reject) => {
|
||||
for (let i = 1; i <= depth; i++) {
|
||||
dictionary = _(defaultQuery)
|
||||
.permutations(i)
|
||||
.map(v => _.join(v, ''))
|
||||
.value();
|
||||
for (const query of dictionary) {
|
||||
if (this.guild.members.cache.size >= this.guild.memberCount) break;
|
||||
this.client.emit(
|
||||
'debug',
|
||||
`[INFO] GuildMemberManager#fetchBruteforce: Querying ${query}, Skip: [${skipValues.join(', ')}]`,
|
||||
);
|
||||
if (skipValues.some(v => query.startsWith(v))) continue;
|
||||
await this._fetchMany({ query, limit })
|
||||
.then(members => {
|
||||
if (members.size === 0) skipValues.push(query);
|
||||
})
|
||||
.catch(reject);
|
||||
await this.guild.client.sleep(delay);
|
||||
}
|
||||
}
|
||||
resolve(this.guild.members.cache);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Experimental method to fetch members from the guild.
|
||||
* <info>Lists up to 10000 members of the guild.</info>
|
||||
* @param {number} [timeout=15_000] Timeout for receipt of members in ms
|
||||
* @returns {Promise<Collection<Snowflake, GuildMember>>}
|
||||
*/
|
||||
fetchByMemberSafety(timeout = 15_000) {
|
||||
return new Promise(resolve => {
|
||||
const nonce = SnowflakeUtil.generate();
|
||||
let timeout_ = setTimeout(() => {
|
||||
this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
|
||||
resolve(this.guild.members.cache);
|
||||
}, timeout).unref();
|
||||
const handler = (members, guild, raw) => {
|
||||
if (guild.id == this.guild.id && raw.nonce == nonce) {
|
||||
if (members.size > 0) {
|
||||
this.client.ws.broadcast({
|
||||
op: 35,
|
||||
d: {
|
||||
guild_id: this.guild.id,
|
||||
query: '',
|
||||
continuation_token: members.first()?.id,
|
||||
nonce,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
clearTimeout(timeout_);
|
||||
this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
|
||||
resolve(this.guild.members.cache);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.client.on('guildMembersChunk', handler);
|
||||
this.client.ws.broadcast({
|
||||
op: 35,
|
||||
d: {
|
||||
guild_id: this.guild.id,
|
||||
query: '',
|
||||
continuation_token: null,
|
||||
nonce,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches multiple members from the guild in the channel.
|
||||
* @param {GuildTextChannelResolvable} channel The channel to get members from (Members has VIEW_CHANNEL permission)
|
||||
* @param {number} [offset=0] Start index of the members to get
|
||||
* @param {boolean} [double=false] Whether to use double range
|
||||
* @param {number} [retryMax=3] Number of retries
|
||||
* @param {number} [time=10e3] Timeout for receipt of members
|
||||
* @returns {Collection<Snowflake, GuildMember>} Members in the guild
|
||||
* @see {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/FetchGuildMember.md}
|
||||
* @example
|
||||
* 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
|
||||
*/
|
||||
fetchMemberList(channel, offset = 0, double = false, retryMax = 3, time = 10_000) {
|
||||
const channel_ = this.guild.channels.resolve(channel);
|
||||
if (!channel_?.isText()) throw new TypeError('INVALID_TYPE', 'channel', 'GuildTextChannelResolvable');
|
||||
if (typeof offset !== 'number') throw new TypeError('INVALID_TYPE', 'offset', 'Number');
|
||||
if (typeof time !== 'number') throw new TypeError('INVALID_TYPE', 'time', 'Number');
|
||||
if (typeof retryMax !== 'number') throw new TypeError('INVALID_TYPE', 'retryMax', 'Number');
|
||||
if (retryMax < 1) throw new RangeError('INVALID_RANGE_RETRY');
|
||||
if (typeof double !== 'boolean') throw new TypeError('INVALID_TYPE', 'double', 'Boolean');
|
||||
// TODO: if (this.guild.large) throw new Error('GUILD_IS_LARGE');
|
||||
return new Promise((resolve, reject) => {
|
||||
const default_ = [[0, 99]];
|
||||
const fetchedMembers = new Collection();
|
||||
if (offset > 99) {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
double
|
||||
? default_.push([offset, offset + 99], [offset + 100, offset + 199])
|
||||
: default_.push([offset, offset + 99]);
|
||||
}
|
||||
let retry = 0;
|
||||
const handler = (members, guild, type, raw) => {
|
||||
timeout.refresh();
|
||||
if (guild.id !== this.guild.id) return;
|
||||
if (type == 'INVALIDATE' && offset > 100) {
|
||||
if (retry < retryMax) {
|
||||
this.guild.shard.send({
|
||||
op: Opcodes.GUILD_SUBSCRIPTIONS,
|
||||
d: {
|
||||
guild_id: this.guild.id,
|
||||
typing: true,
|
||||
threads: true,
|
||||
activities: true,
|
||||
channels: {
|
||||
[channel_.id]: default_,
|
||||
},
|
||||
thread_member_lists: [],
|
||||
members: [],
|
||||
},
|
||||
});
|
||||
retry++;
|
||||
} else {
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(new Error('INVALIDATE_MEMBER', raw.ops[0].range));
|
||||
}
|
||||
} else {
|
||||
for (const member of members.values()) {
|
||||
fetchedMembers.set(member.id, member);
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
resolve(fetchedMembers);
|
||||
}
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(new Error('GUILD_MEMBERS_TIMEOUT'));
|
||||
}, time).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on(Events.GUILD_MEMBER_LIST_UPDATE, handler);
|
||||
this.guild.shard.send({
|
||||
op: Opcodes.GUILD_SUBSCRIPTIONS,
|
||||
d: {
|
||||
guild_id: this.guild.id,
|
||||
typing: true,
|
||||
threads: true,
|
||||
activities: true,
|
||||
channels: {
|
||||
[channel_.id]: default_,
|
||||
},
|
||||
thread_member_lists: [],
|
||||
members: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a role to a member.
|
||||
* @param {GuildMemberResolvable} user The user to add the role from
|
||||
@ -718,6 +494,51 @@ class GuildMemberManager extends CachedManager {
|
||||
return this.resolve(user) ?? this.client.users.resolve(user) ?? userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Experimental method to fetch members from the guild.
|
||||
* <info>Lists up to 10000 members of the guild.</info>
|
||||
* @param {number} [timeout=15_000] Timeout for receipt of members in ms
|
||||
* @returns {Promise<Collection<Snowflake, GuildMember>>}
|
||||
*/
|
||||
fetchByMemberSafety(timeout = 15_000) {
|
||||
return new Promise(resolve => {
|
||||
const nonce = SnowflakeUtil.generate();
|
||||
let timeout_ = setTimeout(() => {
|
||||
this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
|
||||
resolve(this.guild.members.cache);
|
||||
}, timeout).unref();
|
||||
const handler = (members, guild, raw) => {
|
||||
if (guild.id == this.guild.id && raw.nonce == nonce) {
|
||||
if (members.size > 0) {
|
||||
this.client.ws.broadcast({
|
||||
op: Opcodes.SEARCH_RECENT_MEMBERS,
|
||||
d: {
|
||||
guild_id: this.guild.id,
|
||||
query: '',
|
||||
continuation_token: members.first()?.id,
|
||||
nonce,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
clearTimeout(timeout_);
|
||||
this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
|
||||
resolve(this.guild.members.cache);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.client.on('guildMembersChunk', handler);
|
||||
this.client.ws.broadcast({
|
||||
op: Opcodes.SEARCH_RECENT_MEMBERS,
|
||||
d: {
|
||||
guild_id: this.guild.id,
|
||||
query: '',
|
||||
continuation_token: null,
|
||||
nonce,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_fetchMany({
|
||||
limit = 0,
|
||||
withPresences: presences = true,
|
||||
|
@ -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;
|
@ -2,7 +2,7 @@
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { TypeError, Error } = require('../errors');
|
||||
const { TypeError } = require('../errors');
|
||||
const { Message } = require('../structures/Message');
|
||||
const MessagePayload = require('../structures/MessagePayload');
|
||||
const Util = require('../util/Util');
|
||||
@ -123,38 +123,32 @@ class MessageManager extends CachedManager {
|
||||
const messageId = this.resolveId(message);
|
||||
if (!messageId) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
let messagePayload;
|
||||
if (options instanceof MessagePayload) {
|
||||
messagePayload = await options.resolveData();
|
||||
} else {
|
||||
messagePayload = await MessagePayload.create(message instanceof Message ? message : this, options).resolveData();
|
||||
}
|
||||
let { data, files } = await messagePayload.resolveFiles();
|
||||
const { data, files } = await (options instanceof MessagePayload
|
||||
? options
|
||||
: MessagePayload.create(message instanceof Message ? message : this, options)
|
||||
)
|
||||
.resolveData()
|
||||
.resolveFiles();
|
||||
|
||||
if (typeof options == 'object' && typeof options.usingNewAttachmentAPI !== 'boolean') {
|
||||
options.usingNewAttachmentAPI = this.client.options.usingNewAttachmentAPI;
|
||||
}
|
||||
// New API
|
||||
const attachments = await Util.getUploadURL(this.client, this.channel.id, files);
|
||||
const requestPromises = attachments.map(async attachment => {
|
||||
await Util.uploadFile(files[attachment.id].file, attachment.upload_url);
|
||||
return {
|
||||
id: attachment.id,
|
||||
filename: files[attachment.id].name,
|
||||
uploaded_filename: attachment.upload_filename,
|
||||
description: files[attachment.id].description,
|
||||
duration_secs: files[attachment.id].duration_secs,
|
||||
waveform: files[attachment.id].waveform,
|
||||
};
|
||||
});
|
||||
const attachmentsData = await Promise.all(requestPromises);
|
||||
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
|
||||
data.attachments = attachmentsData;
|
||||
// Empty Files
|
||||
|
||||
if (options?.usingNewAttachmentAPI === true) {
|
||||
const attachments = await Util.getAttachments(this.client, this.channel.id, ...files);
|
||||
const requestPromises = attachments.map(async attachment => {
|
||||
await Util.uploadFile(files[attachment.id].file, attachment.upload_url);
|
||||
return {
|
||||
id: attachment.id,
|
||||
filename: files[attachment.id].name,
|
||||
uploaded_filename: attachment.upload_filename,
|
||||
description: files[attachment.id].description,
|
||||
duration_secs: files[attachment.id].duration_secs,
|
||||
waveform: files[attachment.id].waveform,
|
||||
};
|
||||
});
|
||||
const attachmentsData = await Promise.all(requestPromises);
|
||||
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
|
||||
data.attachments = attachmentsData;
|
||||
files = [];
|
||||
}
|
||||
|
||||
const d = await this.client.api.channels[this.channel.id].messages[messageId].patch({ data, files });
|
||||
const d = await this.client.api.channels[this.channel.id].messages[messageId].patch({ data });
|
||||
|
||||
const existing = this.cache.get(messageId);
|
||||
if (existing) {
|
||||
@ -251,12 +245,16 @@ class MessageManager extends CachedManager {
|
||||
const existing = this.cache.get(messageId);
|
||||
if (existing && !existing.partial) return existing;
|
||||
}
|
||||
|
||||
// https://discord.com/api/v9/channels/:id/messages?limit=50&around=:msgid
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fetchMany({
|
||||
around: messageId,
|
||||
limit: 50,
|
||||
})
|
||||
this._fetchMany(
|
||||
{
|
||||
around: messageId,
|
||||
limit: 50,
|
||||
},
|
||||
cache,
|
||||
)
|
||||
.then(data_ =>
|
||||
data_.has(messageId) ? resolve(data_.get(messageId)) : reject(new Error('MESSAGE_ID_NOT_FOUND')),
|
||||
)
|
||||
@ -264,13 +262,6 @@ class MessageManager extends CachedManager {
|
||||
});
|
||||
}
|
||||
|
||||
async _fetchMany(options = {}, cache) {
|
||||
const data = await this.client.api.channels[this.channel.id].messages.get({ query: options });
|
||||
const messages = new Collection();
|
||||
for (const message of data) messages.set(message.id, this._add(message, cache));
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} MessageSearchOptions
|
||||
* @property {Array<UserResolvable>} [authors] An array of author to filter by
|
||||
@ -388,6 +379,13 @@ class MessageManager extends CachedManager {
|
||||
total: data.total_results,
|
||||
};
|
||||
}
|
||||
|
||||
async _fetchMany(options = {}, cache) {
|
||||
const data = await this.client.api.channels[this.channel.id].messages.get({ query: options });
|
||||
const messages = new Collection();
|
||||
for (const message of data) messages.set(message.id, this._add(message, cache));
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageManager;
|
||||
|
@ -89,7 +89,7 @@ class PermissionOverwriteManager extends CachedManager {
|
||||
* @private
|
||||
*/
|
||||
async upsert(userOrRole, options, overwriteOptions = {}, existing) {
|
||||
const userOrRoleId = this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole);
|
||||
let userOrRoleId = this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole);
|
||||
let { type, reason } = overwriteOptions;
|
||||
if (typeof type !== 'number') {
|
||||
userOrRole = this.channel.guild.roles.resolve(userOrRole) ?? this.client.users.resolve(userOrRole);
|
||||
|
@ -3,15 +3,15 @@
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { Error } = require('../errors');
|
||||
const { lazy } = require('../util/Util');
|
||||
const User = lazy(() => require('../structures/User'));
|
||||
const User = require('../structures/User');
|
||||
|
||||
/**
|
||||
* Manages API methods for users who reacted to a reaction and stores their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class ReactionUserManager extends CachedManager {
|
||||
constructor(reaction, iterable) {
|
||||
super(reaction.client, User(), iterable);
|
||||
super(reaction.client, User, iterable);
|
||||
|
||||
/**
|
||||
* The reaction that this manager belongs to
|
||||
@ -22,7 +22,7 @@ class ReactionUserManager extends CachedManager {
|
||||
|
||||
/**
|
||||
* The cache of this manager
|
||||
* @type {Collection<Snowflake, Discord.User>}
|
||||
* @type {Collection<Snowflake, User>}
|
||||
* @name ReactionUserManager#cache
|
||||
*/
|
||||
|
||||
@ -36,7 +36,7 @@ class ReactionUserManager extends CachedManager {
|
||||
/**
|
||||
* Fetches all the users that gave this reaction. Resolves with a collection of users, mapped by their ids.
|
||||
* @param {FetchReactionUsersOptions} [options] Options for fetching the users
|
||||
* @returns {Promise<Collection<Snowflake, Discord.User>>}
|
||||
* @returns {Promise<Collection<Snowflake, User>>}
|
||||
*/
|
||||
async fetch({ limit = 100, after } = {}) {
|
||||
const message = this.reaction.message;
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const Buffer = require('node:buffer').Buffer;
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const BaseManager = require('./BaseManager');
|
||||
const { GuildMember } = require('../structures/GuildMember');
|
||||
const { Message } = require('../structures/Message');
|
||||
const ThreadMember = require('../structures/ThreadMember');
|
||||
@ -11,19 +11,22 @@ const { RelationshipTypes } = require('../util/Constants');
|
||||
/**
|
||||
* Manages API methods for Relationships and stores their cache.
|
||||
*/
|
||||
class RelationshipManager {
|
||||
class RelationshipManager extends BaseManager {
|
||||
constructor(client, users) {
|
||||
/**
|
||||
* The client that instantiated this manager.
|
||||
* @type {Client}
|
||||
*/
|
||||
this.client = client;
|
||||
super(client);
|
||||
/**
|
||||
* A collection of users this manager is caching. (Type: Number)
|
||||
* @type {Collection<Snowflake, RelationshipTypes>}
|
||||
* @readonly
|
||||
*/
|
||||
this.cache = new Collection();
|
||||
/**
|
||||
* @type {Collection<Snowflake, string>}
|
||||
*/
|
||||
this.friendNicknames = new Collection();
|
||||
/**
|
||||
* @type {Collection<Snowflake, Date>}
|
||||
*/
|
||||
this.sinceCache = new Collection();
|
||||
this._setup(users);
|
||||
}
|
||||
|
||||
@ -35,7 +38,7 @@ class RelationshipManager {
|
||||
get friendCache() {
|
||||
const users = this.cache
|
||||
.filter(value => value === RelationshipTypes.FRIEND)
|
||||
.map((value, key) => [key, this.client.users.cache.get(key)]);
|
||||
.map((_, key) => [key, this.client.users.cache.get(key)]);
|
||||
return new Collection(users);
|
||||
}
|
||||
|
||||
@ -47,7 +50,7 @@ class RelationshipManager {
|
||||
get blockedCache() {
|
||||
const users = this.cache
|
||||
.filter(value => value === RelationshipTypes.BLOCKED)
|
||||
.map((value, key) => [key, this.client.users.cache.get(key)]);
|
||||
.map((_, key) => [key, this.client.users.cache.get(key)]);
|
||||
return new Collection(users);
|
||||
}
|
||||
|
||||
@ -59,7 +62,7 @@ class RelationshipManager {
|
||||
get incomingCache() {
|
||||
const users = this.cache
|
||||
.filter(value => value === RelationshipTypes.PENDING_INCOMING)
|
||||
.map((value, key) => [key, this.client.users.cache.get(key)]);
|
||||
.map((_, key) => [key, this.client.users.cache.get(key)]);
|
||||
return new Collection(users);
|
||||
}
|
||||
|
||||
@ -71,16 +74,29 @@ class RelationshipManager {
|
||||
get outgoingCache() {
|
||||
const users = this.cache
|
||||
.filter(value => value === RelationshipTypes.PENDING_OUTGOING)
|
||||
.map((value, key) => [key, this.client.users.cache.get(key)]);
|
||||
.map((_, key) => [key, this.client.users.cache.get(key)]);
|
||||
return new Collection(users);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of cache
|
||||
* @returns {Array<{id: Snowflake, type: RelationshipTypes}>}
|
||||
* @typedef {Object} RelationshipJSONData
|
||||
* @property {Snowflake} id The ID of the target user
|
||||
* @property {RelationshipTypes} type The type of relationship
|
||||
* @property {string | null} nickname The nickname of the user in this relationship (1-32 characters)
|
||||
* @property {string} since When the user requested a relationship (ISO8601 timestamp)
|
||||
*/
|
||||
toArray() {
|
||||
return this.cache.map((value, key) => ({ id: key, type: RelationshipTypes[value] }));
|
||||
|
||||
/**
|
||||
* Return array of cache
|
||||
* @returns {RelationshipJSONData[]}
|
||||
*/
|
||||
toJSON() {
|
||||
return this.cache.map((value, key) => ({
|
||||
id: key,
|
||||
type: RelationshipTypes[value],
|
||||
nickname: this.friendNicknames.get(key),
|
||||
since: this.sinceCache.get(key).toISOString(),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,8 +107,9 @@ class RelationshipManager {
|
||||
_setup(users) {
|
||||
if (!Array.isArray(users)) return;
|
||||
for (const relationShip of users) {
|
||||
this.client.user.friendNicknames.set(relationShip.id, relationShip.nickname);
|
||||
this.friendNicknames.set(relationShip.id, relationShip.nickname);
|
||||
this.cache.set(relationShip.id, relationShip.type);
|
||||
this.sinceCache.set(relationShip.id, new Date(relationShip.since || 0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,66 +150,55 @@ class RelationshipManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a friend relationship with a client user.
|
||||
* Deletes a friend / blocked relationship with a client user or cancels a friend request.
|
||||
* @param {UserResolvable} user Target
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
deleteFriend(user) {
|
||||
async deleteRelationship(user) {
|
||||
const id = this.resolveId(user);
|
||||
// Check if already friends
|
||||
if (this.cache.get(id) !== RelationshipTypes.FRIEND) return false;
|
||||
return this.__cancel(id);
|
||||
if (
|
||||
![RelationshipTypes.FRIEND, RelationshipTypes.BLOCKED, RelationshipTypes.PENDING_OUTGOING].includes(
|
||||
this.cache.get(id),
|
||||
)
|
||||
) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
await this.client.api.users['@me'].relationships[id].delete({
|
||||
DiscordContext: { location: 'Friends' },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a blocked relationship with a client user.
|
||||
* @param {UserResolvable} user Target
|
||||
* @returns {Promise<boolean>}
|
||||
* @typedef {Object} FriendRequestOptions
|
||||
* @property {UserResolvable} [user] Target
|
||||
* @property {string} [username] Discord username
|
||||
* @property {number | null} [discriminator] Discord discriminator
|
||||
*/
|
||||
deleteBlocked(user) {
|
||||
const id = this.resolveId(user);
|
||||
// Check if already blocked
|
||||
if (this.cache.get(id) !== RelationshipTypes.BLOCKED) return false;
|
||||
return this.__cancel(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a friend request.
|
||||
* @param {string} username Username of the user to send the request to
|
||||
* @param {?number} discriminator Discriminator of the user to send the request to
|
||||
* @param {FriendRequestOptions} options Target
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async sendFriendRequest(username, discriminator) {
|
||||
await this.client.api.users('@me').relationships.post({
|
||||
data: {
|
||||
username,
|
||||
discriminator: discriminator == 0 ? null : parseInt(discriminator),
|
||||
},
|
||||
headers: {
|
||||
'X-Context-Properties': Buffer.from(JSON.stringify({ location: 'Add Friend' }), 'utf8').toString('base64'),
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a friend request.
|
||||
* @param {UserResolvable} user the user you want to delete
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
cancelFriendRequest(user) {
|
||||
const id = this.resolveId(user);
|
||||
if (this.cache.get(id) !== RelationshipTypes.PENDING_OUTGOING) return false;
|
||||
return this.__cancel(id);
|
||||
}
|
||||
|
||||
async __cancel(id) {
|
||||
await this.client.api.users['@me'].relationships[id].delete({
|
||||
headers: {
|
||||
'X-Context-Properties': Buffer.from(JSON.stringify({ location: 'Friends' }), 'utf8').toString('base64'),
|
||||
},
|
||||
});
|
||||
return true;
|
||||
async sendFriendRequest(options) {
|
||||
if (options?.user) {
|
||||
const id = this.resolveId(options.user);
|
||||
await this.client.api.users['@me'].relationships[id].put({
|
||||
data: {},
|
||||
DiscordContext: { location: 'ContextMenu' },
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
await this.client.api.users['@me'].relationships.post({
|
||||
data: {
|
||||
username: options.username,
|
||||
discriminator: options.discriminator,
|
||||
},
|
||||
DiscordContext: { location: 'Add Friend' },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -203,16 +209,14 @@ class RelationshipManager {
|
||||
async addFriend(user) {
|
||||
const id = this.resolveId(user);
|
||||
// Check if already friends
|
||||
if (this.cache.get(id) === RelationshipTypes.FRIEND) return false;
|
||||
if (this.cache.get(id) === RelationshipTypes.FRIEND) return Promise.resolve(false);
|
||||
// Check if outgoing request
|
||||
if (this.cache.get(id) === RelationshipTypes.PENDING_OUTGOING) return false;
|
||||
if (this.cache.get(id) === RelationshipTypes.PENDING_OUTGOING) return Promise.resolve(false);
|
||||
await this.client.api.users['@me'].relationships[id].put({
|
||||
data: {
|
||||
type: RelationshipTypes.FRIEND,
|
||||
},
|
||||
headers: {
|
||||
'X-Context-Properties': Buffer.from(JSON.stringify({ location: 'Friends' }), 'utf8').toString('base64'),
|
||||
},
|
||||
DiscordContext: { location: 'Friends' },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@ -223,14 +227,19 @@ class RelationshipManager {
|
||||
* @param {?string} nickname New nickname
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async setNickname(user, nickname) {
|
||||
async setNickname(user, nickname = null) {
|
||||
const id = this.resolveId(user);
|
||||
if (this.cache.get(id) !== RelationshipTypes.FRIEND) return false;
|
||||
if (this.cache.get(id) !== RelationshipTypes.FRIEND) return Promise.resolve(false);
|
||||
await this.client.api.users['@me'].relationships[id].patch({
|
||||
data: {
|
||||
nickname: typeof nickname === 'string' ? nickname : null,
|
||||
},
|
||||
});
|
||||
if (nickname) {
|
||||
this.friendNicknames.set(id, nickname);
|
||||
} else {
|
||||
this.friendNicknames.delete(id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -242,14 +251,12 @@ class RelationshipManager {
|
||||
async addBlocked(user) {
|
||||
const id = this.resolveId(user);
|
||||
// Check
|
||||
if (this.cache.get(id) === RelationshipTypes.BLOCKED) return false;
|
||||
if (this.cache.get(id) === RelationshipTypes.BLOCKED) return Promise.resolve(false);
|
||||
await this.client.api.users['@me'].relationships[id].put({
|
||||
data: {
|
||||
type: RelationshipTypes.BLOCKED,
|
||||
},
|
||||
headers: {
|
||||
'X-Context-Properties': Buffer.from(JSON.stringify({ location: 'ContextMenu' }), 'utf8').toString('base64'),
|
||||
},
|
||||
DiscordContext: { location: 'ContextMenu' },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
@ -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;
|
@ -2,7 +2,6 @@
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { TypeError } = require('../errors');
|
||||
const ThreadChannel = require('../structures/ThreadChannel');
|
||||
|
||||
/**
|
||||
@ -119,34 +118,8 @@ class ThreadManager extends CachedManager {
|
||||
* @param {boolean} [cache=true] Whether to cache the new thread objects if they aren't already
|
||||
* @returns {Promise<FetchedThreads>}
|
||||
*/
|
||||
async fetchArchived(options = {}, cache = true) {
|
||||
if (this.client.user.bot) {
|
||||
let { type = 'public', fetchAll = false, before, limit } = options;
|
||||
let path = this.client.api.channels(this.channel.id);
|
||||
if (type === 'private' && !fetchAll) {
|
||||
path = path.users('@me');
|
||||
}
|
||||
let timestamp;
|
||||
let id;
|
||||
if (typeof before !== 'undefined') {
|
||||
if (before instanceof ThreadChannel || /^\d{17,19}$/.test(String(before))) {
|
||||
id = this.resolveId(before);
|
||||
timestamp = this.resolve(before)?.archivedAt?.toISOString();
|
||||
} else {
|
||||
try {
|
||||
timestamp = new Date(before).toISOString();
|
||||
} catch {
|
||||
throw new TypeError('INVALID_TYPE', 'before', 'DateResolvable or ThreadChannelResolvable');
|
||||
}
|
||||
}
|
||||
}
|
||||
const raw = await path.threads
|
||||
.archived(type)
|
||||
.get({ query: { before: type === 'private' && !fetchAll ? id : timestamp, limit } });
|
||||
return this.constructor._mapThreads(raw, this.client, { parent: this.channel, cache });
|
||||
} else {
|
||||
return this.fetchActive(cache, { archived: true, ...options });
|
||||
}
|
||||
fetchArchived(options = {}, cache = true) {
|
||||
return this.fetchActive(cache, { archived: true, ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,22 +138,16 @@ class ThreadManager extends CachedManager {
|
||||
* @param {FetchChannelThreadsOptions} [options] Options for self-bots where advanced users can specify further options
|
||||
* @returns {Promise<FetchedThreads>}
|
||||
*/
|
||||
async fetchActive(cache = true, options = null) {
|
||||
if (options && this.client.user.bot) {
|
||||
throw new Error('INVALID_BOT_OPTIONS: Options can only be specified for user accounts.');
|
||||
}
|
||||
|
||||
const raw = this.client.user.bot
|
||||
? await this.client.api.guilds(this.channel.guild.id).threads.active.get()
|
||||
: await this.client.api.channels(this.channel.id).threads.search.get({
|
||||
query: {
|
||||
archived: options?.archived ?? false,
|
||||
limit: options?.limit ?? 25,
|
||||
offset: options?.offset ?? 0,
|
||||
sort_by: options?.sortBy ?? 'last_message_time',
|
||||
sort_order: options?.sortOrder ?? 'desc',
|
||||
},
|
||||
});
|
||||
async fetchActive(cache = true, options = {}) {
|
||||
const raw = await this.client.api.channels(this.channel.id).threads.search.get({
|
||||
query: {
|
||||
archived: options?.archived ?? false,
|
||||
limit: options?.limit ?? 25,
|
||||
offset: options?.offset ?? 0,
|
||||
sort_by: options?.sortBy ?? 'last_message_time',
|
||||
sort_order: options?.sortOrder ?? 'desc',
|
||||
},
|
||||
});
|
||||
|
||||
return this.constructor._mapThreads(raw, this.client, { parent: this.channel, cache });
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ class ThreadMemberManager extends CachedManager {
|
||||
const id = this.resolveId(member);
|
||||
return id
|
||||
? this._fetchOne(id, options)
|
||||
: this._fetchMany(typeof member !== 'boolean' ? member : { ...options, cache: member });
|
||||
: this._fetchMany(typeof member === 'boolean' ? { ...options, cache: member } : options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,12 +60,11 @@ class UserManager extends CachedManager {
|
||||
data: {
|
||||
recipients: [id],
|
||||
},
|
||||
headers: {
|
||||
'X-Context-Properties': 'e30=', // {}
|
||||
},
|
||||
DiscordContext: {},
|
||||
});
|
||||
|
||||
const dm_channel = await this.client.channels._add(data, null, { cache });
|
||||
await dm_channel.sync();
|
||||
dm_channel.sync();
|
||||
return dm_channel;
|
||||
}
|
||||
|
||||
@ -87,10 +86,9 @@ class UserManager extends CachedManager {
|
||||
* Obtains a user from Discord, or the user cache if it's already available.
|
||||
* @param {UserResolvable} user The user to fetch
|
||||
* @param {BaseFetchOptions} [options] Additional options for this fetch
|
||||
* @param {?Snowflake} [options.guildId] The guild ID to fetch the member for
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async fetch(user, { cache = true, force = false, guildId = null } = {}) {
|
||||
async fetch(user, { cache = true, force = false } = {}) {
|
||||
const id = this.resolveId(user);
|
||||
if (!force) {
|
||||
const existing = this.cache.get(id);
|
||||
@ -98,9 +96,7 @@ class UserManager extends CachedManager {
|
||||
}
|
||||
|
||||
const data = await this.client.api.users(id).get();
|
||||
const userObject = this._add(data, cache);
|
||||
await userObject.getProfile(guildId ?? null).catch(() => {});
|
||||
return userObject;
|
||||
return this._add(data, cache);
|
||||
}
|
||||
|
||||
/**
|
||||
|
53
src/managers/UserNoteManager.js
Normal file
53
src/managers/UserNoteManager.js
Normal file
@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const BaseManager = require('./BaseManager');
|
||||
|
||||
/**
|
||||
* Manages API methods for Client and stores their cache.
|
||||
* @extends {BaseManager}
|
||||
*/
|
||||
class UserNoteManager extends BaseManager {
|
||||
constructor(client, data = {}) {
|
||||
super(client);
|
||||
/**
|
||||
* Cache User Note
|
||||
* @type {Collection<Snowflake, string>}
|
||||
*/
|
||||
this.cache = new Collection(Object.entries(data));
|
||||
}
|
||||
|
||||
_reload(data = {}) {
|
||||
this.cache = new Collection(Object.entries(data));
|
||||
return this;
|
||||
}
|
||||
|
||||
async updateNote(id, note = null) {
|
||||
await this.client.api.users['@me'].notes(id).put({ data: { note } });
|
||||
if (!note) this.cache.delete(id, note);
|
||||
else this.cache.set(id, note);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a user from Discord, or the user cache if it's already available.
|
||||
* @param {UserResolvable} user The user to fetch
|
||||
* @param {BaseFetchOptions} [options] Additional options for this fetch
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async fetch(user, { cache = true, force = false } = {}) {
|
||||
const id = this.resolveId(user);
|
||||
if (!force) {
|
||||
const existing = this.cache.get(id);
|
||||
if (existing) return existing;
|
||||
}
|
||||
const data = await this.client.api.users['@me'].notes[id]
|
||||
.get()
|
||||
.then(d => d.note)
|
||||
.catch(() => '');
|
||||
if (cache) this.cache.set(id, data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserNoteManager;
|
@ -1,13 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const Buffer = require('node:buffer').Buffer;
|
||||
const http = require('node:http');
|
||||
const https = require('node:https');
|
||||
const { setTimeout } = require('node:timers');
|
||||
const makeFetchCookie = require('fetch-cookie');
|
||||
const FormData = require('form-data');
|
||||
const fetchOriginal = require('node-fetch');
|
||||
const { CookieJar } = require('tough-cookie');
|
||||
const { getProxyObject } = require('../util/Util');
|
||||
const { UserAgent } = require('../util/Constants');
|
||||
|
||||
const cookieJar = new CookieJar();
|
||||
const fetch = makeFetchCookie(fetchOriginal, cookieJar);
|
||||
@ -23,6 +24,9 @@ class APIRequest {
|
||||
this.options = options;
|
||||
this.retries = 0;
|
||||
|
||||
const { userAgentSuffix } = this.client.options;
|
||||
this.fullUserAgent = `${UserAgent}${userAgentSuffix.length ? `, ${userAgentSuffix.join(', ')}` : ''}`;
|
||||
|
||||
let queryString = '';
|
||||
if (options.query) {
|
||||
const query = Object.entries(options.query)
|
||||
@ -33,13 +37,11 @@ class APIRequest {
|
||||
this.path = `${path}${queryString && `?${queryString}`}`;
|
||||
}
|
||||
|
||||
make(captchaKey = undefined, captchaRqtoken = undefined) {
|
||||
if (agent === null) {
|
||||
if (typeof this.client.options.proxy === 'string' && this.client.options.proxy.length > 0) {
|
||||
agent = getProxyObject(this.client.options.proxy);
|
||||
} else if (this.client.options.http.agent instanceof https.Agent) {
|
||||
make(captchaKey, captchaRqToken) {
|
||||
if (!agent) {
|
||||
if (this.client.options.http.agent instanceof http.Agent) {
|
||||
this.client.options.http.agent.keepAlive = true;
|
||||
agent = this.client.options.http.agent;
|
||||
agent.keepAlive = true;
|
||||
} else {
|
||||
agent = new https.Agent({ ...this.client.options.http.agent, keepAlive: true });
|
||||
}
|
||||
@ -56,7 +58,7 @@ class APIRequest {
|
||||
authority: 'discord.com',
|
||||
accept: '*/*',
|
||||
'accept-language': 'en-US',
|
||||
'sec-ch-ua': `"Not?A_Brand";v="8", "Chromium";v="108"`,
|
||||
'sec-ch-ua': '"Not?A_Brand";v="8", "Chromium";v="108"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
@ -65,18 +67,19 @@ class APIRequest {
|
||||
'x-debug-options': 'bugReporterEnabled',
|
||||
'x-discord-locale': 'en-US',
|
||||
'x-discord-timezone': 'Asia/Saigon',
|
||||
'x-super-properties': `${Buffer.from(
|
||||
this.client.options.jsonTransformer(this.client.options.ws.properties),
|
||||
'ascii',
|
||||
).toString('base64')}`,
|
||||
'x-super-properties': `${Buffer.from(JSON.stringify(this.client.options.ws.properties), 'ascii').toString(
|
||||
'base64',
|
||||
)}`,
|
||||
Referer: 'https://discord.com/channels/@me',
|
||||
origin: 'https://discord.com',
|
||||
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
||||
'User-Agent': this.fullUserAgent,
|
||||
};
|
||||
|
||||
if (this.options.auth !== false) headers.Authorization = this.rest.getAuth();
|
||||
if (this.options.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(this.options.reason);
|
||||
if (this.options.headers) headers = Object.assign(headers, this.options.headers);
|
||||
|
||||
// Delete all headers if undefined
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
if (value === undefined) delete headers[key];
|
||||
@ -86,9 +89,12 @@ class APIRequest {
|
||||
'User-Agent': this.client.options.http.headers['User-Agent'],
|
||||
};
|
||||
}
|
||||
if (captchaKey && typeof captchaKey == 'string') {
|
||||
headers['x-captcha-key'] = captchaKey;
|
||||
if (captchaRqtoken) headers['x-captcha-rqtoken'] = captchaRqtoken;
|
||||
|
||||
// Some options
|
||||
if (this.options.DiscordContext) {
|
||||
headers['X-Context-Properties'] = Buffer.from(JSON.stringify(this.options.DiscordContext), 'utf8').toString(
|
||||
'base64',
|
||||
);
|
||||
}
|
||||
|
||||
let body;
|
||||
@ -107,19 +113,21 @@ class APIRequest {
|
||||
headers = Object.assign(headers, body.getHeaders());
|
||||
// eslint-disable-next-line eqeqeq
|
||||
} else if (this.options.data != null) {
|
||||
if (this.options.useFormDataPayloadJSON) {
|
||||
if (this.options.usePayloadJSON) {
|
||||
body = new FormData();
|
||||
body.append('payload_json', JSON.stringify(this.options.data));
|
||||
headers = Object.assign(headers, body.getHeaders());
|
||||
} else {
|
||||
body = JSON.stringify(this.options.data);
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
}
|
||||
|
||||
// Captcha
|
||||
if (captchaKey && typeof captchaKey == 'string') headers['X-Captcha-Key'] = captchaKey;
|
||||
if (captchaRqToken && typeof captchaRqToken == 'string') headers['X-Captcha-Rqtoken'] = captchaRqToken;
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), this.client.options.restRequestTimeout).unref();
|
||||
|
||||
return fetch(url, {
|
||||
method: this.method,
|
||||
headers,
|
||||
|
@ -1,139 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const proxyParser = proxy => {
|
||||
const protocolSplit = proxy.split('://');
|
||||
const protocol = protocolSplit.length === 1 ? null : protocolSplit[0];
|
||||
const rest = protocolSplit.length === 1 ? protocolSplit[0] : protocolSplit[1];
|
||||
const authSplit = rest.split('@');
|
||||
if (authSplit.length === 1) {
|
||||
const host = authSplit[0].split(':')[0];
|
||||
const port = Number(authSplit[0].split(':')[1]);
|
||||
const proxyConfig = {
|
||||
host,
|
||||
port,
|
||||
};
|
||||
if (protocol != null) {
|
||||
proxyConfig.protocol = protocol;
|
||||
}
|
||||
return proxyConfig;
|
||||
}
|
||||
const host = authSplit[1].split(':')[0];
|
||||
const port = Number(authSplit[1].split(':')[1]);
|
||||
const [username, password] = authSplit[0].split(':');
|
||||
const proxyConfig = {
|
||||
host,
|
||||
port,
|
||||
auth: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
};
|
||||
if (protocol != null) {
|
||||
proxyConfig.protocol = protocol;
|
||||
}
|
||||
return proxyConfig;
|
||||
};
|
||||
|
||||
module.exports = class CaptchaSolver {
|
||||
constructor(service, key, defaultCaptchaSolver, proxyString = '') {
|
||||
this.service = 'custom';
|
||||
this.solver = undefined;
|
||||
this.defaultCaptchaSolver = defaultCaptchaSolver;
|
||||
this.key = null;
|
||||
this.proxy = proxyString.length ? proxyParser(proxyString) : null;
|
||||
this._setup(service, key);
|
||||
}
|
||||
_missingModule(name) {
|
||||
return new Error(`${name} module not found, please install it with \`npm i ${name}\``);
|
||||
}
|
||||
_setup(service, key) {
|
||||
switch (service) {
|
||||
case '2captcha': {
|
||||
if (!key || typeof key !== 'string') throw new Error('2captcha key is not provided');
|
||||
try {
|
||||
const lib = require('2captcha');
|
||||
this.service = '2captcha';
|
||||
this.key = key;
|
||||
this.solver = new lib.Solver(key);
|
||||
this.solve = (data, userAgent) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const siteKey = data.captcha_sitekey;
|
||||
let postD = {
|
||||
invisible: 1,
|
||||
userAgent,
|
||||
};
|
||||
if (this.proxy !== null) {
|
||||
postD = {
|
||||
...postD,
|
||||
proxytype: this.proxy.protocol?.toUpperCase(),
|
||||
proxy: `${'auth' in this.proxy ? `${this.proxy.auth.username}:${this.proxy.auth.password}@` : ''}${
|
||||
this.proxy.host
|
||||
}:${this.proxy.port}`,
|
||||
};
|
||||
}
|
||||
if (data.captcha_rqdata) {
|
||||
postD = {
|
||||
...postD,
|
||||
data: data.captcha_rqdata,
|
||||
};
|
||||
}
|
||||
this.solver
|
||||
.hcaptcha(siteKey, 'discord.com', postD)
|
||||
.then(res => {
|
||||
if (typeof res.data == 'string') {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
reject(new Error('Unknown Response'));
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
break;
|
||||
} catch (e) {
|
||||
throw this._missingModule('2captcha');
|
||||
}
|
||||
}
|
||||
case 'capmonster': {
|
||||
if (!key || typeof key !== 'string') throw new Error('Capmonster key is not provided');
|
||||
try {
|
||||
const { HCaptchaTask } = require('node-capmonster');
|
||||
this.service = 'capmonster';
|
||||
this.key = key;
|
||||
const client = new HCaptchaTask(this.key);
|
||||
this.solve = (captchaData, userAgent) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (userAgent) client.setUserAgent(userAgent);
|
||||
if (this.proxy !== null) {
|
||||
client.setGlobalProxy(
|
||||
this.proxy.protocol,
|
||||
this.proxy.host,
|
||||
this.proxy.port,
|
||||
'auth' in this.proxy ? this.proxy.auth.username : undefined,
|
||||
'auth' in this.proxy ? this.proxy.auth.password : undefined,
|
||||
);
|
||||
}
|
||||
client
|
||||
.createWithTask(
|
||||
client.task({
|
||||
websiteURL: 'https://discord.com/channels/@me',
|
||||
websiteKey: captchaData.captcha_sitekey,
|
||||
isInvisible: !!captchaData.captcha_rqdata,
|
||||
data: captchaData.captcha_rqdata,
|
||||
}),
|
||||
)
|
||||
.then(id => client.joinTaskResult(id))
|
||||
.then(result => resolve(result.gRecaptchaResponse))
|
||||
.catch(reject);
|
||||
});
|
||||
} catch (e) {
|
||||
throw this._missingModule('node-capmonster');
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this.solve = this.defaultCaptchaSolver;
|
||||
}
|
||||
}
|
||||
}
|
||||
solve() {}
|
||||
};
|
@ -25,7 +25,7 @@ class DiscordAPIError extends Error {
|
||||
|
||||
/**
|
||||
* HTTP error code returned by Discord
|
||||
* @type {number | string}
|
||||
* @type {number}
|
||||
*/
|
||||
this.code = error.code;
|
||||
|
||||
@ -35,6 +35,22 @@ class DiscordAPIError extends Error {
|
||||
*/
|
||||
this.httpStatus = status;
|
||||
|
||||
/**
|
||||
* The data associated with the request that caused this error
|
||||
* @type {HTTPErrorData}
|
||||
*/
|
||||
this.requestData = {
|
||||
json: request.options.data,
|
||||
files: request.options.files ?? [],
|
||||
headers: request.options.headers,
|
||||
};
|
||||
|
||||
/**
|
||||
* The number of times this request has been retried
|
||||
* @type {number}
|
||||
*/
|
||||
this.retries = request.retries;
|
||||
|
||||
/**
|
||||
* @typedef {Object} Captcha
|
||||
* @property {Array<string>} captcha_key ['message']
|
||||
@ -49,21 +65,6 @@ class DiscordAPIError extends Error {
|
||||
* @type {Captcha | null}
|
||||
*/
|
||||
this.captcha = error?.captcha_service ? error : null;
|
||||
|
||||
/**
|
||||
* The data associated with the request that caused this error
|
||||
* @type {HTTPErrorData}
|
||||
*/
|
||||
this.requestData = {
|
||||
json: request.options.data,
|
||||
files: request.options.files ?? [],
|
||||
};
|
||||
|
||||
/**
|
||||
* The number of times this request has been retried
|
||||
* @type {number}
|
||||
*/
|
||||
this.retries = request.retries;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,7 +4,6 @@ const { setInterval } = require('node:timers');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const APIRequest = require('./APIRequest');
|
||||
const routeBuilder = require('./APIRouter');
|
||||
const CaptchaSolver = require('./CaptchaSolver');
|
||||
const RequestHandler = require('./RequestHandler');
|
||||
const { Error } = require('../errors');
|
||||
const { Endpoints } = require('../util/Constants');
|
||||
@ -23,17 +22,6 @@ class RESTManager {
|
||||
this.handlers.sweep(handler => handler._inactive);
|
||||
}, client.options.restSweepInterval * 1_000).unref();
|
||||
}
|
||||
this.captchaService = null;
|
||||
this.setup();
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.captchaService = new CaptchaSolver(
|
||||
this.client.options.captchaService,
|
||||
this.client.options.captchaKey,
|
||||
this.client.options.captchaSolver,
|
||||
this.client.options.captchaWithProxy ? this.client.options.proxy : '',
|
||||
);
|
||||
}
|
||||
|
||||
get api() {
|
||||
@ -41,16 +29,8 @@ class RESTManager {
|
||||
}
|
||||
|
||||
getAuth() {
|
||||
if ((this.client.token && this.client.user && this.client.user.bot) || this.client.accessToken) {
|
||||
return `Bot ${this.client.token}`;
|
||||
} else if (this.client.token) {
|
||||
return this.client.token;
|
||||
}
|
||||
/*
|
||||
// v13.7
|
||||
const token = this.client.token ?? this.client.accessToken;
|
||||
if (token) return `Bot ${token}`;
|
||||
*/
|
||||
if (token) return token?.replace(/Bot /g, '');
|
||||
throw new Error('TOKEN_MISSING');
|
||||
}
|
||||
|
||||
|
@ -2,13 +2,12 @@
|
||||
|
||||
const { setTimeout } = require('node:timers');
|
||||
const { setTimeout: sleep } = require('node:timers/promises');
|
||||
const { inspect } = require('util');
|
||||
const { AsyncQueue } = require('@sapphire/async-queue');
|
||||
const DiscordAPIError = require('./DiscordAPIError');
|
||||
const HTTPError = require('./HTTPError');
|
||||
const RateLimitError = require('./RateLimitError');
|
||||
const {
|
||||
Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE, API_REQUEST, CAPTCHA_REQUIRED },
|
||||
Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE, API_REQUEST },
|
||||
} = require('../util/Constants');
|
||||
|
||||
const captchaMessage = [
|
||||
@ -19,11 +18,12 @@ const captchaMessage = [
|
||||
'invalid-response',
|
||||
'You need to update your app',
|
||||
'response-already-used-error',
|
||||
'rqkey-mismatch',
|
||||
];
|
||||
|
||||
function parseResponse(res) {
|
||||
if (res.headers.get('content-type')?.startsWith('application/json')) return res.json();
|
||||
return res.arrayBuffer(); // Cre: TheDevYellowy
|
||||
return res.arrayBuffer();
|
||||
}
|
||||
|
||||
function getAPIOffset(serverDate) {
|
||||
@ -354,18 +354,9 @@ class RequestHandler {
|
||||
let data;
|
||||
try {
|
||||
data = await parseResponse(res);
|
||||
if (data?.captcha_service) {
|
||||
/**
|
||||
* Emitted when a request is blocked by a captcha
|
||||
* @event Client#captchaRequired
|
||||
* @param {Request} request The request that was blocked
|
||||
* @param {Captcha} data The data returned by Discord
|
||||
*/
|
||||
this.manager.client.emit(CAPTCHA_REQUIRED, request, data);
|
||||
}
|
||||
if (
|
||||
data?.captcha_service &&
|
||||
this.manager.client.options.captchaService &&
|
||||
typeof this.manager.client.options.captchaSolver == 'function' &&
|
||||
request.retries < this.manager.client.options.captchaRetryLimit &&
|
||||
captchaMessage.some(s => data.captcha_key[0].includes(s))
|
||||
) {
|
||||
@ -376,20 +367,17 @@ class RequestHandler {
|
||||
Method : ${request.method}
|
||||
Path : ${request.path}
|
||||
Route : ${request.route}
|
||||
Info : ${inspect(data, { depth: null })}`,
|
||||
Sitekey : ${data.captcha_sitekey}
|
||||
rqToken : ${data.captcha_rqtoken}`,
|
||||
);
|
||||
const captcha = await this.manager.captchaService.solve(
|
||||
data,
|
||||
this.manager.client.options.http.headers['User-Agent'],
|
||||
);
|
||||
// Sleep: await this.manager.client.sleep(5_000);
|
||||
const captcha = await this.manager.client.options.captchaSolver(data, request.fullUserAgent);
|
||||
this.manager.client.emit(
|
||||
DEBUG,
|
||||
`Captcha details:
|
||||
Method : ${request.method}
|
||||
Path : ${request.path}
|
||||
Route : ${request.route}
|
||||
Key : ${captcha ? `${captcha.slice(0, 30)}...` : '[Captcha not solved]'}
|
||||
Key : ${captcha ? `${captcha.slice(0, 120)}...` : '[Captcha not solved]'}
|
||||
rqToken : ${data.captcha_rqtoken}`,
|
||||
);
|
||||
request.retries++;
|
||||
@ -398,6 +386,7 @@ class RequestHandler {
|
||||
} catch (err) {
|
||||
throw new HTTPError(err.message, err.constructor.name, err.status, request);
|
||||
}
|
||||
|
||||
throw new DiscordAPIError(data, res.status, request);
|
||||
}
|
||||
|
||||
|
@ -1,28 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const { setTimeout } = require('node:timers');
|
||||
const { findBestMatch } = require('string-similarity');
|
||||
const Base = require('./Base');
|
||||
const MessagePayload = require('./MessagePayload');
|
||||
const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager');
|
||||
const {
|
||||
ApplicationCommandOptionTypes,
|
||||
ApplicationCommandTypes,
|
||||
ChannelTypes,
|
||||
Events,
|
||||
InteractionTypes,
|
||||
} = require('../util/Constants');
|
||||
const { ApplicationCommandOptionTypes, ApplicationCommandTypes, ChannelTypes } = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
const { lazy, getAttachments, uploadFile } = require('../util/Util');
|
||||
const Message = lazy(() => require('../structures/Message').Message);
|
||||
|
||||
/**
|
||||
* Represents an application command.
|
||||
* @extends {Base}
|
||||
*/
|
||||
class ApplicationCommand extends Base {
|
||||
constructor(client, data) {
|
||||
constructor(client, data, guild, guildId) {
|
||||
super(client);
|
||||
|
||||
/**
|
||||
@ -37,11 +26,24 @@ class ApplicationCommand extends Base {
|
||||
*/
|
||||
this.applicationId = data.application_id;
|
||||
|
||||
/**
|
||||
* The guild this command is part of
|
||||
* @type {?Guild}
|
||||
*/
|
||||
this.guild = guild ?? null;
|
||||
|
||||
/**
|
||||
* The guild's id this command is part of, this may be non-null when `guild` is `null` if the command
|
||||
* was fetched from the `ApplicationCommandManager`
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.guildId = guild?.id ?? guildId ?? null;
|
||||
|
||||
/**
|
||||
* The manager for permissions of this command on its guild or arbitrary guilds when the command is global
|
||||
* @type {ApplicationCommandPermissionsManager}
|
||||
*/
|
||||
this.permissions = new ApplicationCommandPermissionsManager(this, this.applicationId);
|
||||
this.permissions = new ApplicationCommandPermissionsManager(this);
|
||||
|
||||
/**
|
||||
* The type of this application command
|
||||
@ -49,30 +51,10 @@ class ApplicationCommand extends Base {
|
||||
*/
|
||||
this.type = ApplicationCommandTypes[data.type];
|
||||
|
||||
this.user = client.users.cache.get(this.applicationId);
|
||||
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* The guild this command is part of
|
||||
* @type {?Guild}
|
||||
* @readonly
|
||||
*/
|
||||
get guild() {
|
||||
return this.client.guilds.resolve(this.guildId);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
if ('guild_id' in data) {
|
||||
/**
|
||||
* The guild's id this command is part of, this may be non-null when `guild` is `null` if the command
|
||||
* was fetched from the `ApplicationCommandManager`
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.guildId = data.guild_id ?? null;
|
||||
}
|
||||
|
||||
if ('name' in data) {
|
||||
/**
|
||||
* The name of this command
|
||||
@ -148,7 +130,6 @@ class ApplicationCommand extends Base {
|
||||
*/
|
||||
this.defaultPermission = data.default_permission;
|
||||
}
|
||||
|
||||
/* eslint-disable max-len */
|
||||
|
||||
if ('default_member_permissions' in data) {
|
||||
@ -336,7 +317,6 @@ class ApplicationCommand extends Base {
|
||||
setDefaultPermission(defaultPermission = true) {
|
||||
return this.edit({ defaultPermission });
|
||||
}
|
||||
|
||||
/* eslint-enable max-len */
|
||||
|
||||
/**
|
||||
@ -391,6 +371,7 @@ class ApplicationCommand extends Base {
|
||||
equals(command, enforceOptionOrder = false) {
|
||||
// If given an id, check if the id matches
|
||||
if (command.id && this.id !== command.id) return false;
|
||||
|
||||
let defaultMemberPermissions = null;
|
||||
let dmPermission = command.dmPermission ?? command.dm_permission;
|
||||
|
||||
@ -404,6 +385,7 @@ class ApplicationCommand extends Base {
|
||||
defaultMemberPermissions =
|
||||
command.defaultMemberPermissions !== null ? new Permissions(command.defaultMemberPermissions).bitfield : null;
|
||||
}
|
||||
|
||||
// Check top level parameters
|
||||
const commandType = typeof command.type === 'string' ? command.type : ApplicationCommandTypes[command.type];
|
||||
if (
|
||||
@ -446,9 +428,7 @@ class ApplicationCommand extends Base {
|
||||
const newOptions = new Map(options.map(option => [option.name, option]));
|
||||
for (const option of existing) {
|
||||
const foundOption = newOptions.get(option.name);
|
||||
if (!foundOption || !this._optionEquals(option, foundOption)) {
|
||||
return false;
|
||||
}
|
||||
if (!foundOption || !this._optionEquals(option, foundOption)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -597,423 +577,6 @@ class ApplicationCommand extends Base {
|
||||
[maxLengthKey]: option.maxLength ?? option.max_length,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Send Slash command to channel
|
||||
* @param {Message} message Discord Message
|
||||
* @param {Array<string>} subCommandArray SubCommand Array
|
||||
* @param {Array<any>} options The options to Slash Command
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
// eslint-disable-next-line consistent-return
|
||||
async sendSlashCommand(message, subCommandArray = [], options = []) {
|
||||
// Todo: Refactor [Done]
|
||||
const buildError = (type, value, array, msg) =>
|
||||
new Error(`Invalid ${type}: ${value} ${msg}\nList of ${type}:\n${array}`);
|
||||
// Check Options
|
||||
if (!(message instanceof Message())) {
|
||||
throw new TypeError('The message must be a Discord.Message');
|
||||
}
|
||||
if (!Array.isArray(options)) {
|
||||
throw new TypeError('The options must be an array of strings');
|
||||
}
|
||||
if (this.type !== 'CHAT_INPUT') throw new Error('This command is not a chat input [/]');
|
||||
const optionFormat = [];
|
||||
const attachments = [];
|
||||
const attachmentsBuffer = [];
|
||||
const parseChoices = (list_choices, value) => {
|
||||
if (value !== undefined) {
|
||||
if (Array.isArray(list_choices) && list_choices.length) {
|
||||
const choice = list_choices.find(c => c.name === value) || list_choices.find(c => c.value === value);
|
||||
if (choice) {
|
||||
return choice.value;
|
||||
}
|
||||
throw buildError(
|
||||
'choice',
|
||||
value,
|
||||
list_choices.map((c, i) => ` #${i + 1} Name: ${c.name} Value: ${c.value}`).join('\n'),
|
||||
'is not a valid choice for this option',
|
||||
);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const parseOption = async (optionCommand, value) => {
|
||||
const data = {
|
||||
type: ApplicationCommandOptionTypes[optionCommand.type],
|
||||
name: optionCommand.name,
|
||||
};
|
||||
if (value !== undefined) {
|
||||
value = parseChoices(optionCommand.choices, value);
|
||||
switch (optionCommand.type) {
|
||||
case 'BOOLEAN': {
|
||||
data.value = Boolean(value);
|
||||
break;
|
||||
}
|
||||
case 'INTEGER': {
|
||||
data.value = Number(value);
|
||||
break;
|
||||
}
|
||||
case 'ATTACHMENT': {
|
||||
data.value = await addDataFromAttachment(value, this.client);
|
||||
break;
|
||||
}
|
||||
case 'SUB_COMMAND_GROUP': {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (optionCommand.autocomplete) {
|
||||
let optionsBuild;
|
||||
switch (subCommandArray.length) {
|
||||
case 0: {
|
||||
optionsBuild = [
|
||||
...optionFormat,
|
||||
{
|
||||
type: ApplicationCommandOptionTypes[optionCommand.type],
|
||||
name: optionCommand.name,
|
||||
value,
|
||||
focused: true,
|
||||
},
|
||||
];
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
const subCommand = this.options.find(o => o.name == subCommandArray[0] && o.type == 'SUB_COMMAND');
|
||||
optionsBuild = [
|
||||
{
|
||||
type: ApplicationCommandOptionTypes[subCommand.type],
|
||||
name: subCommand.name,
|
||||
options: [
|
||||
...optionFormat,
|
||||
{
|
||||
type: ApplicationCommandOptionTypes[optionCommand.type],
|
||||
name: optionCommand.name,
|
||||
value,
|
||||
focused: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
const subGroup = this.options.find(
|
||||
o => o.name == subCommandArray[0] && o.type == 'SUB_COMMAND_GROUP',
|
||||
);
|
||||
const subCommand = subGroup.options.find(
|
||||
o => o.name == subCommandArray[1] && o.type == 'SUB_COMMAND',
|
||||
);
|
||||
optionsBuild = [
|
||||
{
|
||||
type: ApplicationCommandOptionTypes[subGroup.type],
|
||||
name: subGroup.name,
|
||||
options: [
|
||||
{
|
||||
type: ApplicationCommandOptionTypes[subCommand.type],
|
||||
name: subCommand.name,
|
||||
options: [
|
||||
...optionFormat,
|
||||
{
|
||||
type: ApplicationCommandOptionTypes[optionCommand.type],
|
||||
name: optionCommand.name,
|
||||
value,
|
||||
focused: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
const autoValue = await getAutoResponse(optionsBuild, value);
|
||||
data.value = autoValue;
|
||||
} else {
|
||||
data.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
optionFormat.push(data);
|
||||
}
|
||||
return optionFormat;
|
||||
};
|
||||
const parseSubCommand = async (subCommandName, options, subGroup) => {
|
||||
const options_sub = subGroup ? subGroup.options : this.options;
|
||||
const subCommand = options_sub.find(
|
||||
o => (o.name == subCommandName || o.nameLocalized == subCommandName) && o.type == 'SUB_COMMAND',
|
||||
);
|
||||
if (!subCommand) {
|
||||
throw buildError(
|
||||
'SubCommand',
|
||||
subCommandName,
|
||||
options_sub.map((o, i) => ` #${i + 1} Name: ${o.name}`).join('\n'),
|
||||
'is not a valid sub command',
|
||||
);
|
||||
}
|
||||
const valueRequired = subCommand.options?.filter(o => o.required).length || 0;
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const optionInput = subCommand.options[i];
|
||||
const value = options[i];
|
||||
await parseOption(optionInput, value);
|
||||
}
|
||||
if (valueRequired > options.length) {
|
||||
throw new Error(`Value required missing\nDebug:
|
||||
Required: ${valueRequired} - Options: ${optionFormat.length}`);
|
||||
}
|
||||
return {
|
||||
type: ApplicationCommandOptionTypes[subCommand.type],
|
||||
name: subCommand.name,
|
||||
options: optionFormat,
|
||||
};
|
||||
};
|
||||
const parseSubGroupCommand = async (subGroupName, subName) => {
|
||||
const subGroup = this.options.find(
|
||||
o => (o.name == subGroupName || o.nameLocalized == subGroupName) && o.type == 'SUB_COMMAND_GROUP',
|
||||
);
|
||||
if (!subGroup) {
|
||||
throw buildError(
|
||||
'SubGroupCommand',
|
||||
subGroupName,
|
||||
this.options.map((o, i) => ` #${i + 1} Name: ${o.name}`).join('\n'),
|
||||
'is not a valid sub group command',
|
||||
);
|
||||
}
|
||||
const data = await parseSubCommand(subName, options, subGroup);
|
||||
return {
|
||||
type: ApplicationCommandOptionTypes[subGroup.type],
|
||||
name: subGroup.name,
|
||||
options: [data],
|
||||
};
|
||||
};
|
||||
async function addDataFromAttachment(data, client) {
|
||||
const data_ = await MessagePayload.resolveFile(data);
|
||||
if (!data_.file) {
|
||||
throw new TypeError(
|
||||
'The attachment data must be a BufferResolvable or Stream or FileOptions of MessageAttachment',
|
||||
);
|
||||
}
|
||||
if (client.options.usingNewAttachmentAPI === true) {
|
||||
const attachments_ = await getAttachments(client, message.channelId, data_);
|
||||
await uploadFile(data_.file, attachments_[0].upload_url);
|
||||
const id = attachments.length;
|
||||
attachments.push({
|
||||
id: id,
|
||||
filename: data_.name,
|
||||
uploaded_filename: attachments_[0].upload_filename,
|
||||
});
|
||||
return id;
|
||||
} else {
|
||||
const id = attachments.length;
|
||||
attachments.push({
|
||||
id: id,
|
||||
filename: data_.name,
|
||||
});
|
||||
attachmentsBuffer.push(data_);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
const getDataPost = (dataAdd = [], nonce, autocomplete = false) => {
|
||||
if (!Array.isArray(dataAdd) && typeof dataAdd == 'object') {
|
||||
dataAdd = [dataAdd];
|
||||
}
|
||||
const data = {
|
||||
type: autocomplete ? InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE : InteractionTypes.APPLICATION_COMMAND,
|
||||
application_id: this.applicationId,
|
||||
guild_id: message.guildId,
|
||||
channel_id: message.channelId,
|
||||
session_id: this.client.sessionId,
|
||||
data: {
|
||||
version: this.version,
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
type: ApplicationCommandTypes[this.type],
|
||||
options: dataAdd,
|
||||
attachments: attachments,
|
||||
},
|
||||
nonce,
|
||||
};
|
||||
if (this.guildId) {
|
||||
data.data.guild_id = message.guildId;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
const getAutoResponse = async (sendData, value) => {
|
||||
let nonce = SnowflakeUtil.generate();
|
||||
const data = getDataPost(sendData, nonce, true);
|
||||
await this.client.api.interactions.post({
|
||||
data,
|
||||
files: attachmentsBuffer,
|
||||
});
|
||||
return new Promise(resolve => {
|
||||
const handler = data => {
|
||||
timeout.refresh();
|
||||
if (data.nonce !== nonce) return;
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
if (data.choices.length > 1) {
|
||||
// Find best match name
|
||||
const bestMatch = findBestMatch(
|
||||
value,
|
||||
data.choices.map(c => c.name),
|
||||
);
|
||||
const result = data.choices.find(c => c.name == bestMatch.bestMatch.target);
|
||||
resolve(result.value);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
resolve(value);
|
||||
}, this.client.options.interactionTimeout).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
|
||||
});
|
||||
};
|
||||
const sendData = async (optionsData = []) => {
|
||||
let nonce = SnowflakeUtil.generate();
|
||||
const data = getDataPost(optionsData, nonce);
|
||||
await this.client.api.interactions.post({
|
||||
data,
|
||||
useFormDataPayloadJSON: true,
|
||||
files: attachmentsBuffer,
|
||||
});
|
||||
this.client._interactionCache.set(nonce, {
|
||||
channelId: message.channelId,
|
||||
guildId: message.guildId,
|
||||
metadata: data,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = data => {
|
||||
timeout.refresh();
|
||||
if (data.metadata?.nonce !== nonce) return;
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener('interactionResponse', handler);
|
||||
this.client.decrementMaxListeners();
|
||||
if (data.status) {
|
||||
resolve(data.metadata);
|
||||
} else {
|
||||
reject(
|
||||
new Error('INTERACTION_ERROR', {
|
||||
cause: data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener('interactionResponse', handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(
|
||||
new Error('INTERACTION_TIMEOUT', {
|
||||
cause: data,
|
||||
}),
|
||||
);
|
||||
}, this.client.options.interactionTimeout).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on('interactionResponse', handler);
|
||||
});
|
||||
};
|
||||
// SubCommandArray length max 2
|
||||
// length = 0 => no sub command
|
||||
// length = 1 => sub command
|
||||
// length = 2 => sub command group + sub command
|
||||
switch (subCommandArray.length) {
|
||||
case 0: {
|
||||
const valueRequired = this.options?.filter(o => o.required).length || 0;
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const optionInput = this.options[i];
|
||||
const value = options[i];
|
||||
await parseOption(optionInput, value);
|
||||
}
|
||||
if (valueRequired > options.length) {
|
||||
throw new Error(`Value required missing\nDebug:
|
||||
Required: ${valueRequired} - Options: ${optionFormat.length}`);
|
||||
}
|
||||
return sendData(optionFormat);
|
||||
}
|
||||
case 1: {
|
||||
const optionsData = await parseSubCommand(subCommandArray[0], options);
|
||||
return sendData(optionsData);
|
||||
}
|
||||
case 2: {
|
||||
const optionsData = await parseSubGroupCommand(subCommandArray[0], subCommandArray[1], options);
|
||||
return sendData(optionsData);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Message Context Menu
|
||||
* @param {Message} message Discord Message
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
async sendContextMenu(message) {
|
||||
if (!(message instanceof Message())) {
|
||||
throw new TypeError('The message must be a Discord.Message');
|
||||
}
|
||||
if (this.type == 'CHAT_INPUT') return false;
|
||||
const nonce = SnowflakeUtil.generate();
|
||||
const data = {
|
||||
type: InteractionTypes.APPLICATION_COMMAND,
|
||||
application_id: this.applicationId,
|
||||
guild_id: message.guildId,
|
||||
channel_id: message.channelId,
|
||||
session_id: this.client.sessionId,
|
||||
data: {
|
||||
version: this.version,
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
type: ApplicationCommandTypes[this.type],
|
||||
target_id: ApplicationCommandTypes[this.type] == 1 ? message.author.id : message.id,
|
||||
},
|
||||
nonce,
|
||||
};
|
||||
if (this.guildId) {
|
||||
data.data.guild_id = message.guildId;
|
||||
}
|
||||
await this.client.api.interactions.post({
|
||||
data,
|
||||
useFormDataPayloadJSON: true,
|
||||
});
|
||||
this.client._interactionCache.set(nonce, {
|
||||
channelId: message.channelId,
|
||||
guildId: message.guildId,
|
||||
metadata: data,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = data => {
|
||||
timeout.refresh();
|
||||
if (data.metadata?.nonce !== nonce) return;
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener('interactionResponse', handler);
|
||||
this.client.decrementMaxListeners();
|
||||
if (data.status) {
|
||||
resolve(data.metadata);
|
||||
} else {
|
||||
reject(
|
||||
new Error('INTERACTION_ERROR', {
|
||||
cause: data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener('interactionResponse', handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(
|
||||
new Error('INTERACTION_TIMEOUT', {
|
||||
cause: data,
|
||||
}),
|
||||
);
|
||||
}, this.client.options.interactionTimeout).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on('interactionResponse', handler);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ApplicationCommand;
|
||||
|
@ -2,6 +2,9 @@
|
||||
|
||||
const { ApplicationRoleConnectionMetadataTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Role connection metadata object for an application.
|
||||
*/
|
||||
class ApplicationRoleConnectionMetadata {
|
||||
constructor(data) {
|
||||
/**
|
||||
|
@ -194,7 +194,7 @@ class AutoModerationRule extends Base {
|
||||
* @returns {Promise<AutoModerationRule>}
|
||||
*/
|
||||
setKeywordFilter(keywordFilter, reason) {
|
||||
return this.edit({ triggerMetadata: { keywordFilter }, reason });
|
||||
return this.edit({ triggerMetadata: { ...this.triggerMetadata, keywordFilter }, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -205,7 +205,7 @@ class AutoModerationRule extends Base {
|
||||
* @returns {Promise<AutoModerationRule>}
|
||||
*/
|
||||
setRegexPatterns(regexPatterns, reason) {
|
||||
return this.edit({ triggerMetadata: { regexPatterns }, reason });
|
||||
return this.edit({ triggerMetadata: { ...this.triggerMetadata, regexPatterns }, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,7 +215,7 @@ class AutoModerationRule extends Base {
|
||||
* @returns {Promise<AutoModerationRule>}
|
||||
*/
|
||||
setPresets(presets, reason) {
|
||||
return this.edit({ triggerMetadata: { presets }, reason });
|
||||
return this.edit({ triggerMetadata: { ...this.triggerMetadata, presets }, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,7 +225,7 @@ class AutoModerationRule extends Base {
|
||||
* @returns {Promise<AutoModerationRule>}
|
||||
*/
|
||||
setAllowList(allowList, reason) {
|
||||
return this.edit({ triggerMetadata: { allowList }, reason });
|
||||
return this.edit({ triggerMetadata: { ...this.triggerMetadata, allowList }, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -235,7 +235,7 @@ class AutoModerationRule extends Base {
|
||||
* @returns {Promise<AutoModerationRule>}
|
||||
*/
|
||||
setMentionTotalLimit(mentionTotalLimit, reason) {
|
||||
return this.edit({ triggerMetadata: { mentionTotalLimit }, reason });
|
||||
return this.edit({ triggerMetadata: { ...this.triggerMetadata, mentionTotalLimit }, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver');
|
||||
const Interaction = require('./Interaction');
|
||||
const { Error } = require('../errors');
|
||||
const { InteractionResponseTypes, ApplicationCommandOptionTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,6 @@
|
||||
const GuildChannel = require('./GuildChannel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const GuildTextThreadManager = require('../managers/GuildTextThreadManager');
|
||||
const InteractionManager = require('../managers/InteractionManager');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
|
||||
/**
|
||||
@ -21,12 +20,6 @@ class BaseGuildTextChannel extends GuildChannel {
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the interactions sent to this channel
|
||||
* @type {InteractionManager}
|
||||
*/
|
||||
this.interactions = new InteractionManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the threads belonging to this channel
|
||||
* @type {GuildTextThreadManager}
|
||||
@ -187,15 +180,10 @@ class BaseGuildTextChannel extends GuildChannel {
|
||||
sendTyping() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
createMessageComponentCollector() {}
|
||||
awaitMessageComponent() {}
|
||||
bulkDelete() {}
|
||||
fetchWebhooks() {}
|
||||
createWebhook() {}
|
||||
setRateLimitPerUser() {}
|
||||
setNSFW() {}
|
||||
sendSlash() {}
|
||||
searchInteraction() {}
|
||||
}
|
||||
|
||||
TextBasedChannel.applyToClass(BaseGuildTextChannel, true);
|
||||
|
@ -3,7 +3,6 @@
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const GuildChannel = require('./GuildChannel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const InteractionManager = require('../managers/InteractionManager');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
const { VideoQualityModes } = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
@ -28,12 +27,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
*/
|
||||
this.nsfw = Boolean(data.nsfw);
|
||||
|
||||
/**
|
||||
* A manager of the interactions sent to this channel
|
||||
* @type {InteractionManager}
|
||||
*/
|
||||
this.interactions = new InteractionManager(this);
|
||||
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
@ -95,7 +88,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
}
|
||||
|
||||
if ('nsfw' in data) {
|
||||
this.nsfw = Boolean(data.nsfw);
|
||||
this.nsfw = data.nsfw;
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,11 +163,11 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
* Sets the bitrate of the channel.
|
||||
* @param {number} bitrate The new bitrate
|
||||
* @param {string} [reason] Reason for changing the channel's bitrate
|
||||
* @returns {Promise<VoiceChannel>}
|
||||
* @returns {Promise<BaseGuildVoiceChannel>}
|
||||
* @example
|
||||
* // Set the bitrate of a voice channel
|
||||
* voiceChannel.setBitrate(48_000)
|
||||
* .then(vc => console.log(`Set bitrate to ${vc.bitrate}bps for ${vc.name}`))
|
||||
* channel.setBitrate(48_000)
|
||||
* .then(channel => console.log(`Set bitrate to ${channel.bitrate}bps for ${channel.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setBitrate(bitrate, reason) {
|
||||
@ -201,11 +194,11 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
* Sets the user limit of the channel.
|
||||
* @param {number} userLimit The new user limit
|
||||
* @param {string} [reason] Reason for changing the user limit
|
||||
* @returns {Promise<VoiceChannel>}
|
||||
* @returns {Promise<BaseGuildVoiceChannel>}
|
||||
* @example
|
||||
* // Set the user limit of a voice channel
|
||||
* voiceChannel.setUserLimit(42)
|
||||
* .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`))
|
||||
* channel.setUserLimit(42)
|
||||
* .then(channel => console.log(`Set user limit to ${channel.userLimit} for ${channel.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setUserLimit(userLimit, reason) {
|
||||
@ -216,7 +209,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
* Sets the camera video quality mode of the channel.
|
||||
* @param {VideoQualityMode|number} videoQualityMode The new camera video quality mode.
|
||||
* @param {string} [reason] Reason for changing the camera video quality mode.
|
||||
* @returns {Promise<VoiceChannel>}
|
||||
* @returns {Promise<BaseGuildVoiceChannel>}
|
||||
*/
|
||||
setVideoQualityMode(videoQualityMode, reason) {
|
||||
return this.edit({ videoQualityMode }, reason);
|
||||
@ -229,9 +222,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
sendTyping() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
createMessageComponentCollector() {}
|
||||
awaitMessageComponent() {}
|
||||
bulkDelete() {}
|
||||
fetchWebhooks() {}
|
||||
createWebhook() {}
|
||||
setRateLimitPerUser() {}
|
||||
|
@ -7,7 +7,7 @@ const Base = require('./Base');
|
||||
* Represents a call
|
||||
* @extends {Base}
|
||||
*/
|
||||
class Call extends Base {
|
||||
class CallState extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
/**
|
||||
@ -16,14 +16,11 @@ class Call extends Base {
|
||||
*/
|
||||
this.channelId = data.channel_id;
|
||||
|
||||
/**
|
||||
* The list of user ID who is ringing
|
||||
* @type {Collection<Snowflake, User>}
|
||||
*/
|
||||
this.ringing = new Collection();
|
||||
this._ringing = [];
|
||||
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
if ('region' in data) {
|
||||
/**
|
||||
@ -33,11 +30,10 @@ class Call extends Base {
|
||||
this.region = data.region;
|
||||
}
|
||||
if ('ringing' in data) {
|
||||
for (const userId of data.ringing) {
|
||||
this.ringing.set(userId, this.client.users.cache.get(userId));
|
||||
}
|
||||
this._ringing = data.ringing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The channel of the call
|
||||
* @type {?DMChannel|PartialGroupDMChannel}
|
||||
@ -45,14 +41,23 @@ class Call extends Base {
|
||||
get channel() {
|
||||
return this.client.channels.cache.get(this.channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the voice region of the call
|
||||
* @param {string} region Region of the call
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
setVoiceRegion(region) {
|
||||
setRTCRegion(region) {
|
||||
return this.client.api.channels(this.channelId).call.patch({ data: { region } });
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of user ID who is ringing
|
||||
* @type {Collection<Snowflake, User>}
|
||||
*/
|
||||
get ringing() {
|
||||
return new Collection(this._ringing.map(id => [id, this.client.users.cache.get(id)]));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Call;
|
||||
module.exports = CallState;
|
@ -53,7 +53,6 @@ class Channel extends Base {
|
||||
if ('flags' in data) {
|
||||
/**
|
||||
* The flags that are applied to the channel.
|
||||
* <info>This is only `null` in a {@link PartialGroupDMChannel}. In all other cases, it is not `null`.</info>
|
||||
* @type {?Readonly<ChannelFlags>}
|
||||
*/
|
||||
this.flags = new ChannelFlags(data.flags).freeze();
|
||||
@ -203,8 +202,8 @@ class Channel extends Base {
|
||||
if ((data.recipients && data.type !== ChannelTypes.GROUP_DM) || data.type === ChannelTypes.DM) {
|
||||
channel = new DMChannel(client, data);
|
||||
} else if (data.type === ChannelTypes.GROUP_DM) {
|
||||
const PartialGroupDMChannel = require('./PartialGroupDMChannel');
|
||||
channel = new PartialGroupDMChannel(client, data);
|
||||
const GroupDMChannel = require('./GroupDMChannel');
|
||||
channel = new GroupDMChannel(client, data);
|
||||
}
|
||||
} else {
|
||||
guild ??= client.guilds.cache.get(data.guild_id);
|
||||
|
@ -1,233 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { ApplicationRoleConnectionMetadata } = require('./ApplicationRoleConnectionMetadata');
|
||||
const Team = require('./Team');
|
||||
const Application = require('./interfaces/Application');
|
||||
const ApplicationCommandManager = require('../managers/ApplicationCommandManager');
|
||||
const ApplicationFlags = require('../util/ApplicationFlags');
|
||||
const { ApplicationRoleConnectionMetadataTypes } = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
|
||||
/**
|
||||
* @typedef {Object} ClientApplicationInstallParams
|
||||
* @property {InviteScope[]} scopes The scopes to add the application to the server with
|
||||
* @property {Readonly<Permissions>} permissions The permissions this bot will request upon joining
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a client application.
|
||||
* @extends {Application}
|
||||
*/
|
||||
class ClientApplication extends Application {
|
||||
constructor(client, data, user) {
|
||||
super(client, data);
|
||||
|
||||
/**
|
||||
* The application command manager for this application
|
||||
* @type {ApplicationCommandManager}
|
||||
*/
|
||||
this.commands = new ApplicationCommandManager(this.client, undefined, user);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
|
||||
/**
|
||||
* 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 ('approximate_guild_count' in data) {
|
||||
/**
|
||||
* An approximate amount of guilds this application is in.
|
||||
* @type {?number}
|
||||
*/
|
||||
this.approximateGuildCount = data.approximate_guild_count;
|
||||
} else {
|
||||
this.approximateGuildCount ??= null;
|
||||
}
|
||||
|
||||
if ('guild_id' in data) {
|
||||
/**
|
||||
* The id of the guild associated with this application.
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.guildId = data.guild_id;
|
||||
} else {
|
||||
this.guildId ??= null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if ('popular_application_command_ids' in data) {
|
||||
/**
|
||||
* List of popular command
|
||||
* @type {?Collection<Snowflake, ApplicationCommand>}
|
||||
*/
|
||||
this.popularCommands = new Collection();
|
||||
data.popular_application_command_ids.forEach(id => {
|
||||
this.popularCommands.set(id, this.commands?.cache?.get(id));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The owner of this OAuth application
|
||||
* @type {?(User|Team)}
|
||||
*/
|
||||
this.owner = data.team
|
||||
? new Team(this.client, data.team)
|
||||
: data.owner
|
||||
? this.client.users._add(data.owner)
|
||||
: this.owner ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The guild associated with this application.
|
||||
* @type {?Guild}
|
||||
* @readonly
|
||||
*/
|
||||
get guild() {
|
||||
return this.client.guilds.cache.get(this.guildId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this application is partial
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get partial() {
|
||||
return !this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains this application from Discord.
|
||||
* @returns {Promise<ClientApplication>}
|
||||
*/
|
||||
async fetch() {
|
||||
const app = await this.client.api.oauth2.authorize.get({
|
||||
query: {
|
||||
client_id: this.id,
|
||||
scope: 'bot applications.commands',
|
||||
},
|
||||
});
|
||||
const user = this.client.users._add(app.bot);
|
||||
user._partial = false;
|
||||
this._patch(app.application);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientApplication;
|
@ -2,7 +2,7 @@
|
||||
|
||||
const { Presence } = require('./Presence');
|
||||
const { TypeError } = require('../errors');
|
||||
const { Opcodes, ActivityTypes } = require('../util/Constants');
|
||||
const { ActivityTypes, Opcodes } = require('../util/Constants');
|
||||
|
||||
const CustomStatusActivityTypes = [ActivityTypes.CUSTOM, ActivityTypes[ActivityTypes.CUSTOM]];
|
||||
|
||||
@ -22,7 +22,6 @@ class ClientPresence extends Presence {
|
||||
*/
|
||||
set(presence) {
|
||||
const packet = this._parse(presence);
|
||||
// Parse with custom class
|
||||
this._patch(packet, true);
|
||||
if (typeof presence.shardId === 'undefined') {
|
||||
this.client.ws.broadcast({ op: Opcodes.STATUS_UPDATE, d: packet });
|
||||
@ -33,8 +32,6 @@ class ClientPresence extends Presence {
|
||||
} else {
|
||||
this.client.ws.shards.get(presence.shardId).send({ op: Opcodes.STATUS_UPDATE, d: packet });
|
||||
}
|
||||
// Parse with default class
|
||||
// this._patch(packet, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -48,14 +45,13 @@ class ClientPresence extends Presence {
|
||||
const data = {
|
||||
activities: [],
|
||||
afk: typeof afk === 'boolean' ? afk : false,
|
||||
since: 0,
|
||||
since: typeof since === 'number' && !Number.isNaN(since) ? since : null,
|
||||
status: status ?? this.status,
|
||||
};
|
||||
if (activities?.length) {
|
||||
for (const [i, activity] of activities.entries()) {
|
||||
if (![ActivityTypes.CUSTOM, 'CUSTOM'].includes(activity.type) && typeof activity.name !== 'string') {
|
||||
throw new TypeError('INVALID_TYPE', `activities[${i}].name`, 'string');
|
||||
}
|
||||
if (typeof activity.name !== 'string') throw new TypeError('INVALID_TYPE', `activities[${i}].name`, 'string');
|
||||
|
||||
activity.type ??= ActivityTypes.PLAYING;
|
||||
|
||||
if (CustomStatusActivityTypes.includes(activity.type) && !activity.state) {
|
||||
|
@ -3,19 +3,24 @@
|
||||
const { setInterval } = require('node:timers');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const Invite = require('./Invite');
|
||||
const { Message } = require('./Message');
|
||||
const User = require('./User');
|
||||
const { Util } = require('..');
|
||||
const { Error: Error_ } = require('../errors');
|
||||
const { Opcodes, NitroType, HypeSquadType } = require('../util/Constants');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
const PremiumUsageFlags = require('../util/PremiumUsageFlags');
|
||||
const PurchasedFlags = require('../util/PurchasedFlags');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Represents the logged in client's Discord user.
|
||||
* @extends {User}
|
||||
*/
|
||||
class ClientUser extends User {
|
||||
#packageName = null;
|
||||
#intervalSamsungPresence = setInterval(() => {
|
||||
this.client.emit('debug', `[UPDATE] Samsung Presence: ${this.#packageName}`);
|
||||
if (!this.#packageName) return;
|
||||
this.setSamsungActivity(this.#packageName, 'UPDATE');
|
||||
}, 1000 * 60 * 10).unref();
|
||||
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
|
||||
@ -29,7 +34,7 @@ class ClientUser extends User {
|
||||
|
||||
if ('mfa_enabled' in data) {
|
||||
/**
|
||||
* If the bot's {@link ClientApplication#owner Owner} has MFA enabled on their account
|
||||
* If the bot's {@link Application#owner Owner} has MFA enabled on their account
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.mfaEnabled = typeof data.mfa_enabled === 'boolean' ? data.mfa_enabled : null;
|
||||
@ -39,16 +44,6 @@ class ClientUser extends User {
|
||||
|
||||
if ('token' in data) this.client.token = data.token;
|
||||
|
||||
// Todo: Add (Selfbot)
|
||||
if ('premium_type' in data) {
|
||||
const nitro = NitroType[data.premium_type ?? 0];
|
||||
/**
|
||||
* Nitro type of the client user.
|
||||
* @type {NitroType}
|
||||
*/
|
||||
this.nitroType = nitro ?? `UNKNOWN_TYPE_${data.premium_type}`;
|
||||
}
|
||||
|
||||
if ('purchased_flags' in data) {
|
||||
/**
|
||||
* Purchased state of the client user.
|
||||
@ -70,7 +65,7 @@ class ClientUser extends User {
|
||||
* Phone number of the client user.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.phoneNumber = data.phone;
|
||||
this.phone = data.phone;
|
||||
}
|
||||
|
||||
if ('nsfw_allowed' in data) {
|
||||
@ -86,48 +81,35 @@ class ClientUser extends User {
|
||||
* Email address of the client user.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.emailAddress = data.email;
|
||||
this.email = data.email;
|
||||
}
|
||||
|
||||
if ('bio' in data) {
|
||||
/**
|
||||
* About me (User)
|
||||
* <info>The user must be force fetched for this property to be present or be updated</info>
|
||||
* @type {?string}
|
||||
*/
|
||||
this.bio = data.bio;
|
||||
}
|
||||
|
||||
if ('pronouns' in data) {
|
||||
/**
|
||||
* Pronouns (User)
|
||||
* <info>The user must be force fetched for this property to be present or be updated</info>
|
||||
* @type {?string}
|
||||
*/
|
||||
this.pronouns = data.pronouns;
|
||||
}
|
||||
|
||||
if (!this.friendNicknames?.size) {
|
||||
if ('premium_type' in data) {
|
||||
/**
|
||||
* The friend nicknames cache of the client user.
|
||||
* @type {Collection<Snowflake, string>}
|
||||
* @private
|
||||
* Premium types denote the level of premium a user has.
|
||||
* @type {number}
|
||||
* @see {@link https://discord-userdoccers.vercel.app/resources/user#premium-type}
|
||||
*/
|
||||
this.friendNicknames = new Collection();
|
||||
this.premiumType = data.premium_type;
|
||||
}
|
||||
|
||||
if (!this._intervalSamsungPresence) {
|
||||
this._intervalSamsungPresence = setInterval(() => {
|
||||
this.client.emit('debug', `Samsung Presence: ${this._packageName}`);
|
||||
if (!this._packageName) return;
|
||||
this.setSamsungActivity(this._packageName, 'UPDATE');
|
||||
}, 1000 * 60 * 10).unref();
|
||||
// 20 minutes max
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch note
|
||||
* @param {Object} data Note data
|
||||
* @private
|
||||
*/
|
||||
_patchNote(data) {
|
||||
/**
|
||||
* The notes cache of the client user.
|
||||
* @type {Collection<Snowflake, string>}
|
||||
* @private
|
||||
*/
|
||||
this.notes = data ? new Collection(Object.entries(data)) : new Collection();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,7 +147,6 @@ class ClientUser extends User {
|
||||
* <info>Changing usernames in Discord is heavily rate limited, with only 2 requests
|
||||
* every hour. Use this sparingly!</info>
|
||||
* @param {string} username The new username
|
||||
* @param {string} password The password of the account
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // Set username
|
||||
@ -173,14 +154,8 @@ class ClientUser extends User {
|
||||
* .then(user => console.log(`My new username is ${user.username}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setUsername(username, password) {
|
||||
if (!password && !this.client.password) {
|
||||
throw new Error('A password is required to change a username.');
|
||||
}
|
||||
return this.edit({
|
||||
username,
|
||||
password: this.client.password ? this.client.password : password,
|
||||
});
|
||||
setUsername(username) {
|
||||
return this.edit({ username });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -197,186 +172,6 @@ class ClientUser extends User {
|
||||
avatar = avatar && (await DataResolver.resolveImage(avatar));
|
||||
return this.edit({ avatar });
|
||||
}
|
||||
/**
|
||||
* Sets the banner of the logged in client.
|
||||
* @param {?(BufferResolvable|Base64Resolvable)} banner The new banner
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // Set banner
|
||||
* client.user.setBanner('./banner.png')
|
||||
* .then(user => console.log(`New banner set!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async setBanner(banner) {
|
||||
if (this.nitroType !== 'NITRO_BOOST') {
|
||||
throw new Error('You must be a Nitro Boosted User to change your banner.');
|
||||
}
|
||||
banner = banner && (await DataResolver.resolveImage(banner));
|
||||
return this.edit({ banner });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HyperSquad House
|
||||
* @param {HypeSquadType} type
|
||||
* * `LEAVE`: 0
|
||||
* * `HOUSE_BRAVERY`: 1
|
||||
* * `HOUSE_BRILLIANCE`: 2
|
||||
* * `HOUSE_BALANCE`: 3
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Set HyperSquad HOUSE_BRAVERY
|
||||
* client.user.setHypeSquad(1); || client.user.setHypeSquad('HOUSE_BRAVERY');
|
||||
* // Leave
|
||||
* client.user.setHypeSquad(0);
|
||||
*/
|
||||
async setHypeSquad(type) {
|
||||
const id = typeof type === 'string' ? HypeSquadType[type] : type;
|
||||
if (!id && id !== 0) throw new Error('Invalid HypeSquad type.');
|
||||
if (id !== 0) {
|
||||
const data = await this.client.api.hypesquad.online.post({
|
||||
data: { house_id: id },
|
||||
});
|
||||
return data;
|
||||
} else {
|
||||
const data = await this.client.api.hypesquad.online.delete();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Accent color
|
||||
* @param {ColorResolvable} color Color to set
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setAccentColor(color = null) {
|
||||
return this.edit({ accent_color: color ? Util.resolveColor(color) : null });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set discriminator
|
||||
* @param {User.discriminator} discriminator It is #1234
|
||||
* @param {string} password The password of the account
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setDiscriminator(discriminator, password) {
|
||||
if (this.nitroType == 'NONE') throw new Error('You must be a Nitro User to change your discriminator.');
|
||||
if (!password && !this.client.password) {
|
||||
throw new Error('A password is required to change a discriminator.');
|
||||
}
|
||||
return this.edit({
|
||||
discriminator,
|
||||
username: this.username,
|
||||
password: this.client.password ? this.client.password : password,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set About me
|
||||
* @param {string | null} bio Bio to set
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setAboutMe(bio = null) {
|
||||
return this.edit({
|
||||
bio,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the email
|
||||
* @param {Email<string>} email Email to change
|
||||
* @param {string} password Password of the account
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setEmail(email, password) {
|
||||
throw new Error('This method is not available yet. Please use the official Discord client to change your email.');
|
||||
// eslint-disable-next-line no-unreachable
|
||||
if (!password && !this.client.password) {
|
||||
throw new Error('A password is required to change a email.');
|
||||
}
|
||||
return this.edit({
|
||||
email,
|
||||
password: this.client.password ? this.client.password : password,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new password
|
||||
* @param {string} oldPassword Old password
|
||||
* @param {string} newPassword New password to set
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setPassword(oldPassword, newPassword) {
|
||||
if (!oldPassword && !this.client.password) {
|
||||
throw new Error('A password is required to change a password.');
|
||||
}
|
||||
if (!newPassword) throw new Error('New password is required.');
|
||||
return this.edit({
|
||||
password: this.client.password ? this.client.password : oldPassword,
|
||||
new_password: newPassword,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable account
|
||||
* @param {string} password Password of the account
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
async disableAccount(password) {
|
||||
if (!password && !this.client.password) {
|
||||
throw new Error('A password is required to disable an account.');
|
||||
}
|
||||
const data = await this.client.api.users['@me'].disable.post({
|
||||
data: {
|
||||
password: this.client.password ? this.client.password : password,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selfdeaf (Global)
|
||||
* @param {boolean} status Whether or not the ClientUser is deafened
|
||||
* @returns {boolean}
|
||||
*/
|
||||
setDeaf(status) {
|
||||
if (typeof status !== 'boolean') throw new Error('Deaf status must be a boolean.');
|
||||
this.client.ws.broadcast({
|
||||
op: Opcodes.VOICE_STATE_UPDATE,
|
||||
d: { self_deaf: status },
|
||||
});
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selfmute (Global)
|
||||
* @param {boolean} status Whether or not the ClientUser is muted
|
||||
* @returns {boolean}
|
||||
*/
|
||||
setMute(status) {
|
||||
if (typeof status !== 'boolean') throw new Error('Mute status must be a boolean.');
|
||||
this.client.ws.broadcast({
|
||||
op: Opcodes.VOICE_STATE_UPDATE,
|
||||
d: { self_mute: status },
|
||||
});
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account. Warning: Cannot be changed once used!
|
||||
* @param {string} password Password of the account
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
async deleteAccount(password) {
|
||||
if (!password && !this.client.password) {
|
||||
throw new Error('A password is required to delete an account.');
|
||||
}
|
||||
const data = await this.client.api.users['@me/delete'].post({
|
||||
data: {
|
||||
password: this.client.password ? this.client.password : password,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for setting activities
|
||||
@ -451,15 +246,10 @@ class ClientUser extends User {
|
||||
* @see {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/RichPresence.md}
|
||||
*/
|
||||
setActivity(name, options = {}) {
|
||||
if (!name) {
|
||||
return this.setPresence({ activities: [], shardId: options.shardId });
|
||||
}
|
||||
if (!name) return this.setPresence({ activities: [], shardId: options.shardId });
|
||||
|
||||
const activity = Object.assign({}, options, typeof name === 'object' ? name : { name });
|
||||
return this.setPresence({
|
||||
activities: [activity],
|
||||
shardId: activity.shardId,
|
||||
});
|
||||
return this.setPresence({ activities: [activity], shardId: activity.shardId });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -472,6 +262,81 @@ class ClientUser extends User {
|
||||
return this.setPresence({ afk, shardId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the banner of the logged in client.
|
||||
* @param {?(BufferResolvable|Base64Resolvable)} banner The new banner
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // Set banner
|
||||
* client.user.setBanner('./banner.png')
|
||||
* .then(user => console.log(`New banner set!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async setBanner(banner) {
|
||||
banner = banner && (await DataResolver.resolveImage(banner));
|
||||
return this.edit({ banner });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HyperSquad House
|
||||
* @param {string|number} type
|
||||
* * `LEAVE`: 0
|
||||
* * `HOUSE_BRAVERY`: 1
|
||||
* * `HOUSE_BRILLIANCE`: 2
|
||||
* * `HOUSE_BALANCE`: 3
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Set HyperSquad HOUSE_BRAVERY
|
||||
* client.user.setHypeSquad(1); || client.user.setHypeSquad('HOUSE_BRAVERY');
|
||||
* // Leave
|
||||
* client.user.setHypeSquad(0);
|
||||
*/
|
||||
setHypeSquad(type) {
|
||||
switch (type) {
|
||||
case 'LEAVE': {
|
||||
type = 0;
|
||||
break;
|
||||
}
|
||||
case 'HOUSE_BRAVERY': {
|
||||
type = 1;
|
||||
break;
|
||||
}
|
||||
case 'HOUSE_BRILLIANCE': {
|
||||
type = 2;
|
||||
break;
|
||||
}
|
||||
case 'HOUSE_BALANCE': {
|
||||
type = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (type == 0) {
|
||||
return this.client.api.hypesquad.online.delete();
|
||||
} else {
|
||||
return this.client.api.hypesquad.online.post({
|
||||
data: { house_id: type },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Accent color
|
||||
* @param {ColorResolvable} color Color to set
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setAccentColor(color = null) {
|
||||
return this.edit({ accent_color: color ? Util.resolveColor(color) : null });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set About me
|
||||
* @param {string} [bio=null] Bio to set
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setAboutMe(bio = null) {
|
||||
return this.edit({ bio });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an invite [Friend Invites]
|
||||
* maxAge: 604800 | maxUses: 1
|
||||
@ -505,63 +370,10 @@ class ClientUser extends User {
|
||||
|
||||
/**
|
||||
* Revoke all friend invites
|
||||
* @returns {Promise<Collection<string, Invite>>}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async revokeAllFriendInvites() {
|
||||
const data = await this.client.api.users['@me'].invites.delete();
|
||||
const collection = new Collection();
|
||||
for (const invite of data) {
|
||||
collection.set(invite.code, new Invite(this.client, invite));
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of messages mentioning clientUser
|
||||
* @param {number} [limit=25] Maximum number of messages to get
|
||||
* @param {boolean} [mentionRoles=true] Whether or not to mention roles
|
||||
* @param {boolean} [mentionEveryone=true] Whether or not to mention `@everyone`
|
||||
* @returns {Promise<Collection<Snowflake, Message>>}
|
||||
*/
|
||||
async getMentions(limit = 25, mentionRoles = true, mentionEveryone = true) {
|
||||
// https://canary.discord.com/api/v9/users/@me/mentions?limit=25&roles=true&everyone=true
|
||||
const data = await this.client.api.users['@me'].mentions.get({
|
||||
query: {
|
||||
limit,
|
||||
roles: mentionRoles,
|
||||
everyone: mentionEveryone,
|
||||
},
|
||||
});
|
||||
const collection = new Collection();
|
||||
for (const msg of data) {
|
||||
collection.set(msg.id, new Message(this.client, msg));
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Theme color
|
||||
* @param {ColorResolvable} primary The primary color of the user's profile
|
||||
* @param {ColorResolvable} accent The accent color of the user's profile
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
async setThemeColors(primary, accent) {
|
||||
if (!primary || !accent) throw new Error('PRIMARY_COLOR or ACCENT_COLOR are required.');
|
||||
// Check nitro
|
||||
if (this.nitroType !== 'NITRO_BOOST') {
|
||||
throw new Error_('NITRO_BOOST_REQUIRED', 'themeColors');
|
||||
}
|
||||
primary = Util.resolveColor(primary) || this.themeColors[0];
|
||||
accent = Util.resolveColor(accent) || this.themeColors[1];
|
||||
const data_ = await this.client.api.users['@me'].profile.patch({
|
||||
data: {
|
||||
theme_colors: [primary, accent],
|
||||
},
|
||||
});
|
||||
this._ProfilePatch({
|
||||
user_profile: data_,
|
||||
});
|
||||
return this;
|
||||
revokeAllFriendInvites() {
|
||||
return this.client.api.users['@me'].invites.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -587,8 +399,8 @@ class ClientUser extends User {
|
||||
update: type,
|
||||
},
|
||||
});
|
||||
if (type !== 'STOP') this._packageName = packageName;
|
||||
else this._packageName = null;
|
||||
if (type !== 'STOP') this.#packageName = packageName;
|
||||
else this.#packageName = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -598,9 +410,7 @@ class ClientUser extends User {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
stopRinging(channel) {
|
||||
const id = this.client.channels.resolveId(channel);
|
||||
if (!channel) return false;
|
||||
return this.client.api.channels(id).call['stop-ringing'].post({
|
||||
return this.client.api.channels(this.client.channels.resolveId(channel)).call['stop-ringing'].post({
|
||||
data: {},
|
||||
});
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class ContextMenuInteraction extends BaseCommandInteraction {
|
||||
);
|
||||
|
||||
/**
|
||||
* The id of the target of the interaction
|
||||
* The id of the target of this interaction
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.targetId = data.data.target_id;
|
||||
|
@ -1,12 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { joinVoiceChannel, entersState, VoiceConnectionStatus } = require('@discordjs/voice');
|
||||
const { Channel } = require('./Channel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const InteractionManager = require('../managers/InteractionManager');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
const { Status, Opcodes } = require('../util/Constants');
|
||||
const { Opcodes, Status } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents a direct message channel between two users.
|
||||
@ -25,12 +23,6 @@ class DMChannel extends Channel {
|
||||
* @type {MessageManager}
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the interactions sent to this channel
|
||||
* @type {InteractionManager}
|
||||
*/
|
||||
this.interactions = new InteractionManager(this);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
@ -65,7 +57,7 @@ class DMChannel extends Channel {
|
||||
if ('is_message_request' in data) {
|
||||
/**
|
||||
* Whether the channel is a message request
|
||||
* @type {boolean}
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.messageRequest = data.is_message_request;
|
||||
}
|
||||
@ -138,78 +130,6 @@ class DMChannel extends Channel {
|
||||
return this.recipient.toString();
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
/* eslint-disable no-empty-function */
|
||||
get lastMessage() {}
|
||||
get lastPinAt() {}
|
||||
send() {}
|
||||
sendTyping() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
createMessageComponentCollector() {}
|
||||
awaitMessageComponent() {}
|
||||
sendSlash() {}
|
||||
searchInteraction() {}
|
||||
// Doesn't work on DM channels; bulkDelete() {}
|
||||
// Doesn't work on DM channels; setRateLimitPerUser() {}
|
||||
// Doesn't work on DM channels; setNSFW() {}
|
||||
// Testing feature: Call
|
||||
// URL: https://discord.com/api/v9/channels/:DMchannelId/call/ring
|
||||
/**
|
||||
* Call this DMChannel. Return discordjs/voice VoiceConnection
|
||||
* @param {CallOptions} options Options for the call
|
||||
* @returns {Promise<VoiceConnection>}
|
||||
*/
|
||||
call(options = {}) {
|
||||
options = Object.assign(
|
||||
{
|
||||
ring: true,
|
||||
},
|
||||
options || {},
|
||||
);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.client.options.patchVoice) {
|
||||
reject(
|
||||
new Error(
|
||||
'VOICE_NOT_PATCHED',
|
||||
'Enable voice patching in client options\nhttps://discordjs-self-v13.netlify.app/#/docs/docs/main/typedef/ClientOptions',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (options.ring) {
|
||||
this.ring();
|
||||
}
|
||||
const connection = joinVoiceChannel({
|
||||
channelId: this.id,
|
||||
guildId: null,
|
||||
adapterCreator: this.voiceAdapterCreator,
|
||||
selfDeaf: options.selfDeaf ?? false,
|
||||
selfMute: options.selfMute ?? false,
|
||||
});
|
||||
entersState(connection, VoiceConnectionStatus.Ready, 30000)
|
||||
.then(connection => {
|
||||
resolve(connection);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.destroy();
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ring the user's phone / PC (call)
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
ring() {
|
||||
return this.client.api.channels(this.id).call.ring.post({
|
||||
data: {
|
||||
recipients: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync VoiceState of this DMChannel.
|
||||
* @returns {undefined}
|
||||
@ -222,6 +142,19 @@ class DMChannel extends Channel {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ring the user's phone / PC (call)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
ring() {
|
||||
return this.client.api.channels(this.id).call.ring.post({
|
||||
data: {
|
||||
recipients: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The user in this voice-based channel
|
||||
* @type {Collection<Snowflake, User>}
|
||||
@ -236,18 +169,7 @@ class DMChannel extends Channel {
|
||||
}
|
||||
return coll;
|
||||
}
|
||||
/**
|
||||
* Get connection to current call
|
||||
* @type {?VoiceConnection}
|
||||
* @readonly
|
||||
*/
|
||||
get voiceConnection() {
|
||||
const check = this.client.callVoice?.joinConfig?.channelId == this.id;
|
||||
if (check) {
|
||||
return this.client.callVoice;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current shard
|
||||
* @type {WebSocketShard}
|
||||
@ -256,6 +178,7 @@ class DMChannel extends Channel {
|
||||
get shard() {
|
||||
return this.client.ws.shards.first();
|
||||
}
|
||||
|
||||
/**
|
||||
* The voice state adapter for this client that can be used with @discordjs/voice to play audio in DM / Group DM channels.
|
||||
* @type {?Function}
|
||||
@ -276,14 +199,19 @@ class DMChannel extends Channel {
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
/* eslint-disable no-empty-function */
|
||||
get lastMessage() {}
|
||||
get lastPinAt() {}
|
||||
send() {}
|
||||
sendTyping() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
// Doesn't work on DM channels; setRateLimitPerUser() {}
|
||||
// Doesn't work on DM channels; setNSFW() {}
|
||||
}
|
||||
|
||||
TextBasedChannel.applyToClass(DMChannel, true, [
|
||||
'bulkDelete',
|
||||
'fetchWebhooks',
|
||||
'createWebhook',
|
||||
'setRateLimitPerUser',
|
||||
'setNSFW',
|
||||
]);
|
||||
TextBasedChannel.applyToClass(DMChannel, true, ['fetchWebhooks', 'createWebhook', 'setRateLimitPerUser', 'setNSFW']);
|
||||
|
||||
module.exports = DMChannel;
|
||||
|
@ -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;
|
@ -3,7 +3,6 @@
|
||||
const GuildChannel = require('./GuildChannel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const GuildForumThreadManager = require('../managers/GuildForumThreadManager');
|
||||
const InteractionManager = require('../managers/InteractionManager');
|
||||
const { SortOrderTypes, ForumLayoutTypes } = require('../util/Constants');
|
||||
const { transformAPIGuildForumTag, transformAPIGuildDefaultReaction } = require('../util/Util');
|
||||
|
||||
@ -46,12 +45,6 @@ class ForumChannel extends GuildChannel {
|
||||
constructor(guild, data, client) {
|
||||
super(guild, data, client, false);
|
||||
|
||||
/**
|
||||
* A manager of the interactions sent to this channel
|
||||
* @type {InteractionManager}
|
||||
*/
|
||||
this.interactions = new InteractionManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the threads belonging to this channel
|
||||
* @type {GuildForumThreadManager}
|
||||
@ -260,12 +253,9 @@ TextBasedChannel.applyToClass(ForumChannel, true, [
|
||||
'send',
|
||||
'lastMessage',
|
||||
'lastPinAt',
|
||||
'bulkDelete',
|
||||
'sendTyping',
|
||||
'createMessageCollector',
|
||||
'awaitMessages',
|
||||
'createMessageComponentCollector',
|
||||
'awaitMessageComponent',
|
||||
]);
|
||||
|
||||
module.exports = ForumChannel;
|
||||
|
@ -1,71 +1,91 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { joinVoiceChannel, entersState, VoiceConnectionStatus } = require('@discordjs/voice');
|
||||
const { Channel } = require('./Channel');
|
||||
const Invite = require('./Invite');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const { Error } = require('../errors');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
const { Status, Opcodes } = require('../util/Constants');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
|
||||
/**
|
||||
* Represents a Partial Group DM Channel on Discord.
|
||||
* Represents a Group DM Channel on Discord.
|
||||
* @extends {Channel}
|
||||
* @implements {TextBasedChannel}
|
||||
*/
|
||||
class PartialGroupDMChannel extends Channel {
|
||||
class GroupDMChannel extends Channel {
|
||||
constructor(client, data) {
|
||||
super(client, data);
|
||||
|
||||
// No flags are present when fetching partial group DM channels.
|
||||
this.flags = null;
|
||||
// Override the channel type so partials have a known type
|
||||
this.type = 'GROUP_DM';
|
||||
|
||||
/**
|
||||
* The name of this Group DM Channel
|
||||
* @type {?string}
|
||||
*/
|
||||
this.name = null;
|
||||
|
||||
/**
|
||||
* The hash of the channel icon
|
||||
* @type {?string}
|
||||
*/
|
||||
this.icon = null;
|
||||
|
||||
/**
|
||||
* Messages data
|
||||
* @type {Collection}
|
||||
* A manager of the messages belonging to this channel
|
||||
* @type {MessageManager}
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Last Message ID
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.lastMessageId = null;
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
|
||||
/**
|
||||
* Last Pin Timestamp
|
||||
* @type {UnixTimestamp}
|
||||
*/
|
||||
this.lastPinTimestamp = null;
|
||||
if ('recipients' in data && Array.isArray(data.recipients)) {
|
||||
this._recipients = data.recipients;
|
||||
data.recipients.forEach(u => this.client.users._add(u));
|
||||
} else {
|
||||
this._recipients = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner ID
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.ownerId = null;
|
||||
if ('last_message_id' in data) {
|
||||
/**
|
||||
* The channel's last message id, if one was sent
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.lastMessageId = data.last_message_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invites fetch
|
||||
* @type {Collection<string, Invite>}
|
||||
*/
|
||||
this.invites = new Collection();
|
||||
if ('last_pin_timestamp' in data) {
|
||||
/**
|
||||
* The timestamp when the last pinned message was pinned, if there was one
|
||||
* @type {?number}
|
||||
*/
|
||||
this.lastPinTimestamp = new Date(data.last_pin_timestamp).getTime();
|
||||
} else {
|
||||
this.lastPinTimestamp ??= null;
|
||||
}
|
||||
|
||||
this._recipients = [];
|
||||
if ('owner_id' in data) {
|
||||
/**
|
||||
* Owner ID
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.ownerId = data.owner_id;
|
||||
}
|
||||
|
||||
this._patch(data);
|
||||
if ('name' in data) {
|
||||
/**
|
||||
* The name of this Group DM Channel
|
||||
* @type {?string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
}
|
||||
|
||||
if ('icon' in data) {
|
||||
/**
|
||||
* The hash of the channel icon
|
||||
* @type {?string}
|
||||
*/
|
||||
this.icon = data.icon;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL to this channel's icon.
|
||||
* @param {StaticImageURLOptions} [options={}] Options for the Image URL
|
||||
* @returns {?string}
|
||||
*/
|
||||
iconURL({ format, size } = {}) {
|
||||
return this.icon && this.client.rest.cdn.GDMIcon(this.id, this.icon, format, size);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,7 +95,7 @@ class PartialGroupDMChannel extends Channel {
|
||||
*/
|
||||
get recipients() {
|
||||
const collect = new Collection();
|
||||
this._recipients.map(recipient => collect.set(recipient.id, this.client.users._add(recipient)));
|
||||
this._recipients.map(recipient => collect.set(recipient.id, this.client.users.cache.get(recipient.id)));
|
||||
collect.set(this.client.user.id, this.client.user);
|
||||
return collect;
|
||||
}
|
||||
@ -90,38 +110,82 @@ class PartialGroupDMChannel extends Channel {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} data Channel Data
|
||||
* @private
|
||||
* Whether this DMChannel is a partial
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
if ('recipients' in data && Array.isArray(data.recipients)) {
|
||||
this._recipients = data.recipients;
|
||||
}
|
||||
if ('last_pin_timestamp' in data) {
|
||||
const date = new Date(data.last_pin_timestamp);
|
||||
this.lastPinTimestamp = date.getTime();
|
||||
}
|
||||
if ('last_message_id' in data) {
|
||||
this.lastMessageId = data.last_message_id;
|
||||
}
|
||||
if ('owner_id' in data) {
|
||||
this.ownerId = data.owner_id;
|
||||
}
|
||||
if ('name' in data) {
|
||||
this.name = data.name;
|
||||
}
|
||||
if ('icon' in data) {
|
||||
this.icon = data.icon;
|
||||
}
|
||||
get partial() {
|
||||
return typeof this.lastMessageId === 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Leave this Group DM Channel.
|
||||
* @param {?boolean} slient Leave without notifying other members
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
* @example
|
||||
* // Delete the channel
|
||||
* channel.delete()
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async delete(slient = false) {
|
||||
if (typeof slient === 'boolean' && slient) {
|
||||
await this.client.api.channels[this.id].delete({
|
||||
query: {
|
||||
silent: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await this.client.api.channels[this.id].delete();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the recipient's mention instead of the
|
||||
* GroupDMChannel object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // Logs: Hello from Group Test!
|
||||
* console.log(`Hello from ${channel}!`);
|
||||
*/
|
||||
toString() {
|
||||
return (
|
||||
this.name ??
|
||||
this._recipients
|
||||
.filter(user => user.id !== this.client.user.id)
|
||||
.map(user => user.username)
|
||||
.join(', ')
|
||||
);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const json = super.toJSON({
|
||||
createdTimestamp: true,
|
||||
});
|
||||
json.iconURL = this.iconURL();
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* The data for editing a channe;.
|
||||
* @typedef {Object} GroupDMChannelEditData
|
||||
* @property {string} [name] The name of the channel
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [icon] The icon of the channel
|
||||
* @property {GuildMemberResolvable} [owner] The owner of the channel
|
||||
*/
|
||||
|
||||
/**
|
||||
* Edit channel data
|
||||
* @param {Object} data name, icon owner
|
||||
* @returns {Promise<undefined>}
|
||||
* @private
|
||||
* @param {GroupDMChannelEditData} data Data
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
* @example
|
||||
* // Set the channel name
|
||||
* channel.edit({
|
||||
* name: 'Group Test',
|
||||
* })
|
||||
* .then(updated => console.log(`New channel name ${updated}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async edit(data) {
|
||||
const _data = {};
|
||||
@ -132,64 +196,17 @@ class PartialGroupDMChannel extends Channel {
|
||||
if ('owner' in data) {
|
||||
_data.owner = data.owner;
|
||||
}
|
||||
const newData = await this.client.api.channels(this.id).patch({
|
||||
const newData = await this.client.api.channels[this.id].patch({
|
||||
data: _data,
|
||||
});
|
||||
|
||||
return this.client.actions.ChannelUpdate.handle(newData).updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL to this channel's icon.
|
||||
* @param {StaticImageURLOptions} [options={}] Options for the Image URL
|
||||
* @returns {?string}
|
||||
*/
|
||||
iconURL({ format, size } = {}) {
|
||||
return this.icon && this.client.rest.cdn.GDMIcon(this.id, this.icon, format, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a user to this Group DM Channel.
|
||||
* @param {UserResolvable} user User to add to the group
|
||||
* @returns {Promise<PartialGroupDMChannel>}
|
||||
*/
|
||||
async addMember(user) {
|
||||
const userId = this.client.users.resolveId(user);
|
||||
user = this.client.users.resolve(userId);
|
||||
if (!userId) {
|
||||
return Promise.reject(new TypeError('User is not a User or User ID'));
|
||||
}
|
||||
// API
|
||||
// if (this.recipients.get(userId)) return Promise.reject(new Error('USER_ALREADY_IN_GROUP_DM_CHANNEL')); // Fails sometimes if member leaves recently (ex. user leave msg's channel used for adding)
|
||||
await this.client.api.channels[this.id].recipients[userId].put();
|
||||
this._recipients.push(user);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a user from this Group DM Channel.
|
||||
* @param {UserResolvable} user User to remove from the group
|
||||
* @returns {Promise<PartialGroupDMChannel>}
|
||||
*/
|
||||
async removeMember(user) {
|
||||
if (this.ownerId !== this.client.user.id) {
|
||||
return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
|
||||
}
|
||||
user = this.client.users.resolveId(user);
|
||||
if (!user) {
|
||||
return Promise.reject(new TypeError('User is not a User or User ID'));
|
||||
}
|
||||
// API
|
||||
// if (!this.recipients.get(user)) return Promise.reject(new Error('USER_NOT_IN_GROUP_DM_CHANNEL'));
|
||||
await this.client.api.channels[this.id].recipients[user].delete();
|
||||
this._recipients = this._recipients.filter(r => r.id !== user);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames this Group DM Channel.
|
||||
* @param {?string} name Name of the channel
|
||||
* @returns {Promise<PartialGroupDMChannel>}
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
*/
|
||||
setName(name) {
|
||||
return this.edit({ name });
|
||||
@ -198,7 +215,7 @@ class PartialGroupDMChannel extends Channel {
|
||||
/**
|
||||
* Sets the icon of this Group DM Channel.
|
||||
* @param {?(Base64Resolvable|BufferResolvable)} icon Icon of the channel
|
||||
* @returns {Promise<PartialGroupDMChannel>}
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
*/
|
||||
setIcon(icon) {
|
||||
return this.edit({ icon });
|
||||
@ -207,22 +224,38 @@ class PartialGroupDMChannel extends Channel {
|
||||
/**
|
||||
* Changes the owner of this Group DM Channel.
|
||||
* @param {UserResolvable} user User to transfer ownership to
|
||||
* @returns {Promise<PartialGroupDMChannel>}
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
*/
|
||||
setOwner(user) {
|
||||
const id = this.client.users.resolveId(user);
|
||||
if (!id) {
|
||||
throw new TypeError('User is not a User or User ID');
|
||||
}
|
||||
if (this.ownerId !== this.client.user.id) {
|
||||
throw new Error('NOT_OWNER_GROUP_DM_CHANNEL');
|
||||
}
|
||||
if (this.ownerId === id) {
|
||||
return this;
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
return this.edit({ owner: id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a user to this Group DM Channel.
|
||||
* @param {UserResolvable} user User to add to the group
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
*/
|
||||
async addUser(user) {
|
||||
user = this.client.users.resolveId(user);
|
||||
await this.client.api.channels[this.id].recipients[user].put();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a user from this Group DM Channel.
|
||||
* @param {UserResolvable} user User to remove from the group
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
*/
|
||||
async removeUser(user) {
|
||||
user = this.client.users.resolveId(user);
|
||||
await this.client.api.channels[this.id].recipients[user].delete();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the invite for this Group DM Channel.
|
||||
* @returns {Promise<Invite>}
|
||||
@ -233,131 +266,35 @@ class PartialGroupDMChannel extends Channel {
|
||||
max_age: 86400,
|
||||
},
|
||||
});
|
||||
const invite = new Invite(this.client, inviteCode);
|
||||
this.invites.set(invite.code, invite);
|
||||
return invite;
|
||||
return new Invite(this.client, inviteCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the invites for this Group DM Channel.
|
||||
* @param {boolean} force Using API to fetch invites or cache
|
||||
* @returns {Promise<Collection<string, Invite>>}
|
||||
*/
|
||||
async fetchInvite(force = false) {
|
||||
if (this.ownerId !== this.client.user.id) {
|
||||
return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
|
||||
}
|
||||
if (!force && this.invites.size) return this.invites;
|
||||
async fetchAllInvite() {
|
||||
const invites = await this.client.api.channels(this.id).invites.get();
|
||||
await Promise.all(invites.map(invite => this.invites.set(invite.code, new Invite(this.client, invite))));
|
||||
return this.invites;
|
||||
return new Collection(invites.map(invite => [invite.code, new Invite(this.client, invite)]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete invites from this Group DM Channel.
|
||||
* @param {Invite} invite Invite to add to the channel
|
||||
* @returns {Promise<PartialGroupDMChannel>}
|
||||
* @param {InviteResolvable} invite Invite to add to the channel
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
*/
|
||||
async removeInvite(invite) {
|
||||
if (this.ownerId !== this.client.user.id) {
|
||||
return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
|
||||
}
|
||||
if (!(invite instanceof Invite)) {
|
||||
return Promise.reject(new TypeError('Invite is not an instance of Discord.Invite'));
|
||||
}
|
||||
await this.client.api.channels(this.id).invites[invite.code].delete();
|
||||
this.invites.delete(invite.code);
|
||||
// Resolve
|
||||
let code = invite?.code;
|
||||
if (!code && URL.canParse(invite)) code = new URL(invite).pathname.slice(1);
|
||||
else code = invite;
|
||||
await this.client.api.channels(this.id).invites[invite].delete();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Leave this Group DM Channel.
|
||||
* @param {?boolean} slient Leave without notifying other members
|
||||
* @returns {Promise<Channel>}
|
||||
* @example
|
||||
* // Delete the channel
|
||||
* channel.delete()
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async delete(slient = false) {
|
||||
if (typeof slient === 'boolean' && slient) {
|
||||
await this.client.api.channels(this.id).delete({
|
||||
query: {
|
||||
silent: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await this.client.api.channels(this.id).delete();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
/* eslint-disable no-empty-function */
|
||||
get lastMessage() {}
|
||||
get lastPinAt() {}
|
||||
send() {}
|
||||
sendTyping() {}
|
||||
|
||||
/**
|
||||
* @typedef {Object} CallOptions
|
||||
* @property {boolean} [selfDeaf] Whether to deafen yourself
|
||||
* @property {boolean} [selfMute] Whether to mute yourself
|
||||
* @property {boolean} [ring=true] Emit a ringtone
|
||||
*/
|
||||
// Testing feature: Call
|
||||
// URL: https://discord.com/api/v9/channels/DMchannelId/call/ring
|
||||
/**
|
||||
* Call this Group DMChannel. Return discordjs/voice VoiceConnection
|
||||
* @param {CallOptions} options Options for the call
|
||||
* @returns {Promise<VoiceConnection>}
|
||||
*/
|
||||
call(options = {}) {
|
||||
options = Object.assign(
|
||||
{
|
||||
ring: true,
|
||||
},
|
||||
options || {},
|
||||
);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.client.options.patchVoice) {
|
||||
reject(
|
||||
new Error(
|
||||
'VOICE_NOT_PATCHED',
|
||||
'Enable voice patching in client options\nhttps://discordjs-self-v13.netlify.app/#/docs/docs/main/typedef/ClientOptions',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (options.ring) {
|
||||
this.client.api.channels(this.id).call.ring.post({
|
||||
data: {
|
||||
recipients: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
const connection = joinVoiceChannel({
|
||||
channelId: this.id,
|
||||
guildId: null,
|
||||
adapterCreator: this.voiceAdapterCreator,
|
||||
selfDeaf: options.selfDeaf ?? false,
|
||||
selfMute: options.selfMute ?? false,
|
||||
});
|
||||
entersState(connection, VoiceConnectionStatus.Ready, 30000)
|
||||
.then(connection => {
|
||||
resolve(connection);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.destroy();
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ring the user's phone / PC (call)
|
||||
* @param {UserResolvable[]} recipients Array of recipients
|
||||
* @param {UserResolvable[]} [recipients] Array of recipients
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
ring(recipients) {
|
||||
@ -382,6 +319,7 @@ class PartialGroupDMChannel extends Channel {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The user in this voice-based channel
|
||||
* @type {Collection<Snowflake, User>}
|
||||
@ -396,18 +334,7 @@ class PartialGroupDMChannel extends Channel {
|
||||
}
|
||||
return coll;
|
||||
}
|
||||
/**
|
||||
* Get connection to current call
|
||||
* @type {?VoiceConnection}
|
||||
* @readonly
|
||||
*/
|
||||
get voiceConnection() {
|
||||
const check = this.client.callVoice?.joinConfig?.channelId == this.id;
|
||||
if (check) {
|
||||
return this.client.callVoice;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current shard
|
||||
* @type {WebSocketShard}
|
||||
@ -416,6 +343,7 @@ class PartialGroupDMChannel extends Channel {
|
||||
get shard() {
|
||||
return this.client.ws.shards.first();
|
||||
}
|
||||
|
||||
/**
|
||||
* The voice state adapter for this client that can be used with @discordjs/voice to play audio in DM / Group DM channels.
|
||||
* @type {?Function}
|
||||
@ -436,14 +364,24 @@ class PartialGroupDMChannel extends Channel {
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
/* eslint-disable no-empty-function */
|
||||
get lastMessage() {}
|
||||
get lastPinAt() {}
|
||||
send() {}
|
||||
sendTyping() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
// Doesn't work on DM channels; setRateLimitPerUser() {}
|
||||
// Doesn't work on DM channels; setNSFW() {}
|
||||
}
|
||||
|
||||
TextBasedChannel.applyToClass(PartialGroupDMChannel, true, [
|
||||
'bulkDelete',
|
||||
TextBasedChannel.applyToClass(GroupDMChannel, true, [
|
||||
'fetchWebhooks',
|
||||
'createWebhook',
|
||||
'setRateLimitPerUser',
|
||||
'setNSFW',
|
||||
]);
|
||||
|
||||
module.exports = PartialGroupDMChannel;
|
||||
module.exports = GroupDMChannel;
|
@ -11,13 +11,13 @@ const Webhook = require('./Webhook');
|
||||
const WelcomeScreen = require('./WelcomeScreen');
|
||||
const { Error } = require('../errors');
|
||||
const AutoModerationRuleManager = require('../managers/AutoModerationRuleManager');
|
||||
const GuildApplicationCommandManager = require('../managers/GuildApplicationCommandManager');
|
||||
const GuildBanManager = require('../managers/GuildBanManager');
|
||||
const GuildChannelManager = require('../managers/GuildChannelManager');
|
||||
const GuildEmojiManager = require('../managers/GuildEmojiManager');
|
||||
const GuildInviteManager = require('../managers/GuildInviteManager');
|
||||
const GuildMemberManager = require('../managers/GuildMemberManager');
|
||||
const GuildScheduledEventManager = require('../managers/GuildScheduledEventManager');
|
||||
const GuildSettingManager = require('../managers/GuildSettingManager');
|
||||
const GuildStickerManager = require('../managers/GuildStickerManager');
|
||||
const PresenceManager = require('../managers/PresenceManager');
|
||||
const RoleManager = require('../managers/RoleManager');
|
||||
@ -33,7 +33,6 @@ const {
|
||||
PremiumTiers,
|
||||
} = require('../util/Constants');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const SystemChannelFlags = require('../util/SystemChannelFlags');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
@ -60,55 +59,61 @@ class Guild extends AnonymousGuild {
|
||||
super(client, data, false);
|
||||
|
||||
/**
|
||||
* A manager of the members belonging to this guild.
|
||||
* A manager of the application commands belonging to this guild
|
||||
* @type {GuildApplicationCommandManager}
|
||||
*/
|
||||
this.commands = new GuildApplicationCommandManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the members belonging to this guild
|
||||
* @type {GuildMemberManager}
|
||||
*/
|
||||
this.members = new GuildMemberManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the channels belonging to this guild.
|
||||
* A manager of the channels belonging to this guild
|
||||
* @type {GuildChannelManager}
|
||||
*/
|
||||
this.channels = new GuildChannelManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the bans belonging to this guild.
|
||||
* A manager of the bans belonging to this guild
|
||||
* @type {GuildBanManager}
|
||||
*/
|
||||
this.bans = new GuildBanManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the roles belonging to this guild.
|
||||
* A manager of the roles belonging to this guild
|
||||
* @type {RoleManager}
|
||||
*/
|
||||
this.roles = new RoleManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the presences belonging to this guild.
|
||||
* A manager of the presences belonging to this guild
|
||||
* @type {PresenceManager}
|
||||
*/
|
||||
this.presences = new PresenceManager(this.client);
|
||||
|
||||
/**
|
||||
* A manager of the voice states of this guild.
|
||||
* A manager of the voice states of this guild
|
||||
* @type {VoiceStateManager}
|
||||
*/
|
||||
this.voiceStates = new VoiceStateManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the stage instances of this guild.
|
||||
* A manager of the stage instances of this guild
|
||||
* @type {StageInstanceManager}
|
||||
*/
|
||||
this.stageInstances = new StageInstanceManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the invites of this guild.
|
||||
* A manager of the invites of this guild
|
||||
* @type {GuildInviteManager}
|
||||
*/
|
||||
this.invites = new GuildInviteManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the scheduled events of this guild.
|
||||
* A manager of the scheduled events of this guild
|
||||
* @type {GuildScheduledEventManager}
|
||||
*/
|
||||
this.scheduledEvents = new GuildScheduledEventManager(this);
|
||||
@ -120,10 +125,9 @@ class Guild extends AnonymousGuild {
|
||||
this.autoModerationRules = new AutoModerationRuleManager(this);
|
||||
|
||||
if (!data) return;
|
||||
|
||||
if (data.unavailable) {
|
||||
/**
|
||||
* Whether the guild is available to access. If it is not available, it indicates a server outage.
|
||||
* Whether the guild is available to access. If it is not available, it indicates a server outage
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.available = false;
|
||||
@ -137,12 +141,10 @@ class Guild extends AnonymousGuild {
|
||||
* @type {number}
|
||||
*/
|
||||
this.shardId = data.shardId;
|
||||
|
||||
this.settings = new GuildSettingManager(this.client, this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the structure has been deleted.
|
||||
* Whether or not the structure has been deleted
|
||||
* @type {boolean}
|
||||
* @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
|
||||
*/
|
||||
@ -193,7 +195,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('discovery_splash' in data) {
|
||||
/**
|
||||
* The hash of the guild discovery splash image.
|
||||
* The hash of the guild discovery splash image
|
||||
* @type {?string}
|
||||
*/
|
||||
this.discoverySplash = data.discovery_splash;
|
||||
@ -201,7 +203,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('member_count' in data) {
|
||||
/**
|
||||
* The full amount of members in this guild.
|
||||
* The full amount of members in this guild
|
||||
* @type {number}
|
||||
*/
|
||||
this.memberCount = data.member_count;
|
||||
@ -209,7 +211,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('large' in data) {
|
||||
/**
|
||||
* Whether the guild is "large" (has more than {@link WebsocketOptions large_threshold} members, 50 by default).
|
||||
* Whether the guild is "large" (has more than {@link WebsocketOptions large_threshold} members, 50 by default)
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.large = Boolean(data.large);
|
||||
@ -217,7 +219,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('premium_progress_bar_enabled' in data) {
|
||||
/**
|
||||
* Whether this guild has its premium (boost) progress bar enabled.
|
||||
* Whether this guild has its premium (boost) progress bar enabled
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.premiumProgressBarEnabled = data.premium_progress_bar_enabled;
|
||||
@ -228,9 +230,6 @@ class Guild extends AnonymousGuild {
|
||||
* * ANIMATED_ICON
|
||||
* * AUTO_MODERATION
|
||||
* * BANNER
|
||||
* * CLYDE_ENABLED
|
||||
* <warn> `CLYDE_ENABLED` is now an experimental feature of Discord.
|
||||
* See [this](https://rollouts.advaith.io/#2023-03_clyde_ai) for more information.</warn>
|
||||
* * COMMERCE
|
||||
* * COMMUNITY
|
||||
* * CREATOR_MONETIZABLE_PROVISIONAL
|
||||
@ -254,9 +253,9 @@ class Guild extends AnonymousGuild {
|
||||
* * MORE_STICKERS
|
||||
* * THREE_DAY_THREAD_ARCHIVE
|
||||
* * SEVEN_DAY_THREAD_ARCHIVE
|
||||
* * RAID_ALERTS_DISABLED
|
||||
* * PRIVATE_THREADS
|
||||
* * ROLE_ICONS
|
||||
* * RAID_ALERTS_DISABLED
|
||||
* * ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE
|
||||
* * ROLE_SUBSCRIPTIONS_ENABLED
|
||||
* @typedef {string} Features
|
||||
@ -265,7 +264,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('application_id' in data) {
|
||||
/**
|
||||
* The id of the application that created this guild (if applicable).
|
||||
* The id of the application that created this guild (if applicable)
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.applicationId = data.application_id;
|
||||
@ -273,7 +272,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('afk_timeout' in data) {
|
||||
/**
|
||||
* The time in seconds before a user is counted as "away from keyboard".
|
||||
* The time in seconds before a user is counted as "away from keyboard"
|
||||
* @type {?number}
|
||||
*/
|
||||
this.afkTimeout = data.afk_timeout;
|
||||
@ -281,7 +280,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('afk_channel_id' in data) {
|
||||
/**
|
||||
* The id of the voice channel where AFK members are moved.
|
||||
* The id of the voice channel where AFK members are moved
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.afkChannelId = data.afk_channel_id;
|
||||
@ -289,7 +288,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('system_channel_id' in data) {
|
||||
/**
|
||||
* The system channel's id.
|
||||
* The system channel's id
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.systemChannelId = data.system_channel_id;
|
||||
@ -297,7 +296,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('premium_tier' in data) {
|
||||
/**
|
||||
* The premium tier of this guild.
|
||||
* The premium tier of this guild
|
||||
* @type {PremiumTier}
|
||||
*/
|
||||
this.premiumTier = PremiumTiers[data.premium_tier];
|
||||
@ -305,7 +304,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('widget_enabled' in data) {
|
||||
/**
|
||||
* Whether widget images are enabled on this guild.
|
||||
* Whether widget images are enabled on this guild
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.widgetEnabled = data.widget_enabled;
|
||||
@ -313,7 +312,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('widget_channel_id' in data) {
|
||||
/**
|
||||
* The widget channel's id, if enabled.
|
||||
* The widget channel's id, if enabled
|
||||
* @type {?string}
|
||||
*/
|
||||
this.widgetChannelId = data.widget_channel_id;
|
||||
@ -321,7 +320,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('explicit_content_filter' in data) {
|
||||
/**
|
||||
* The explicit content filter level of the guild.
|
||||
* The explicit content filter level of the guild
|
||||
* @type {ExplicitContentFilterLevel}
|
||||
*/
|
||||
this.explicitContentFilter = ExplicitContentFilterLevels[data.explicit_content_filter];
|
||||
@ -329,7 +328,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('mfa_level' in data) {
|
||||
/**
|
||||
* The required MFA level for this guild.
|
||||
* The required MFA level for this guild
|
||||
* @type {MFALevel}
|
||||
*/
|
||||
this.mfaLevel = MFALevels[data.mfa_level];
|
||||
@ -337,7 +336,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('joined_at' in data) {
|
||||
/**
|
||||
* The timestamp the client user joined the guild at.
|
||||
* The timestamp the client user joined the guild at
|
||||
* @type {number}
|
||||
*/
|
||||
this.joinedTimestamp = new Date(data.joined_at).getTime();
|
||||
@ -345,7 +344,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('default_message_notifications' in data) {
|
||||
/**
|
||||
* The default message notification level of the guild.
|
||||
* The default message notification level of the guild
|
||||
* @type {DefaultMessageNotificationLevel}
|
||||
*/
|
||||
this.defaultMessageNotifications = DefaultMessageNotificationLevels[data.default_message_notifications];
|
||||
@ -353,7 +352,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('system_channel_flags' in data) {
|
||||
/**
|
||||
* The value set for the guild's system channel flags.
|
||||
* The value set for the guild's system channel flags
|
||||
* @type {Readonly<SystemChannelFlags>}
|
||||
*/
|
||||
this.systemChannelFlags = new SystemChannelFlags(data.system_channel_flags).freeze();
|
||||
@ -361,7 +360,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('max_members' in data) {
|
||||
/**
|
||||
* The maximum amount of members the guild can have.
|
||||
* The maximum amount of members the guild can have
|
||||
* @type {?number}
|
||||
*/
|
||||
this.maximumMembers = data.max_members;
|
||||
@ -371,8 +370,8 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('max_presences' in data) {
|
||||
/**
|
||||
* The maximum amount of presences the guild can have.
|
||||
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter.</info>
|
||||
* The maximum amount of presences the guild can have
|
||||
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info>
|
||||
* @type {?number}
|
||||
*/
|
||||
this.maximumPresences = data.max_presences ?? 25_000;
|
||||
@ -402,8 +401,8 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('approximate_member_count' in data) {
|
||||
/**
|
||||
* The approximate amount of members the guild has.
|
||||
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter.</info>
|
||||
* The approximate amount of members the guild has
|
||||
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info>
|
||||
* @type {?number}
|
||||
*/
|
||||
this.approximateMemberCount = data.approximate_member_count;
|
||||
@ -413,8 +412,8 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('approximate_presence_count' in data) {
|
||||
/**
|
||||
* The approximate amount of presences the guild has.
|
||||
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter.</info>
|
||||
* The approximate amount of presences the guild has
|
||||
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info>
|
||||
* @type {?number}
|
||||
*/
|
||||
this.approximatePresenceCount = data.approximate_presence_count;
|
||||
@ -423,15 +422,15 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* The use count of the vanity URL code of the guild, if any.
|
||||
* <info>You will need to fetch this parameter using {@link Guild#fetchVanityData} if you want to receive it.</info>
|
||||
* The use count of the vanity URL code of the guild, if any
|
||||
* <info>You will need to fetch this parameter using {@link Guild#fetchVanityData} if you want to receive it</info>
|
||||
* @type {?number}
|
||||
*/
|
||||
this.vanityURLUses ??= null;
|
||||
|
||||
if ('rules_channel_id' in data) {
|
||||
/**
|
||||
* The rules channel's id for the guild.
|
||||
* The rules channel's id for the guild
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.rulesChannelId = data.rules_channel_id;
|
||||
@ -439,7 +438,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('public_updates_channel_id' in data) {
|
||||
/**
|
||||
* The community updates channel's id for the guild.
|
||||
* The community updates channel's id for the guild
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.publicUpdatesChannelId = data.public_updates_channel_id;
|
||||
@ -447,7 +446,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('preferred_locale' in data) {
|
||||
/**
|
||||
* The preferred locale of the guild, defaults to `en-US`.
|
||||
* The preferred locale of the guild, defaults to `en-US`
|
||||
* @type {Locale}
|
||||
* @see {@link https://discord.com/developers/docs/reference#locales}
|
||||
*/
|
||||
@ -489,7 +488,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if ('owner_id' in data) {
|
||||
/**
|
||||
* The user id of this guild's owner.
|
||||
* The user id of this guild's owner
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.ownerId = data.owner_id;
|
||||
@ -524,7 +523,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if (!this.emojis) {
|
||||
/**
|
||||
* A manager of the emojis belonging to this guild.
|
||||
* A manager of the emojis belonging to this guild
|
||||
* @type {GuildEmojiManager}
|
||||
*/
|
||||
this.emojis = new GuildEmojiManager(this);
|
||||
@ -538,13 +537,11 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
if (!this.stickers) {
|
||||
/**
|
||||
* A manager of the stickers belonging to this guild.
|
||||
* A manager of the stickers belonging to this guild
|
||||
* @type {GuildStickerManager}
|
||||
*/
|
||||
this.stickers = new GuildStickerManager(this);
|
||||
if (data.stickers) {
|
||||
for (const sticker of data.stickers) this.stickers._add(sticker);
|
||||
}
|
||||
if (data.stickers) for (const sticker of data.stickers) this.stickers._add(sticker);
|
||||
} else if (data.stickers) {
|
||||
this.client.actions.GuildStickersUpdate.handle({
|
||||
guild_id: this.id,
|
||||
@ -554,7 +551,7 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the client user joined the guild.
|
||||
* The time the client user joined the guild
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
@ -582,7 +579,7 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* AFK voice channel for this guild.
|
||||
* AFK voice channel for this guild
|
||||
* @type {?VoiceChannel}
|
||||
* @readonly
|
||||
*/
|
||||
@ -591,7 +588,7 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* System channel for this guild.
|
||||
* System channel for this guild
|
||||
* @type {?TextChannel}
|
||||
* @readonly
|
||||
*/
|
||||
@ -609,7 +606,7 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Widget channel for this guild.
|
||||
* Widget channel for this guild
|
||||
* @type {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel)}
|
||||
* @readonly
|
||||
*/
|
||||
@ -618,7 +615,7 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rules channel for this guild.
|
||||
* Rules channel for this guild
|
||||
* @type {?TextChannel}
|
||||
* @readonly
|
||||
*/
|
||||
@ -627,7 +624,7 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public updates channel for this guild.
|
||||
* Public updates channel for this guild
|
||||
* @type {?TextChannel}
|
||||
* @readonly
|
||||
*/
|
||||
@ -636,7 +633,7 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* The client user as a GuildMember of this guild.
|
||||
* The client user as a GuildMember of this guild
|
||||
* @type {?GuildMember}
|
||||
* @deprecated Use {@link GuildMemberManager#me} instead.
|
||||
* @readonly
|
||||
@ -651,7 +648,7 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum bitrate available for this guild.
|
||||
* The maximum bitrate available for this guild
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
@ -672,30 +669,6 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mute a guild
|
||||
* @param {boolean} mute Weather or not you want to mute the guild
|
||||
* @param {?number} time The amount of time you want to mute the server for in seconds
|
||||
* @returns {boolean} true if it worked and false if it didn't
|
||||
* @example
|
||||
* guild.mute(true, 3600) // mutes the guild for an hour
|
||||
* guild.mute(true, -1) // mutes the guild forever
|
||||
* guild.mute(false); // unmutes the guild
|
||||
*/
|
||||
async mute(mute, time) {
|
||||
if (mute && time == null) return false;
|
||||
if (time == null && !mute) await this.client.api.guilds(this.id).settings.patch({ muted: false });
|
||||
let ms = time * 1000;
|
||||
let date = new Date(Date.now() + ms).toISOString();
|
||||
return this.settings.edit({
|
||||
mute_config: {
|
||||
end_time: date,
|
||||
selected_time_window: time,
|
||||
},
|
||||
muted: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a collection of integrations to this guild.
|
||||
* Resolves with a collection mapping integrations by their ids.
|
||||
@ -762,7 +735,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Fetches the vanity URL invite object to this guild.
|
||||
* Resolves with an object containing the vanity URL invite code and the use count.
|
||||
* Resolves with an object containing the vanity URL invite code and the use count
|
||||
* @returns {Promise<Vanity>}
|
||||
* @example
|
||||
* // Fetch invite data
|
||||
@ -792,9 +765,7 @@ class Guild extends AnonymousGuild {
|
||||
async fetchWebhooks() {
|
||||
const apiHooks = await this.client.api.guilds(this.id).webhooks.get();
|
||||
const hooks = new Collection();
|
||||
for (const hook of apiHooks) {
|
||||
hooks.set(hook.id, new Webhook(this.client, hook));
|
||||
}
|
||||
for (const hook of apiHooks) hooks.set(hook.id, new Webhook(this.client, hook));
|
||||
return hooks;
|
||||
}
|
||||
|
||||
@ -812,14 +783,14 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for the Guild Widget Settings object.
|
||||
* Data for the Guild Widget Settings object
|
||||
* @typedef {Object} GuildWidgetSettings
|
||||
* @property {boolean} enabled Whether the widget is enabled
|
||||
* @property {?GuildChannel} channel The widget invite channel
|
||||
*/
|
||||
|
||||
/**
|
||||
* The Guild Widget Settings object.
|
||||
* The Guild Widget Settings object
|
||||
* @typedef {Object} GuildWidgetSettingsData
|
||||
* @property {boolean} enabled Whether the widget is enabled
|
||||
* @property {?GuildChannelResolvable} channel The widget invite channel
|
||||
@ -874,6 +845,7 @@ class Guild extends AnonymousGuild {
|
||||
action_type: typeof type === 'string' ? GuildAuditLogs.Actions[type] : type,
|
||||
},
|
||||
});
|
||||
|
||||
return GuildAuditLogs.build(this, data);
|
||||
}
|
||||
|
||||
@ -891,8 +863,8 @@ class Guild extends AnonymousGuild {
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [splash] The invite splash image of the guild
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [discoverySplash] The discovery splash image of the guild
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [banner] The banner of the guild
|
||||
* @property {?(DefaultMessageNotificationLevel|number)} [defaultMessageNotifications] The default message notification
|
||||
* level of the guild
|
||||
* @property {?(DefaultMessageNotificationLevel|number)} [defaultMessageNotifications] The default message
|
||||
* notification level of the guild
|
||||
* @property {SystemChannelFlagsResolvable} [systemChannelFlags] The system channel flags of the guild
|
||||
* @property {?TextChannelResolvable} [rulesChannel] The rules channel of the guild
|
||||
* @property {?TextChannelResolvable} [publicUpdatesChannel] The community updates channel of the guild
|
||||
@ -946,19 +918,13 @@ class Guild extends AnonymousGuild {
|
||||
_data.system_channel_id = this.client.channels.resolveId(data.systemChannel);
|
||||
}
|
||||
if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout);
|
||||
if (typeof data.icon !== 'undefined') {
|
||||
_data.icon = await DataResolver.resolveImage(data.icon);
|
||||
}
|
||||
if (typeof data.icon !== 'undefined') _data.icon = await DataResolver.resolveImage(data.icon);
|
||||
if (data.owner) _data.owner_id = this.client.users.resolveId(data.owner);
|
||||
if (typeof data.splash !== 'undefined') {
|
||||
_data.splash = await DataResolver.resolveImage(data.splash);
|
||||
}
|
||||
if (typeof data.splash !== 'undefined') _data.splash = await DataResolver.resolveImage(data.splash);
|
||||
if (typeof data.discoverySplash !== 'undefined') {
|
||||
_data.discovery_splash = await DataResolver.resolveImage(data.discoverySplash);
|
||||
}
|
||||
if (typeof data.banner !== 'undefined') {
|
||||
_data.banner = await DataResolver.resolveImage(data.banner);
|
||||
}
|
||||
if (typeof data.banner !== 'undefined') _data.banner = await DataResolver.resolveImage(data.banner);
|
||||
if (typeof data.explicitContentFilter !== 'undefined') {
|
||||
_data.explicit_content_filter =
|
||||
typeof data.explicitContentFilter === 'number'
|
||||
@ -990,15 +956,13 @@ class Guild extends AnonymousGuild {
|
||||
if (typeof data.safetyAlertsChannel !== 'undefined') {
|
||||
_data.safety_alerts_channel_id = this.client.channels.resolveId(data.safetyAlertsChannel);
|
||||
}
|
||||
if ('premiumProgressBarEnabled' in data) {
|
||||
_data.premium_progress_bar_enabled = data.premiumProgressBarEnabled;
|
||||
}
|
||||
if ('premiumProgressBarEnabled' in data) _data.premium_progress_bar_enabled = data.premiumProgressBarEnabled;
|
||||
const newData = await this.client.api.guilds(this.id).patch({ data: _data, reason });
|
||||
return this.client.actions.GuildUpdate.handle(newData).updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Welcome channel data.
|
||||
* Welcome channel data
|
||||
* @typedef {Object} WelcomeChannelData
|
||||
* @property {string} description The description to show for this welcome channel
|
||||
* @property {TextChannel|NewsChannel|StoreChannel|Snowflake} channel The channel to link for this welcome channel
|
||||
@ -1006,7 +970,7 @@ class Guild extends AnonymousGuild {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Welcome screen edit data.
|
||||
* Welcome screen edit data
|
||||
* @typedef {Object} WelcomeScreenEditData
|
||||
* @property {boolean} [enabled] Whether the welcome screen is enabled
|
||||
* @property {string} [description] The description for the welcome screen
|
||||
@ -1030,7 +994,7 @@ class Guild extends AnonymousGuild {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updates the guild's welcome screen.
|
||||
* Updates the guild's welcome screen
|
||||
* @param {WelcomeScreenEditData} data Data to edit the welcome screen with
|
||||
* @returns {Promise<WelcomeScreen>}
|
||||
* @example
|
||||
@ -1080,7 +1044,7 @@ class Guild extends AnonymousGuild {
|
||||
/* eslint-disable max-len */
|
||||
/**
|
||||
* Edits the setting of the default message notifications of the guild.
|
||||
* @param {DefaultMessageNotificationLevel|number} defaultMessageNotifications The new default message notification level of the guild
|
||||
* @param {?(DefaultMessageNotificationLevel|number)} defaultMessageNotifications The new default message notification level of the guild
|
||||
* @param {string} [reason] Reason for changing the setting of the default message notifications
|
||||
* @returns {Promise<Guild>}
|
||||
*/
|
||||
@ -1116,7 +1080,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Edits the verification level of the guild.
|
||||
* @param {(VerificationLevel|number)} verificationLevel The new verification level of the guild
|
||||
* @param {?(VerificationLevel|number)} verificationLevel The new verification level of the guild
|
||||
* @param {string} [reason] Reason for changing the guild's verification level
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
@ -1236,7 +1200,7 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new guild's banner.
|
||||
* Sets a new guild banner.
|
||||
* @param {?(Base64Resolvable|BufferResolvable)} banner The new banner of the guild
|
||||
* @param {string} [reason] Reason for changing the guild's banner
|
||||
* @returns {Promise<Guild>}
|
||||
@ -1263,47 +1227,6 @@ class Guild extends AnonymousGuild {
|
||||
setRulesChannel(rulesChannel, reason) {
|
||||
return this.edit({ rulesChannel }, reason);
|
||||
}
|
||||
/**
|
||||
* Change Guild Position (from * to Folder or Home).
|
||||
* @param {number} position Guild Position
|
||||
* * **WARNING**: Type = `FOLDER`, newPosition is the guild's index in the Folder.
|
||||
* @param {string|number} type Move to folder or home
|
||||
* * `FOLDER`: 1
|
||||
* * `HOME`: 2
|
||||
* @param {string|number|void|null} folderID If you want to move to folder
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Move guild to folderID 123456, index 1
|
||||
* guild.setPosition(1, 'FOLDER', 123456)
|
||||
* .then(guild => console.log(`Guild moved to folderID ${guild.folder.folderId}`));
|
||||
*/
|
||||
async setPosition(position, type, folderID) {
|
||||
if (type == 1 || `${type}`.toUpperCase() === 'FOLDER') {
|
||||
folderID = folderID || this.folder.folderId;
|
||||
if (!['number', 'string'].includes(typeof folderID)) {
|
||||
throw new TypeError('INVALID_TYPE', 'folderID', 'String | Number');
|
||||
}
|
||||
// Get Data from Folder ID
|
||||
const folder = await this.client.settings.rawSetting.guild_folders.find(obj => obj.id == folderID);
|
||||
if (!folder) throw new Error('FOLDER_NOT_FOUND');
|
||||
if (folder.guild_ids.length - 1 < position || position < 0) {
|
||||
throw new Error('FOLDER_POSITION_INVALID');
|
||||
}
|
||||
if (position !== folder.guild_ids.indexOf(this.id)) {
|
||||
await this.client.settings.guildChangePosition(this.id, position, 1, folderID);
|
||||
}
|
||||
} else if (type == 2 || `${type}`.toUpperCase() === 'HOME') {
|
||||
if (this.client.settings.guild_positions - 1 < position || position < 0) {
|
||||
throw new Error('FOLDER_POSITION_INVALID');
|
||||
}
|
||||
if (position !== this.position) {
|
||||
await this.client.settings.guildChangePosition(this.id, position, 2, null);
|
||||
}
|
||||
} else {
|
||||
throw new TypeError('INVALID_TYPE', 'type', '`Folder`| `Home`');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the community updates channel of the guild.
|
||||
@ -1351,7 +1274,7 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the enabled state of the guild's premium progress bar.
|
||||
* Edits the enabled state of the guild's premium progress bar
|
||||
* @param {boolean} [enabled=true] The new enabled state of the guild's premium progress bar
|
||||
* @param {string} [reason] Reason for changing the state of the guild's premium progress bar
|
||||
* @returns {Promise<Guild>}
|
||||
@ -1378,7 +1301,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Batch-updates the guild's channels' positions.
|
||||
* <info>Only one channel's parent can be changed at a time.</info>
|
||||
* <info>Only one channel's parent can be changed at a time</info>
|
||||
* @param {ChannelPosition[]} channelPositions Channel positions to update
|
||||
* @returns {Promise<Guild>}
|
||||
* @deprecated Use {@link GuildChannelManager#setPositions} instead
|
||||
@ -1401,14 +1324,14 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* The data needed for updating a guild role's position.
|
||||
* The data needed for updating a guild role's position
|
||||
* @typedef {Object} GuildRolePosition
|
||||
* @property {RoleResolvable} role The role's id
|
||||
* @property {number} position The position to update
|
||||
*/
|
||||
|
||||
/**
|
||||
* Batch-updates the guild's role positions.
|
||||
* Batch-updates the guild's role positions
|
||||
* @param {GuildRolePosition[]} rolePositions Role positions to update
|
||||
* @returns {Promise<Guild>}
|
||||
* @deprecated Use {@link RoleManager#setPositions} instead
|
||||
@ -1446,6 +1369,7 @@ class Guild extends AnonymousGuild {
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this guild's invites are disabled.
|
||||
* @param {boolean} [disabled=true] Whether the invites are disabled
|
||||
@ -1463,7 +1387,7 @@ class Guild extends AnonymousGuild {
|
||||
* @example
|
||||
* // Leave a guild
|
||||
* guild.leave()
|
||||
* .then(guild => console.log(`Left the guild ${guild.name}`))
|
||||
* .then(guild => console.log(`Left the guild: ${guild.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async leave() {
|
||||
@ -1472,32 +1396,17 @@ class Guild extends AnonymousGuild {
|
||||
return this.client.actions.GuildDelete.handle({ id: this.id }).guild;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the guild as read.
|
||||
* @returns {Promise<undefined>} nothing :)
|
||||
* @example
|
||||
* const guild = client.guilds.fetch('222078108977594368');
|
||||
* guild.read();
|
||||
*/
|
||||
async read() {
|
||||
await this.client.api.guilds(this.id).ack.post();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the guild.
|
||||
* @param {string} [mfaCode] The MFA code for the guild owner
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Delete a guild
|
||||
* guild.delete()
|
||||
* .then(guild => console.log(`Deleted the guild ${guild.name}`))
|
||||
* .then(g => console.log(`Deleted the guild ${g}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async delete(mfaCode) {
|
||||
if ((!mfaCode || typeof mfaCode !== 'string' || mfaCode.length !== 6) && this.client.user.mfaEnabled) {
|
||||
throw new Error('MFA_INVALID');
|
||||
}
|
||||
await this.client.api.guilds(this.id).delete({ data: mfaCode ? { code: mfaCode } : undefined });
|
||||
async delete() {
|
||||
await this.client.api.guilds(this.id).delete();
|
||||
return this.client.actions.GuildDelete.handle({ id: this.id }).guild;
|
||||
}
|
||||
|
||||
@ -1528,14 +1437,41 @@ class Guild extends AnonymousGuild {
|
||||
);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const json = super.toJSON({
|
||||
available: false,
|
||||
createdTimestamp: true,
|
||||
nameAcronym: true,
|
||||
presences: false,
|
||||
voiceStates: false,
|
||||
});
|
||||
json.iconURL = this.iconURL();
|
||||
json.splashURL = this.splashURL();
|
||||
json.discoverySplashURL = this.discoverySplashURL();
|
||||
json.bannerURL = this.bannerURL();
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the guild as read.
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* const guild = client.guilds.cache.get('id');
|
||||
* guild.markAsRead();
|
||||
*/
|
||||
markAsRead() {
|
||||
return this.client.api.guilds(this.id).ack.post();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Community Feature.
|
||||
* @param {boolean} stats True / False to enable / disable Community Feature
|
||||
* @param {TextChannelResolvable} publicUpdatesChannel The community updates channel of the guild
|
||||
* @param {TextChannelResolvable} rulesChannel The new rules channel
|
||||
* @param {string} reason Reason for changing the community feature
|
||||
* @param {GuildTextChannelResolvable} [publicUpdatesChannel] The community updates channel of the guild
|
||||
* @param {GuildTextChannelResolvable} [rulesChannel] The new rules channel
|
||||
* @param {string} [reason] Reason for changing the community feature
|
||||
* @returns {Promise<Guild>}
|
||||
*/
|
||||
async setCommunity(stats = true, publicUpdatesChannel = '1', rulesChannel = '1', reason) {
|
||||
async setCommunity(stats = true, publicUpdatesChannel, rulesChannel, reason) {
|
||||
if (stats) {
|
||||
// Check everyone role
|
||||
const everyoneRole = this.roles.everyone;
|
||||
@ -1543,19 +1479,19 @@ class Guild extends AnonymousGuild {
|
||||
await everyoneRole.setMentionable(false, reason);
|
||||
}
|
||||
// Setting
|
||||
this.edit(
|
||||
return this.edit(
|
||||
{
|
||||
defaultMessageNotifications: 'ONLY_MENTIONS',
|
||||
explicitContentFilter: 'ALL_MEMBERS',
|
||||
features: [...this.features, 'COMMUNITY'],
|
||||
publicUpdatesChannel,
|
||||
rulesChannel,
|
||||
publicUpdatesChannel: this.channels.resolveId(publicUpdatesChannel) || '1',
|
||||
rulesChannel: this.channels.resolveId(rulesChannel) || '1',
|
||||
verificationLevel: VerificationLevels[this.verificationLevel] < 1 ? 'LOW' : this.verificationLevel, // Email
|
||||
},
|
||||
reason,
|
||||
);
|
||||
} else {
|
||||
this.edit(
|
||||
return this.edit(
|
||||
{
|
||||
publicUpdatesChannel: null,
|
||||
rulesChannel: null,
|
||||
@ -1569,60 +1505,23 @@ class Guild extends AnonymousGuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Integrations to the guild.
|
||||
* @param {Snowflake} applicationId Application (ID) target
|
||||
* @returns {Promise<boolean>}
|
||||
* Get the top emojis of this guild.
|
||||
* @returns {Promise<Collection<number, GuildEmoji>>}
|
||||
*/
|
||||
addIntegration(applicationId) {
|
||||
if (!this.me.permissions.has('MANAGE_WEBHOOKS')) {
|
||||
throw new Error('MISSING_PERMISSIONS', 'MANAGE_WEBHOOKS');
|
||||
}
|
||||
if (!this.me.permissions.has('MANAGE_GUILD')) {
|
||||
throw new Error('MISSING_PERMISSIONS', 'MANAGE_GUILD');
|
||||
}
|
||||
if (!applicationId || typeof applicationId !== 'string') throw new TypeError('INVALID_APPLICATION_ID');
|
||||
return this.client.authorizeURL(
|
||||
`https://discord.com/api/oauth2/authorize?client_id=${applicationId}&scope=applications.commands`,
|
||||
{
|
||||
guild_id: this.id,
|
||||
permissions: `0`,
|
||||
authorize: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Bot to the guild.
|
||||
* @param {UserResolvable} bot BotId / ApplicationId
|
||||
* @param {?PermissionResolvable} permissions Permissions
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
addBot(bot, permissions) {
|
||||
if (!this.me.permissions.has('MANAGE_WEBHOOKS')) {
|
||||
throw new Error('MISSING_PERMISSIONS', 'MANAGE_WEBHOOKS');
|
||||
}
|
||||
if (!this.me.permissions.has('MANAGE_GUILD')) {
|
||||
throw new Error('MISSING_PERMISSIONS', 'MANAGE_GUILD');
|
||||
}
|
||||
if (!this.client.options.captchaService) throw new Error('MISSING_CAPTCHA_SERVICE');
|
||||
const botId = this.client.users.resolveId(bot);
|
||||
const permission = new Permissions(Permissions.resolve(permissions ?? 0n));
|
||||
if (!botId) throw new TypeError('INVALID_BOT_ID');
|
||||
// Check permission
|
||||
const selfPerm = this.me.permissions.toArray();
|
||||
const missingPerms = permission.toArray().filter(x => !selfPerm.includes(x));
|
||||
if (missingPerms.length) {
|
||||
throw new Error('MISSING_PERMISSIONS', missingPerms.join(', '));
|
||||
}
|
||||
// Add bot
|
||||
return this.client.authorizeURL(
|
||||
`https://discord.com/api/oauth2/authorize?client_id=${botId}&permissions=${permission.bitfield}&scope=applications.commands%20bot`,
|
||||
{
|
||||
guild_id: this.id,
|
||||
permissions: `${permission.bitfield}`,
|
||||
authorize: true,
|
||||
},
|
||||
);
|
||||
topEmojis() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.api
|
||||
.guilds(this.id)
|
||||
['top-emojis'].get()
|
||||
.then(data => {
|
||||
const emojis = new Collection();
|
||||
for (const emoji of data.items) {
|
||||
emojis.set(emoji.emoji_rank, this.emojis.cache.get(emoji.emoji_id));
|
||||
}
|
||||
resolve(emojis);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1649,21 +1548,6 @@ class Guild extends AnonymousGuild {
|
||||
return data;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const json = super.toJSON({
|
||||
available: false,
|
||||
createdTimestamp: true,
|
||||
nameAcronym: true,
|
||||
presences: false,
|
||||
voiceStates: false,
|
||||
});
|
||||
json.iconURL = this.iconURL();
|
||||
json.splashURL = this.splashURL();
|
||||
json.discoverySplashURL = this.discoverySplashURL();
|
||||
json.bannerURL = this.bannerURL();
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* The voice state adapter for this guild that can be used with @discordjs/voice to play audio in voice
|
||||
* and stage channels.
|
||||
@ -1686,26 +1570,6 @@ class Guild extends AnonymousGuild {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the top emojis of this guild.
|
||||
* @returns {Promise<Collection<number, GuildEmoji>>}
|
||||
*/
|
||||
topEmojis() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.api
|
||||
.guilds(this.id)
|
||||
['top-emojis'].get()
|
||||
.then(data => {
|
||||
const emojis = new Collection();
|
||||
for (const emoji of data.items) {
|
||||
emojis.set(emoji.emoji_rank, this.emojis.cache.get(emoji.emoji_id));
|
||||
}
|
||||
resolve(emojis);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a collection of this guild's roles, sorted by their position and ids.
|
||||
* @returns {Collection<Snowflake, Role>}
|
||||
|
@ -224,7 +224,6 @@ class GuildAuditLogs {
|
||||
this.applicationCommands.set(command.id, new ApplicationCommand(guild.client, command, guild));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached auto moderation rules.
|
||||
* @type {Collection<Snowflake, AutoModerationRule>}
|
||||
@ -487,7 +486,6 @@ class GuildAuditLogsEntry {
|
||||
count: Number(data.options.count),
|
||||
};
|
||||
break;
|
||||
|
||||
case Actions.MESSAGE_PIN:
|
||||
case Actions.MESSAGE_UNPIN:
|
||||
this.extra = {
|
||||
@ -533,13 +531,11 @@ class GuildAuditLogsEntry {
|
||||
channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id },
|
||||
};
|
||||
break;
|
||||
|
||||
case Actions.APPLICATION_COMMAND_PERMISSION_UPDATE:
|
||||
this.extra = {
|
||||
applicationId: data.options.application_id,
|
||||
};
|
||||
break;
|
||||
|
||||
case Actions.AUTO_MODERATION_BLOCK_MESSAGE:
|
||||
case Actions.AUTO_MODERATION_FLAG_TO_CHANNEL:
|
||||
case Actions.AUTO_MODERATION_USER_COMMUNICATION_DISABLED:
|
||||
@ -549,7 +545,6 @@ class GuildAuditLogsEntry {
|
||||
channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id },
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -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;
|
@ -8,7 +8,6 @@ const { Error } = require('../errors');
|
||||
const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager');
|
||||
const GuildMemberFlags = require('../util/GuildMemberFlags');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* @type {WeakSet<GuildMember>}
|
||||
@ -102,7 +101,6 @@ class GuildMember extends Base {
|
||||
this.communicationDisabledUntilTimestamp =
|
||||
data.communication_disabled_until && Date.parse(data.communication_disabled_until);
|
||||
}
|
||||
|
||||
if ('flags' in data) {
|
||||
/**
|
||||
* The flags of this member
|
||||
@ -114,51 +112,6 @@ class GuildMember extends Base {
|
||||
}
|
||||
}
|
||||
|
||||
_ProfilePatch(data) {
|
||||
if ('accent_color' in data) {
|
||||
/**
|
||||
* The member's accent color
|
||||
* <info>The user must be force fetched for this property to be present or be updated</info>
|
||||
* @type {?number}
|
||||
*/
|
||||
this.accentColor = data.accent_color;
|
||||
}
|
||||
if ('banner' in data) {
|
||||
/**
|
||||
* The member's banner hash
|
||||
* <info>The user must be force fetched for this property to be present or be updated</info>
|
||||
* @type {?string}
|
||||
*/
|
||||
this.banner = data.banner;
|
||||
}
|
||||
if ('bio' in data) {
|
||||
/**
|
||||
* The member's biography (About me)
|
||||
* <info>The user must be force fetched for this property to be present or be updated</info>
|
||||
* @type {?string}
|
||||
*/
|
||||
this.bio = data.bio;
|
||||
}
|
||||
if ('theme_colors' in data) {
|
||||
/**
|
||||
* The member's theme colors (Profile theme) [Primary, Accent]
|
||||
* <info>The user must be force fetched for this property to be present or be updated</info>
|
||||
* @type {?Array<number>}
|
||||
*/
|
||||
this.themeColors = data.theme_colors;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The hexadecimal version of the user theme color, with a leading hash [Primary, Accent]
|
||||
* <info>The user must be force fetched for this property to be present or be updated</info>
|
||||
* @type {?Array<string>}
|
||||
* @readonly
|
||||
*/
|
||||
get hexThemeColor() {
|
||||
return this.themeColors?.map(c => `#${c.toString(16).padStart(6, '0')}`) || null;
|
||||
}
|
||||
|
||||
_clone() {
|
||||
const clone = super._clone();
|
||||
clone._roles = this._roles.slice();
|
||||
@ -232,21 +185,6 @@ class GuildMember extends Base {
|
||||
return this.client.rest.cdn.GuildMemberAvatar(this.guild.id, this.id, this.avatar, format, size, dynamic);
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to the user's banner.
|
||||
* <info>This method will throw an error if called before the user is force fetched Profile.
|
||||
* See {@link GuildMember#banner} for more info</info>
|
||||
* @param {ImageURLOptions} [options={}] Options for the Image URL
|
||||
* @returns {?string}
|
||||
*/
|
||||
bannerURL({ format, size, dynamic } = {}) {
|
||||
if (typeof this.banner === 'undefined') {
|
||||
throw new Error('USER_BANNER_NOT_FETCHED');
|
||||
}
|
||||
if (!this.banner) return null;
|
||||
return this.client.rest.cdn.GuildMemberBanner(this.guild.id, this.id, this.banner, format, size, dynamic);
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to the member's guild avatar if they have one.
|
||||
* Otherwise, a link to their {@link User#displayAvatarURL} will be returned.
|
||||
@ -444,79 +382,6 @@ class GuildMember extends Base {
|
||||
return this.edit({ flags, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the guild avatar of the logged in client.
|
||||
* @param {?(BufferResolvable|Base64Resolvable)} avatar The new avatar
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setAvatar(avatar) {
|
||||
if (this.user.id !== this.client.user.id) {
|
||||
throw new Error('ONLY_ME');
|
||||
}
|
||||
if (this.client.user.nitroType !== 'NITRO_BOOST') {
|
||||
throw new Error('NITRO_BOOST_REQUIRED', 'avatar');
|
||||
}
|
||||
return this.edit({ avatar });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the guild banner of the logged in client.
|
||||
* @param {?(BufferResolvable|Base64Resolvable)} banner The new banner
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setBanner(banner) {
|
||||
if (this.user.id !== this.client.user.id) {
|
||||
throw new Error('ONLY_ME');
|
||||
}
|
||||
if (this.client.user.nitroType !== 'NITRO_BOOST') {
|
||||
throw new Error('NITRO_BOOST_REQUIRED', 'banner');
|
||||
}
|
||||
return this.edit({ banner });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Guild About me
|
||||
* @param {string | null} bio Bio to set
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setAboutMe(bio = null) {
|
||||
if (this.user.id !== this.client.user.id) {
|
||||
throw new Error('ONLY_ME');
|
||||
}
|
||||
if (this.client.user.nitroType !== 'NITRO_BOOST') {
|
||||
throw new Error('NITRO_BOOST_REQUIRED', 'bio');
|
||||
}
|
||||
return this.edit({ bio });
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Theme color
|
||||
* @param {ColorResolvable} primary The primary color of the user's profile
|
||||
* @param {ColorResolvable} accent The accent color of the user's profile
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
async setThemeColors(primary, accent) {
|
||||
if (this.user.id !== this.client.user.id) {
|
||||
throw new Error('ONLY_ME');
|
||||
}
|
||||
if (!primary || !accent) throw new Error('PRIMARY_COLOR or ACCENT_COLOR are required.');
|
||||
// Check nitro
|
||||
if (this.nitroType !== 'NITRO_BOOST') {
|
||||
throw new Error('NITRO_BOOST_REQUIRED', 'themeColors');
|
||||
}
|
||||
primary = Util.resolveColor(primary) || this.themeColors ? this.themeColors[0] : 0;
|
||||
accent = Util.resolveColor(accent) || this.themeColors ? this.themeColors[1] : 0;
|
||||
const data_ = await this.client.api.guilds[this.guild.id].profile['@me'].patch({
|
||||
data: {
|
||||
theme_colors: [primary, accent],
|
||||
},
|
||||
});
|
||||
this._ProfilePatch({
|
||||
guild_member_profile: data_,
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DM channel between the client and this member.
|
||||
* @param {boolean} [force=false] Whether to skip the cache check and request the API
|
||||
@ -619,8 +484,6 @@ class GuildMember extends Base {
|
||||
this.joinedTimestamp === member.joinedTimestamp &&
|
||||
this.nickname === member.nickname &&
|
||||
this.avatar === member.avatar &&
|
||||
this.accentColor === member.accentColor &&
|
||||
this.bio === member.bio &&
|
||||
this.pending === member.pending &&
|
||||
this.communicationDisabledUntilTimestamp === member.communicationDisabledUntilTimestamp &&
|
||||
this.flags.equals(member.flags) &&
|
||||
@ -629,14 +492,6 @@ class GuildMember extends Base {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get profile guildMember
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
getProfile() {
|
||||
return this.user.getProfile(this.guild.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object.
|
||||
* @returns {string}
|
||||
@ -659,6 +514,33 @@ class GuildMember extends Base {
|
||||
json.displayAvatarURL = this.displayAvatarURL();
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the guild avatar of the logged in client.
|
||||
* @param {?(BufferResolvable|Base64Resolvable)} avatar The new avatar
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setAvatar(avatar) {
|
||||
return this.edit({ avatar });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the guild banner of the logged in client.
|
||||
* @param {?(BufferResolvable|Base64Resolvable)} banner The new banner
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setBanner(banner) {
|
||||
return this.edit({ banner });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Guild About me
|
||||
* @param {string | null} bio Bio to set
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setAboutMe(bio = null) {
|
||||
return this.edit({ bio });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -269,71 +269,10 @@ class Interaction extends Base {
|
||||
* Indicates whether this interaction is a {@link SelectMenuInteraction}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isAnySelectMenu() {
|
||||
return InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT && typeof this.values !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `STRING_SELECT` type.
|
||||
* @returns {boolean}
|
||||
* @deprecated Use {@link Interaction#isStringSelect()} instead
|
||||
*/
|
||||
isSelectMenu() {
|
||||
return this.isStringSelect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `STRING_SELECT` type.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isStringSelect() {
|
||||
return (
|
||||
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
|
||||
MessageComponentTypes[this.componentType] === MessageComponentTypes.STRING_SELECT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `USER_SELECT` type.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isUserSelect() {
|
||||
return (
|
||||
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
|
||||
MessageComponentTypes[this.componentType] === MessageComponentTypes.USER_SELECT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `ROLE_SELECT` type.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isRoleSelect() {
|
||||
return (
|
||||
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
|
||||
MessageComponentTypes[this.componentType] === MessageComponentTypes.ROLE_SELECT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `MENTIONABLE_SELECT` type.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isMentionableSelect() {
|
||||
return (
|
||||
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
|
||||
MessageComponentTypes[this.componentType] === MessageComponentTypes.MENTIONABLE_SELECT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a {@link SelectMenuInteraction} with a `CHANNEL_SELECT` type.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isChannelSelect() {
|
||||
return (
|
||||
InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
|
||||
MessageComponentTypes[this.componentType] === MessageComponentTypes.CHANNEL_SELECT
|
||||
MessageComponentTypes[this.componentType] === MessageComponentTypes.SELECT_MENU
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,114 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { setTimeout } = require('node:timers');
|
||||
const Base = require('./Base');
|
||||
const { Events } = require('../util/Constants');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
|
||||
/**
|
||||
* Represents a interaction on Discord.
|
||||
* @extends {Base}
|
||||
*/
|
||||
class InteractionResponse extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
/**
|
||||
* The id of the channel the interaction was sent in
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.channelId = data.channelId;
|
||||
|
||||
/**
|
||||
* The id of the guild the interaction was sent in, if any
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.guildId = data.guildId ?? this.channel?.guild?.id ?? null;
|
||||
|
||||
/**
|
||||
* The interaction data was sent in
|
||||
* @type {Object}
|
||||
*/
|
||||
this.sendData = data.metadata;
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
if ('id' in data) {
|
||||
/**
|
||||
* The interaction response's ID
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
}
|
||||
if ('nonce' in data) {
|
||||
/**
|
||||
* The interaction response's nonce
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.nonce = data.nonce;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The timestamp the interaction response was created at
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return SnowflakeUtil.timestampFrom(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the interaction response was created at
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* The channel that the interaction was sent in
|
||||
* @type {TextBasedChannels}
|
||||
* @readonly
|
||||
*/
|
||||
get channel() {
|
||||
return this.client.channels.resolve(this.channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The guild the inteaaction was sent in (if in a guild channel)
|
||||
* @type {?Guild}
|
||||
* @readonly
|
||||
*/
|
||||
get guild() {
|
||||
return this.client.guilds.resolve(this.guildId) ?? this.channel?.guild ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Modal send from interaction
|
||||
* @param {number} time Time to wait for modal
|
||||
* @returns {Modal}
|
||||
*/
|
||||
awaitModal(time) {
|
||||
if (!time || typeof time !== 'number' || time < 0) throw new Error('INVALID_TIME');
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = modal => {
|
||||
timeout.refresh();
|
||||
if (modal.nonce != this.nonce || modal.id != this.id) return;
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
resolve(modal);
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(new Error('MODAL_TIMEOUT'));
|
||||
}, time).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InteractionResponse;
|
@ -1,12 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const Buffer = require('node:buffer').Buffer;
|
||||
const Base = require('./Base');
|
||||
const { GuildScheduledEvent } = require('./GuildScheduledEvent');
|
||||
const IntegrationApplication = require('./IntegrationApplication');
|
||||
const InviteStageInstance = require('./InviteStageInstance');
|
||||
const { Error } = require('../errors');
|
||||
const { ChannelTypes, Endpoints } = require('../util/Constants');
|
||||
const { Endpoints } = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
|
||||
// TODO: Convert `inviter` and `channel` in this class to a getter.
|
||||
@ -179,7 +178,7 @@ class Invite extends Base {
|
||||
this.channel = this.client.channels.cache.get(data.channel_id);
|
||||
}
|
||||
|
||||
if ('channel' in data && data.channel) {
|
||||
if ('channel' in data) {
|
||||
/**
|
||||
* The channel this invite is for
|
||||
* @type {Channel}
|
||||
@ -317,53 +316,6 @@ class Invite extends Base {
|
||||
valueOf() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join this Guild using this invite.
|
||||
* @param {boolean} [autoVerify] Whether to automatically verify member
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* await client.fetchInvite('code').then(async invite => {
|
||||
* await invite.acceptInvite();
|
||||
* });
|
||||
*/
|
||||
async acceptInvite(autoVerify = false) {
|
||||
if (!this.guild) throw new Error('INVITE_NO_GUILD');
|
||||
if (this.client.guilds.cache.get(this.guild.id)) return this.guild;
|
||||
const dataHeader = {
|
||||
location: 'Join Guild',
|
||||
location_guild_id: this.guild?.id,
|
||||
location_channel_id: this.channelId,
|
||||
location_channel_type: ChannelTypes[this.channel?.type] ?? 0,
|
||||
};
|
||||
await this.client.api.invites(this.code).post({
|
||||
data: {
|
||||
session_id: this.client.sessionId,
|
||||
},
|
||||
headers: {
|
||||
'X-Context-Properties': Buffer.from(JSON.stringify(dataHeader), 'utf8').toString('base64'),
|
||||
},
|
||||
});
|
||||
const guild = this.client.guilds.cache.get(this.guild.id);
|
||||
/*
|
||||
//
|
||||
if (autoVerify) {
|
||||
console.warn('Feature is under maintenance - Invite#acceptInvite(true)');
|
||||
}
|
||||
*/
|
||||
if (autoVerify) {
|
||||
const getForm = await this.client.api
|
||||
.guilds(this.guild.id)
|
||||
['member-verification'].get({ query: { with_guild: false, invite_code: this.code } })
|
||||
.catch(() => {});
|
||||
if (!getForm) return guild;
|
||||
const form = Object.assign(getForm.form_fields[0], { response: true });
|
||||
// Respond to the form
|
||||
// https://discord.com/api/v9/guilds/:id/requests/@me
|
||||
await this.client.api.guilds(this.guild.id).requests['@me'].put({ data: { form_fields: [form] } });
|
||||
}
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,22 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const process = require('node:process');
|
||||
const { setTimeout } = require('node:timers');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const Base = require('./Base');
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const ClientApplication = require('./ClientApplication');
|
||||
const InteractionCollector = require('./InteractionCollector');
|
||||
const MessageAttachment = require('./MessageAttachment');
|
||||
const MessageButton = require('./MessageButton');
|
||||
const Embed = require('./MessageEmbed');
|
||||
const Mentions = require('./MessageMentions');
|
||||
const MessagePayload = require('./MessagePayload');
|
||||
const MessageSelectMenu = require('./MessageSelectMenu');
|
||||
const ReactionCollector = require('./ReactionCollector');
|
||||
const { Sticker } = require('./Sticker');
|
||||
const Application = require('./interfaces/Application');
|
||||
const { Error } = require('../errors');
|
||||
const ReactionManager = require('../managers/ReactionManager');
|
||||
const { InteractionTypes, MessageTypes, SystemMessageTypes, MaxBulkDeletableMessageAge } = require('../util/Constants');
|
||||
const {
|
||||
InteractionTypes,
|
||||
MessageTypes,
|
||||
SystemMessageTypes,
|
||||
MessageComponentTypes,
|
||||
Events,
|
||||
} = require('../util/Constants');
|
||||
const MessageFlags = require('../util/MessageFlags');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
@ -256,9 +260,9 @@ class Message extends Base {
|
||||
if ('application' in data) {
|
||||
/**
|
||||
* Supplemental application information for group activities
|
||||
* @type {?ClientApplication}
|
||||
* @type {?Application}
|
||||
*/
|
||||
this.groupActivityApplication = new ClientApplication(this.client, data.application);
|
||||
this.groupActivityApplication = new Application(this.client, data.application);
|
||||
} else {
|
||||
this.groupActivityApplication ??= null;
|
||||
}
|
||||
@ -533,65 +537,6 @@ class Message extends Base {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {CollectorOptions} MessageComponentCollectorOptions
|
||||
* @property {MessageComponentType} [componentType] The type of component to listen for
|
||||
* @property {number} [max] The maximum total amount of interactions to collect
|
||||
* @property {number} [maxComponents] The maximum number of components to collect
|
||||
* @property {number} [maxUsers] The maximum number of users to interact
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a message component interaction collector.
|
||||
* @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
|
||||
* @returns {InteractionCollector}
|
||||
* @example
|
||||
* // Create a message component interaction collector
|
||||
* const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
|
||||
* const collector = message.createMessageComponentCollector({ filter, time: 15_000 });
|
||||
* collector.on('collect', i => console.log(`Collected ${i.customId}`));
|
||||
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
|
||||
*/
|
||||
createMessageComponentCollector(options = {}) {
|
||||
return new InteractionCollector(this.client, {
|
||||
...options,
|
||||
interactionType: InteractionTypes.MESSAGE_COMPONENT,
|
||||
message: this,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing the same properties as CollectorOptions, but a few more:
|
||||
* @typedef {Object} AwaitMessageComponentOptions
|
||||
* @property {CollectorFilter} [filter] The filter applied to this collector
|
||||
* @property {number} [time] Time to wait for an interaction before rejecting
|
||||
* @property {MessageComponentType} [componentType] The type of component interaction to collect
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collects a single component interaction that passes the filter.
|
||||
* The Promise will reject if the time expires.
|
||||
* @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
|
||||
* @returns {Promise<MessageComponentInteraction>}
|
||||
* @example
|
||||
* // Collect a message component interaction
|
||||
* const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
|
||||
* message.awaitMessageComponent({ filter, time: 15_000 })
|
||||
* .then(interaction => console.log(`${interaction.customId} was clicked!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
awaitMessageComponent(options = {}) {
|
||||
const _options = { ...options, max: 1 };
|
||||
return new Promise((resolve, reject) => {
|
||||
const collector = this.createMessageComponentCollector(_options);
|
||||
collector.once('end', (interactions, reason) => {
|
||||
const interaction = interactions.first();
|
||||
if (interaction) resolve(interaction);
|
||||
else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the message is editable by the client user
|
||||
* @type {boolean}
|
||||
@ -653,14 +598,7 @@ class Message extends Base {
|
||||
* channel.bulkDelete(messages.filter(message => message.bulkDeletable));
|
||||
*/
|
||||
get bulkDeletable() {
|
||||
return (
|
||||
(this.inGuild() &&
|
||||
this.client.user.bot &&
|
||||
Date.now() - this.createdTimestamp < MaxBulkDeletableMessageAge &&
|
||||
this.deletable &&
|
||||
this.channel?.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_MESSAGES, false)) ??
|
||||
false
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -722,7 +660,7 @@ class Message extends Base {
|
||||
* @property {MessageAttachment[]} [attachments] An array of attachments to keep,
|
||||
* all attachments will be kept if omitted
|
||||
* @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to add to the message
|
||||
* @property {Array<(MessageActionRow|MessageActionRowOptions)>} [components]
|
||||
* @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
|
||||
* Action rows containing interactive components for the message (buttons, select menus)
|
||||
*/
|
||||
|
||||
@ -792,7 +730,7 @@ class Message extends Base {
|
||||
/**
|
||||
* Adds a reaction to the message.
|
||||
* @param {EmojiIdentifierResolvable} emoji The emoji to react with
|
||||
* @param {boolean} [burst=false] Super Reactions (Discord Nitro only)
|
||||
* @param {boolean} [burst=false] Super Reactions
|
||||
* @returns {Promise<MessageReaction>}
|
||||
* @example
|
||||
* // React to a message with a unicode emoji
|
||||
@ -1024,13 +962,221 @@ class Message extends Base {
|
||||
reactions: false,
|
||||
});
|
||||
}
|
||||
// Added
|
||||
|
||||
// TypeScript
|
||||
/**
|
||||
* Check data
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isMessage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Click specific button with X and Y
|
||||
* @typedef {Object} MessageButtonLocation
|
||||
* @property {number} X Index of the row
|
||||
* @property {number} Y Index of the column
|
||||
*/
|
||||
|
||||
/**
|
||||
* Click specific button or automatically click first button if no button is specified.
|
||||
* @param {MessageButtonLocation|string|undefined} button button
|
||||
* @returns {Promise<Message|Modal>}
|
||||
* @example
|
||||
* // Demo msg
|
||||
* Some content
|
||||
* ――――――――――――――――――――――――――――――――> X from 0
|
||||
* │ [button1] [button2] [button3]
|
||||
* │ [button4] [button5] [button6]
|
||||
* ↓
|
||||
* Y from 0
|
||||
* // Click button6 with X and Y
|
||||
* [0,0] [1,0] [2,0]
|
||||
* [0,1] [1,1] [2,1]
|
||||
* // Code
|
||||
* message.clickButton({
|
||||
* X: 2, Y: 1,
|
||||
* });
|
||||
* // Click button with customId (Ex button 5)
|
||||
* message.clickButton('button5');
|
||||
* // Click button 1
|
||||
* message.clickButton();
|
||||
*/
|
||||
clickButton(button) {
|
||||
if (typeof button == 'undefined') {
|
||||
button = this.components
|
||||
.flatMap(row => row.components)
|
||||
.find(b => b.type === 'BUTTON' && b.customId && !b.disabled);
|
||||
} else if (typeof button == 'string') {
|
||||
button = this.components.flatMap(row => row.components).find(b => b.type === 'BUTTON' && b.customId == button);
|
||||
} else {
|
||||
button = this.components[button.Y]?.components[button.X];
|
||||
}
|
||||
button = button.toJSON();
|
||||
if (!button) throw new TypeError('BUTTON_NOT_FOUND');
|
||||
if (!button.custom_id || button.disabled) throw new TypeError('BUTTON_CANNOT_CLICK');
|
||||
const nonce = SnowflakeUtil.generate();
|
||||
const data = {
|
||||
type: InteractionTypes.MESSAGE_COMPONENT,
|
||||
nonce,
|
||||
guild_id: this.guildId,
|
||||
channel_id: this.channelId,
|
||||
message_id: this.id,
|
||||
application_id: this.applicationId ?? this.author.id,
|
||||
session_id: this.client.ws.shards.first()?.sessionId,
|
||||
message_flags: this.flags.bitfield,
|
||||
data: {
|
||||
component_type: MessageComponentTypes.BUTTON,
|
||||
custom_id: button.custom_id,
|
||||
},
|
||||
};
|
||||
this.client.api.interactions.post({
|
||||
data,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutMs = 5_000;
|
||||
// Waiting for MsgCreate / ModalCreate
|
||||
const handler = data => {
|
||||
// UnhandledPacket
|
||||
if (data.d?.nonce == nonce && data.t == 'INTERACTION_SUCCESS') {
|
||||
// Interaction#deferUpdate
|
||||
this.client.removeListener(Events.MESSAGE_CREATE, handler);
|
||||
this.client.removeListener(Events.UNHANDLED_PACKET, handler);
|
||||
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
resolve(this);
|
||||
}
|
||||
if (data.nonce !== nonce) return;
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener(Events.MESSAGE_CREATE, handler);
|
||||
this.client.removeListener(Events.UNHANDLED_PACKET, handler);
|
||||
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
resolve(data);
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener(Events.MESSAGE_CREATE, handler);
|
||||
this.client.removeListener(Events.UNHANDLED_PACKET, handler);
|
||||
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(new Error('INTERACTION_FAILED'));
|
||||
}, timeoutMs).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on(Events.MESSAGE_CREATE, handler);
|
||||
this.client.on(Events.UNHANDLED_PACKET, handler);
|
||||
this.client.on(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Select specific menu
|
||||
* @param {number|string} menu Target
|
||||
* @param {Array<UserResolvable | RoleResolvable | ChannelResolvable | string>} values Any value
|
||||
* @returns {Promise<Message|Modal>}
|
||||
*/
|
||||
selectMenu(menu, values = []) {
|
||||
let selectMenu;
|
||||
if (/[0-4]/.test(menu)) {
|
||||
selectMenu = this.components[menu]?.components[0];
|
||||
} else {
|
||||
selectMenu = this.components
|
||||
.flatMap(row => row.components)
|
||||
.find(
|
||||
b =>
|
||||
['STRING_SELECT', 'USER_SELECT', 'ROLE_SELECT', 'MENTIONABLE_SELECT', 'CHANNEL_SELECT'].includes(b.type) &&
|
||||
b.customId == menu &&
|
||||
!b.disabled,
|
||||
);
|
||||
}
|
||||
if (values.length < selectMenu.minValues) {
|
||||
throw new RangeError(`[SELECT_MENU_MIN_VALUES] The minimum number of values is ${selectMenu.minValues}`);
|
||||
}
|
||||
if (values.length > selectMenu.maxValues) {
|
||||
throw new RangeError(`[SELECT_MENU_MAX_VALUES] The maximum number of values is ${selectMenu.maxValues}`);
|
||||
}
|
||||
values = values.map(value => {
|
||||
switch (selectMenu.type) {
|
||||
case 'STRING_SELECT': {
|
||||
return selectMenu.options.find(obj => obj.value === value || obj.label === value).value;
|
||||
}
|
||||
case 'USER_SELECT': {
|
||||
return this.client.users.resolveId(value);
|
||||
}
|
||||
case 'ROLE_SELECT': {
|
||||
return this.guild.roles.resolveId(value);
|
||||
}
|
||||
case 'MENTIONABLE_SELECT': {
|
||||
return this.client.users.resolveId(value) || this.guild.roles.resolveId(value);
|
||||
}
|
||||
case 'CHANNEL_SELECT': {
|
||||
return this.client.channels.resolveId(value);
|
||||
}
|
||||
default: {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
});
|
||||
const nonce = SnowflakeUtil.generate();
|
||||
const data = {
|
||||
type: InteractionTypes.MESSAGE_COMPONENT,
|
||||
guild_id: this.guildId,
|
||||
channel_id: this.channelId,
|
||||
message_id: this.id,
|
||||
application_id: this.applicationId ?? this.author.id,
|
||||
session_id: this.client.ws.shards.first()?.sessionId,
|
||||
message_flags: this.flags.bitfield,
|
||||
data: {
|
||||
component_type: MessageComponentTypes[selectMenu.type],
|
||||
custom_id: selectMenu.customId,
|
||||
type: MessageComponentTypes[selectMenu.type],
|
||||
values,
|
||||
},
|
||||
nonce,
|
||||
};
|
||||
this.client.api.interactions.post({
|
||||
data,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutMs = 5_000;
|
||||
// Waiting for MsgCreate / ModalCreate
|
||||
const handler = data => {
|
||||
// UnhandledPacket
|
||||
if (data.d?.nonce == nonce && data.t == 'INTERACTION_SUCCESS') {
|
||||
// Interaction#deferUpdate
|
||||
this.client.removeListener(Events.MESSAGE_CREATE, handler);
|
||||
this.client.removeListener(Events.UNHANDLED_PACKET, handler);
|
||||
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
resolve(this);
|
||||
}
|
||||
if (data.nonce !== nonce) return;
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener(Events.MESSAGE_CREATE, handler);
|
||||
this.client.removeListener(Events.UNHANDLED_PACKET, handler);
|
||||
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
resolve(data);
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener(Events.MESSAGE_CREATE, handler);
|
||||
this.client.removeListener(Events.UNHANDLED_PACKET, handler);
|
||||
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(new Error('INTERACTION_FAILED'));
|
||||
}, timeoutMs).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on(Events.MESSAGE_CREATE, handler);
|
||||
this.client.on(Events.UNHANDLED_PACKET, handler);
|
||||
this.client.on(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the message as unread.
|
||||
* @returns {Promise<boolean>}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async markUnread() {
|
||||
await this.client.api.channels[this.channelId].messages[this.id].ack.post({
|
||||
markUnread() {
|
||||
return this.client.api.channels[this.channelId].messages[this.id].ack.post({
|
||||
data: {
|
||||
manual: true,
|
||||
mention_count:
|
||||
@ -1042,145 +1188,18 @@ class Message extends Base {
|
||||
: 0,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the message as read.
|
||||
* @returns {Promise<boolean>}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async markRead() {
|
||||
await this.client.api.channels[this.channelId].messages[this.id].ack.post({
|
||||
markRead() {
|
||||
return this.client.api.channels[this.channelId].messages[this.id].ack.post({
|
||||
data: {
|
||||
token: null,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} MessageButtonLocation
|
||||
* @property {number} row Index of the row
|
||||
* @property {number} col Index of the column
|
||||
*/
|
||||
|
||||
/**
|
||||
* Click specific button or automatically click first button if no button is specified.
|
||||
* @param {MessageButton|MessageButtonLocation|string} button Button ID
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
* @example
|
||||
* client.on('messageCreate', async message => {
|
||||
* if (message.components.length) {
|
||||
* // Find first button and click it
|
||||
* await message.clickButton();
|
||||
* // Click with button ID
|
||||
* await message.clickButton('button-id');
|
||||
* // Click with button location
|
||||
* await message.clickButton({ row: 0, col: 0 });
|
||||
* // Click with class MessageButton
|
||||
* const button = message.components[0].components[0];
|
||||
* await message.clickButton(button);
|
||||
* // Click with class MessageButton (2)
|
||||
* button.click(message);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
clickButton(button) {
|
||||
if (!button) {
|
||||
button = this.components.flatMap(row => row.components).find(b => b.type === 'BUTTON')?.customId;
|
||||
} else if (button instanceof MessageButton) {
|
||||
button = button.customId;
|
||||
} else if (typeof button === 'object') {
|
||||
if (!('row' in button) || !('col' in button)) throw new TypeError('INVALID_BUTTON_LOCATION');
|
||||
button = this.components[button.row]?.components[button.col]?.customId;
|
||||
}
|
||||
if (!button) throw new TypeError('BUTTON_NOT_FOUND');
|
||||
button = this.components.flatMap(row => row.components).find(b => b.customId === button && b.type === 'BUTTON');
|
||||
return button ? button.click(this) : Promise.reject(new TypeError('BUTTON_NOT_FOUND'));
|
||||
}
|
||||
/**
|
||||
* Select specific menu or First Menu
|
||||
* @param {MessageSelectMenu|string|number|Array<any>} menuID MenuId / MessageSelectMenu / Row of Menu / Array of Values (first menu)
|
||||
* @param {Array<any>} options Array of Values
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
* @example
|
||||
* client.on('messageCreate', async message => {
|
||||
* if (message.components.length) {
|
||||
* // Row
|
||||
* await message.selectMenu(1, [message.channel]); // row 1, type: Channel, multi: false
|
||||
* // Id
|
||||
* await message.selectMenu('menu-id', ['uid1', client.user, message.member]); // MenuId, type: User, multi: true
|
||||
* // First Menu
|
||||
* await message.selectMenu(['role-id']); // First Menu, type: role, multi: false
|
||||
* // class MessageSelectMenu
|
||||
* const menu = message.components[0].components[0];
|
||||
* await message.selectMenu(menu, ['option1', 'option2']);
|
||||
* // MessageSelectMenu (2)
|
||||
* menu.select(message, ['option1', 'option2']);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
selectMenu(menuID, options = []) {
|
||||
if (!this.components[0]) throw new TypeError('MESSAGE_NO_COMPONENTS');
|
||||
if (menuID instanceof MessageSelectMenu) {
|
||||
//
|
||||
} else if (/[0-4]/.test(menuID)) {
|
||||
menuID = this.components[menuID]?.components[0];
|
||||
} else {
|
||||
const menuAll = this.components
|
||||
.flatMap(row => row.components)
|
||||
.filter(b =>
|
||||
[
|
||||
'STRING_SELECT',
|
||||
'USER_SELECT',
|
||||
'ROLE_SELECT',
|
||||
'MENTIONABLE_SELECT',
|
||||
'CHANNEL_SELECT',
|
||||
'SELECT_MENU',
|
||||
].includes(b.type),
|
||||
);
|
||||
if (menuAll.length == 0) throw new TypeError('MENU_NOT_FOUND');
|
||||
if (menuID) {
|
||||
menuID = menuAll.find(b => b.customId === menuID);
|
||||
} else {
|
||||
menuID = menuAll[0];
|
||||
}
|
||||
}
|
||||
if (!menuID.type.includes('SELECT')) throw new TypeError('MENU_NOT_FOUND');
|
||||
return menuID.select(this, Array.isArray(menuID) ? menuID : options);
|
||||
}
|
||||
//
|
||||
/**
|
||||
* Send context Menu v2
|
||||
* @param {Snowflake} botId Bot id
|
||||
* @param {string} commandName Command name in Context Menu
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
async contextMenu(botId, commandName) {
|
||||
if (!botId) throw new Error('Bot ID is required');
|
||||
const user = await this.client.users.fetch(botId).catch(() => {});
|
||||
if (!user || !user.bot || !user.application) {
|
||||
throw new Error('BotID is not a bot or does not have an application slash command');
|
||||
}
|
||||
if (!commandName || typeof commandName !== 'string') {
|
||||
throw new Error('Command name is required');
|
||||
}
|
||||
let contextCMD;
|
||||
const data = await this.channel.searchInteraction(botId, 'MESSAGE');
|
||||
for (const command of data.application_commands) {
|
||||
user.application?.commands?._add(command, true);
|
||||
}
|
||||
contextCMD = user.application?.commands?.cache.find(c => c.name == commandName && c.type === 'MESSAGE');
|
||||
if (!contextCMD) {
|
||||
throw new Error(
|
||||
'INTERACTION_SEND_FAILURE',
|
||||
`Command ${commandName} is not found (with search)\nList command avalible: ${user.application?.commands?.cache
|
||||
.filter(a => a.type == 'MESSAGE')
|
||||
.map(a => a.name)
|
||||
.join(', ')}`,
|
||||
);
|
||||
}
|
||||
return contextCMD.sendContextMenu(this, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const { setTimeout } = require('node:timers');
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { RangeError } = require('../errors');
|
||||
const { MessageButtonStyles, MessageComponentTypes, InteractionTypes } = require('../util/Constants');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
const { MessageButtonStyles, MessageComponentTypes } = require('../util/Constants');
|
||||
const Util = require('../util/Util');
|
||||
const { lazy } = require('../util/Util');
|
||||
const Message = lazy(() => require('../structures/Message').Message);
|
||||
|
||||
/**
|
||||
* Represents a button message component.
|
||||
@ -164,68 +160,6 @@ class MessageButton extends BaseMessageComponent {
|
||||
static resolveStyle(style) {
|
||||
return typeof style === 'string' ? style : MessageButtonStyles[style];
|
||||
}
|
||||
// Patch Click
|
||||
/**
|
||||
* Click the button
|
||||
* @param {Message} message Discord Message
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
async click(message) {
|
||||
const nonce = SnowflakeUtil.generate();
|
||||
if (!(message instanceof Message())) throw new Error('[UNKNOWN_MESSAGE] Please pass a valid Message');
|
||||
if (!this.customId || this.style == MessageButtonStyles.LINK || this.disabled) return false;
|
||||
const data = {
|
||||
type: InteractionTypes.MESSAGE_COMPONENT,
|
||||
nonce,
|
||||
guild_id: message.guild?.id ?? null,
|
||||
channel_id: message.channel.id,
|
||||
message_id: message.id,
|
||||
application_id: message.applicationId ?? message.author.id,
|
||||
session_id: message.client.sessionId,
|
||||
message_flags: message.flags.bitfield,
|
||||
data: {
|
||||
component_type: MessageComponentTypes.BUTTON,
|
||||
custom_id: this.customId,
|
||||
},
|
||||
};
|
||||
await message.client.api.interactions.post({
|
||||
data,
|
||||
});
|
||||
message.client._interactionCache.set(nonce, {
|
||||
channelId: message.channelId,
|
||||
guildId: message.guildId,
|
||||
metadata: data,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = data => {
|
||||
timeout.refresh();
|
||||
if (data.metadata?.nonce !== nonce) return;
|
||||
clearTimeout(timeout);
|
||||
message.client.removeListener('interactionResponse', handler);
|
||||
message.client.decrementMaxListeners();
|
||||
if (data.status) {
|
||||
resolve(data.metadata);
|
||||
} else {
|
||||
reject(
|
||||
new Error('INTERACTION_ERROR', {
|
||||
cause: data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
message.client.removeListener('interactionResponse', handler);
|
||||
message.client.decrementMaxListeners();
|
||||
reject(
|
||||
new Error('INTERACTION_TIMEOUT', {
|
||||
cause: data,
|
||||
}),
|
||||
);
|
||||
}, message.client.options.interactionTimeout).unref();
|
||||
message.client.incrementMaxListeners();
|
||||
message.client.on('interactionResponse', handler);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageButton;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user