Skip to content

Commit 5916680

Browse files
almostSoujiQjuh
andauthored
feat(guide): port remaining prs/issues from legacy (#10974)
* chore: remove await wait placeholder prefer using an explanatory placeholder rather than this artificial example original issue: https://github.com/discordjs/guide/issues/1360 * chore: remove implicit grant guide and add disclaimer issue: https://github.com/discordjs/guide/issues/1370/ pr: discordjs/guide#1543 * chore(sharding): improve broadcast sample and use of context argument original PR: discordjs/guide#1624 * feat: add page about setup with proxy original PR: discordjs/guide#1623 * chore: clarify hiding of commands original PR: discordjs/guide#1617 * feat(voice): seeking original PR: discordjs/guide#1483 * chore(oauth2): typo * chore: align with rest of the guide remove abstraction layers in ws proxy handling in favour of directly setting globals * chore: branding over grammar * Apply suggestions from code review Co-authored-by: Qjuh <[email protected]> * chore: remove now obsolete example explanation from comments --------- Co-authored-by: Qjuh <[email protected]>
1 parent 1f0fe39 commit 5916680

File tree

8 files changed

+162
-64
lines changed

8 files changed

+162
-64
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"pages": ["async-await", "collections", "es6-syntax", "notation", "rest-api"]
2+
"pages": ["async-await", "collections", "es6-syntax", "notation", "rest-api", "proxy"]
33
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
title: Using a proxy
3+
---
4+
5+
This guide will show you how to set up a proxy with discord.js. This may be necessary if you are deploying your bot to a server with a firewall only allowing outside traffic through the proxy.
6+
7+
Proxying discord.js requires two components: a REST proxy and a WebSocket proxy.
8+
9+
## Prerequisites
10+
11+
To achieve these two components you can utilize the `undici` and `global-agent` packages:
12+
13+
```sh tab="npm"
14+
npm install undici global-agent
15+
```
16+
17+
```sh tab="yarn"
18+
yarn add undici global-agent
19+
```
20+
21+
```sh tab="pnpm"
22+
pnpm add undici global-agent
23+
```
24+
25+
```sh tab="bun"
26+
bun add undici global-agent
27+
```
28+
29+
## Setting up the proxy for REST calls
30+
31+
The `@discordjs/rest` package handling HTTP requests in discord.js uses the `undici` package. Accordingly, you can provide a custom `ProxyAgent` configuration to the client constructor:
32+
33+
```js title="index.js" lineNumbers
34+
const { ProxyAgent } = require('undici'); // [!code word:ProxyAgent]
35+
const { Client } = require('discord.js');
36+
37+
const client = new Client({
38+
// ...other client options
39+
rest: {
40+
agent: new ProxyAgent('http://my-proxy-server:port'),
41+
},
42+
});
43+
44+
client.login('your-token-goes-here');
45+
```
46+
47+
<Callout>
48+
For further information on the `undici` `ProxyAgent`, please refer to the [undici
49+
documentation](https://undici.nodejs.org/#/docs/api/ProxyAgent.md).
50+
</Callout>
51+
52+
## Setting up the proxy for the WebSocket connection
53+
54+
To set up a proxy for WebSocket, you can use the `global-agent` package. You will need to import and call the `bootstrap()` function and set the required `GLOBAL_AGENT` globals as shown below:
55+
56+
```js title="index.js" lineNumbers
57+
const { ProxyAgent } = require('undici');
58+
const { Client } = require('discord.js');
59+
const { bootstrap } = require('global-agent'); // [!code ++:5]
60+
61+
bootstrap(); // [!code word:bootstrap]
62+
global.GLOBAL_AGENT.HTTP_PROXY = 'http://my-proxy-server:port';
63+
global.GLOBAL_AGENT.HTTPS_PROXY = 'https://my-proxy-server:port';
64+
65+
const client = new Client({
66+
// ...other client options
67+
rest: {
68+
agent: new ProxyAgent('http://my-proxy-server:port'),
69+
},
70+
});
71+
72+
client.login('your-token-goes-here');
73+
```

apps/guide/content/docs/legacy/oauth2/oauth2.mdx

Lines changed: 5 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -75,56 +75,11 @@ The `identify` scope will allow your application to get basic user information f
7575

7676
### Implicit grant flow
7777

78-
You have your website, and you have a URL. Now you need to use those two things to get an access token. For basic applications like [SPAs](https://en.wikipedia.org/wiki/Single-page_application), getting an access token directly is enough. You can do so by changing the `response_type` in the URL to `token`. However, this means you will not get a refresh token, which means the user will have to explicitly re-authorize when this access token has expired.
79-
80-
After you change the `response_type`, you can test the URL right away. Visiting it in your browser, you will be directed to a page that looks like this:
81-
82-
![Authorization Page](./images/authorize-app-page.png)
83-
84-
You can see that by clicking `Authorize`, you allow the application to access your username and avatar. Once you click through, it will redirect you to your redirect URL with a [fragment identifier](https://en.wikipedia.org/wiki/Fragment_identifier) appended to it. You now have an access token and can make requests to Discord's API to get information on the user.
85-
86-
Modify `index.html` to add your OAuth2 URL and to take advantage of the access token if it exists. Even though [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) is for working with query strings, it can work here because the structure of the fragment follows that of a query string after removing the leading "#".
87-
88-
```html title="index.html" lineNumbers=11
89-
<div id="info">Hoi!</div>
90-
<a id="login" style="display: none;" href="your-oauth2-URL-here">Identify Yourself</a>
91-
<script>
92-
window.onload = () => {
93-
const fragment = new URLSearchParams(window.location.hash.slice(1));
94-
const [accessToken, tokenType] = [fragment.get('access_token'), fragment.get('token_type')];
95-
96-
if (!accessToken) {
97-
return (document.getElementById('login').style.display = 'block');
98-
}
99-
100-
fetch('https://discord.com/api/users/@me', {
101-
headers: {
102-
authorization: `${tokenType} ${accessToken}`,
103-
},
104-
})
105-
.then((result) => result.json())
106-
.then((response) => {
107-
const { username, discriminator } = response;
108-
document.getElementById('info').innerText += ` ${username}#${discriminator}`;
109-
})
110-
.catch(console.error);
111-
};
112-
</script>
113-
```
114-
115-
Here you grab the access token and type from the URL if it's there and use it to get info on the user, which is then used to greet them. The response you get from the [`/api/users/@me` endpoint](https://discord.com/developers/docs/resources/user#get-current-user) is a [user object](https://discord.com/developers/docs/resources/user#user-object) and should look something like this:
116-
117-
```json
118-
{
119-
"id": "123456789012345678",
120-
"username": "User",
121-
"discriminator": "0001",
122-
"avatar": "1cc0a3b14aec3499632225c708451d67",
123-
...
124-
}
125-
```
126-
127-
In the following sections, we'll go over various details of Discord and OAuth2.
78+
<Callout type="error">
79+
Implicit grant flow, as previously covered in this section, is vulnerable to token leakage and replay attacks. Please
80+
use the **authorization grant** flow instead. You can find out more about the best implementation practices in the
81+
[Oauth2 RFC](https://datatracker.ietf.org/doc/html/rfc9700).
82+
</Callout>
12883

12984
## More details
13085

apps/guide/content/docs/legacy/sharding/additional-information.mdx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,24 @@ You can access them later as usual via `process.argv`, which contains an array o
7979

8080
## Eval arguments
8181

82-
There may come the point where you will want to pass arguments from the outer scope into a `.broadcastEval()` call.
82+
There may come the point where you will want to pass arguments from the outer scope into a `.broadcastEval()` call. The `context` parameter is useful for this purpose.
8383

8484
```js
85-
function funcName(c, { arg }) {
85+
// [!code word:context]
86+
function funcName(client, context) {
8687
// ...
8788
}
8889

89-
client.shard.broadcastEval(funcName, { context: { arg: 'arg' } });
90+
// Evaluate on all shards
91+
client.shard.broadcastEval(funcName, {
92+
context: { arg: 'arg' },
93+
});
94+
95+
// Evaluate on a specific shard
96+
client.shard.broadcastEval(funcName, {
97+
shard: 0,
98+
context: { arg: 'arg' },
99+
});
90100
```
91101

92102
The `BroadcastEvalOptions` typedef was introduced in discord.js v13 as the second parameter in `.broadcastEval()`. It accepts two properties: `shard` and `context`. The `context` property will be sent as the second argument to your function.

apps/guide/content/docs/legacy/slash-commands/permissions.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The slash command permissions for guilds are defaults only and can be altered by
1414

1515
## Member permissions
1616

17-
You can use `SlashCommandBuilder#setDefaultMemberPermissions` to set the default permissions required for a member to run the command. Setting it to `0` will prohibit anyone in a guild from using the command unless a specific overwrite is configured or the user has the Administrator permission flag.
17+
You can use `SlashCommandBuilder#setDefaultMemberPermissions` to set the default permissions required for a member to run the command. Setting it to `0` will hide the command from and prohibit anyone in a guild from using the command unless a specific overwrite is configured or the user has the Administrator permission flag.
1818

1919
For this, you'll introduce two common and simple moderation commands: `ban` and `kick`. For a ban command, a sensible default is to ensure that users already have the Discord permission `BanMembers` in order to use it.
2020

apps/guide/content/docs/legacy/slash-commands/response-methods.mdx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,12 @@ After you've sent an initial response, you may want to edit that response for va
4949
</Callout>
5050

5151
```js
52-
const wait = require('node:timers/promises').setTimeout;
53-
5452
client.on(Events.InteractionCreate, async (interaction) => {
5553
if (!interaction.isChatInputCommand()) return;
5654

5755
if (interaction.commandName === 'ping') {
5856
await interaction.reply('Pong!');
59-
await wait(2_000);
57+
// do something that requires time (database queries, api requests, ...)
6058
await interaction.editReply('Pong again!'); // [!code word:editReply]
6159
}
6260
});
@@ -71,16 +69,13 @@ As previously mentioned, Discord requires an acknowledgement from your bot withi
7169
In this case, you can make use of the `ChatInputCommandInteraction#deferReply()` method, which triggers the `<application> is thinking...` message. This also acts as the initial response, to confirm to Discord that the interaction was received successfully and gives you a **15-minute timeframe to complete your tasks** before responding.
7270

7371
```js
74-
const wait = require('node:timers/promises').setTimeout;
75-
7672
client.on(Events.InteractionCreate, async (interaction) => {
7773
if (!interaction.isChatInputCommand()) return;
7874

7975
if (interaction.commandName === 'ping') {
8076
await interaction.deferReply(); // [!code word:deferReply]
81-
// you can take up to 15 minutes! We take 4 seconds to demonstrate this
82-
// since it is barely above the 3-second threshold for the initial response
83-
await wait(4_000);
77+
// you can do things that take time here (database queries, api requests, ...) that you need for the initial response
78+
// you can take up to 15 minutes, then the interaction token becomes invalid!
8479
await interaction.editReply('Pong!'); // [!code word:editReply]
8580
}
8681
});

apps/guide/content/docs/voice/meta.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"life-cycles",
99
"voice-connections",
1010
"audio-player",
11-
"audio-resources"
11+
"audio-resources",
12+
"seeking"
1213
],
1314
"root": true,
1415
"icon": "Volume2"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
title: Seeking
3+
---
4+
5+
There is no built-in method for seeking audio resources in discord.js. However external libraries such as `prism-media` can be used to tackle this issue.
6+
7+
## Seeking in a stream
8+
9+
To seek resource, you can create a new function with the following code:
10+
11+
```js
12+
// Require necessary package
13+
const prism = require('prism-media'); // [!code word:prism]
14+
15+
function createFFmpegStream(stream, seek) {
16+
let seekPosition = '0';
17+
if (seek) seekPosition = String(seek);
18+
const transcoder = new prism.FFmpeg({
19+
args: [
20+
'-analyzeduration',
21+
'0',
22+
'-loglevel',
23+
'0',
24+
'-f',
25+
's16le',
26+
'-ar',
27+
'48000',
28+
'-ac',
29+
'2',
30+
'-ss',
31+
seekPosition,
32+
'-ab',
33+
'320',
34+
],
35+
});
36+
const s16le = stream.pipe(transcoder);
37+
const opus = s16le.pipe(new prism.opus.Encoder({ rate: 48000, channels: 2, frameSize: 960 }));
38+
return opus;
39+
}
40+
```
41+
42+
This function takes two arguments: the audio stream and the desired seek position, expressed as duration within the duration of the full stream. It returns the seeked stream.
43+
44+
<Callout>
45+
You can find configuration options in the [prism media documentation](https://amishshah.github.io/prism-media/).
46+
</Callout>
47+
48+
## Using seek with the audio player
49+
50+
```js
51+
const { createAudioResource, createAudioPlayer } = require('@discordjs/voice');
52+
const fs = require('fs');
53+
54+
const player = createAudioPlayer();
55+
const normalAudioResource = createAudioResource('Your audio file path');
56+
57+
player.play(normalAudioResource);
58+
59+
// [!code word:createFFmpegStream]
60+
const seekedAudioStream = createFFmpegStream(fs.createReadStream('Your audio file path'), 10); // Seek to 10s
61+
const seekedAudioResource = createAudioResource(seekedAudioStream);
62+
63+
player.play(seekedAudioResource);
64+
```

0 commit comments

Comments
 (0)