-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGetting_Started.json
287 lines (287 loc) · 27 KB
/
Getting_Started.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
{
"content": [
"|Getting started with Discord app development"
],
"Getting started with Discord app development": {
"level": 1,
"url": "https://discord.com/developers/docs/docs/getting-started#getting-started-with-discord-app-development",
"content": [
"Discord apps let you customize your servers with interactions and automation. This guide is meant to walk through building and running your first Discord app using JavaScript. At the end of this guide, you\u2019ll have an app with a bot user that uses slash commands, sends messages, and interacts with message components.",
"While this guide is beginner-focused, it assumes a basic understanding of JavaScript.",
"> info\n> When developing apps, you should build and test in a server that isn\u2019t actively used by others. If you don\u2019t have your own server already, you can create one for free.",
"\u2015",
"|Overview",
"|Creating an app",
"|Running your app",
"|Handling interactivity",
"|Adding message components",
"|Next steps"
],
"Overview": {
"level": 2,
"url": "https://discord.com/developers/docs/docs/getting-started#overview",
"content": [
"In this guide, we\u2019ll be building a Discord app that lets server members play a slightly-enhanced version of rock paper scissors (with 7 choices instead of the usual 3).",
"|Resources used in this guide"
],
"Resources used in this guide": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#resources-used-in-this-guide",
"content": [
[
"Github repository where the code from this guide lives along with additional feature-specific examples.",
"discord-interactions, an npm package which provides types and helper functions for Discord apps.",
"Glitch, an online environment that simplifies building and hosting apps during early prototyping and development. You can also develop locally with a tool like ngrok."
],
"And here's what the finished app will look like:",
"Demo of example app",
"To make the user flow a bit more explicit:",
[
"User 1 initiates a new game and picks their object using the app\u2019s /challenge slash command",
"A message is sent to channel with a button inviting others to accept the challenge",
"User 2 presses the Accept button",
"User 2 is sent an ephemeral message where they select their object of choice",
"The result of the game is posted back into the original channel for all to see"
]
]
}
},
"Creating an app": {
"level": 2,
"url": "https://discord.com/developers/docs/docs/getting-started#creating-an-app",
"content": [
"The first thing we\u2019ll need to do is create an app. Navigate to the developer dashboard, then click New Application in the upper right corner.",
"App creation modal",
"Enter a name for your app, then click Create.",
"Once you create an app, you'll land on the General Overview page of the app's settings. Here you can view and configure basic information about the app, like its description and icon. You\u2019ll also see an Application ID and Interactions Endpoint URL, which we\u2019ll use a bit later in the guide.",
"|Configuring a bot",
"|Adding scopes and permissions",
"|Installing your app"
],
"Configuring a bot": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#configuring-a-bot",
"content": [
"Next we'll add a bot user to your app, which allows it to appear in Discord similar to other members. On the left hand sidebar click Bot, then the Add Bot button.",
"Once you create a bot, you\u2019ll have an option to update its icon and username. Under that, there\u2019s a Token section with a Reset Token button.",
"Bot tab in app settings",
"Bot tokens are used to authorize API requests and carry all of your bot user\u2019s permissions, making them highly sensitive. Make sure to never share your token or check it into any kind of version control.",
"Go ahead and click Reset Token, and store the token somewhere safe (like in a password manager).",
"> warn\n> You won\u2019t be able to view your token again unless you regenerate it, so make sure to keep it somewhere safe."
]
},
"Adding scopes and permissions": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#adding-scopes-and-permissions",
"content": [
"Apps need approval from installing users to perform actions inside of Discord (like creating a slash command or adding emojis). So before installing your app, let's add some scopes and permissions to request during installation. Click on OAuth2 in the left sidebar, then URL generator.",
"> info\n> The URL generator helps create an installation link with the scopes and permissions your app needs to function. You can use the link to install the app onto your server, or share it with others so they can install it on their own.",
"For now, we\u2019ll just add two scopes:",
[
"applications.commands lets your app create commands in guilds its installed",
"bot is to enable your bot user. After you click bot, you can also add different user permissions to the bot. For now, just check Send Messages."
],
"See a list of all OAuth2 scopes, or read more on user permissions in the documentation.",
"URL generator screenshot",
"Once you add scopes, you should see a URL that you can copy to install your app."
]
},
"Installing your app": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#installing-your-app",
"content": [
"Copy the URL from above, and paste it into your browser. You\u2019ll be guided through the installation flow, where you should make sure you\u2019re installing the app on a server where you can develop and test.",
"After installing your app, you can head over to your server and see that it has joined \u2728",
"With your app configured and installed, let\u2019s start developing it."
]
}
},
"Running your app": {
"level": 2,
"url": "https://discord.com/developers/docs/docs/getting-started#running-your-app",
"content": [
"All of the code used in the example app can be found in the Github repository.",
"To make development a bit simpler, the app uses discord-interactions, which provides types and helper functions. If you prefer to use other languages or libraries, there\u2019s a page with community-built resources which you can browse through.",
"|Remix the project",
"|Adding credentials",
"|Installing slash commands"
],
"Remix the project": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#remix-the-project",
"content": [
"This guide uses Glitch, which allows you to quickly clone and develop an app from within your browser. There are also instructions on developing locally using ngrok in the README if you'd prefer.",
"> info\n> While Glitch is great for development and testing, it has technical limitations so other hosting providers should be considered for production apps.",
"To start, remix (or clone) the Glitch project \ud83c\udf8f",
"When you remix the project, you'll see a new Glitch project with a unique name similar to this:",
"Glitch project overview",
"|Project structure"
],
"Project structure": {
"level": 4,
"url": "https://discord.com/developers/docs/docs/getting-started#remix-the-project-project-structure",
"content": [
"All of the files for the project are on the left-hand side. Here's a quick glimpse at the structure:",
{
"language": "",
"code": "\u251c\u2500\u2500 examples -> short, feature-specific sample apps\n\u2502 \u251c\u2500\u2500 app.js -> completed app.js code\n\u2502 \u251c\u2500\u2500 button.js\n\u2502 \u251c\u2500\u2500 command.js\n\u2502 \u251c\u2500\u2500 modal.js\n\u2502 \u251c\u2500\u2500 selectMenu.js\n\u251c\u2500\u2500 .env -> your credentials and IDs\n\u251c\u2500\u2500 app.js -> main entrypoint for app\n\u251c\u2500\u2500 commands.js -> slash command payloads + helpers\n\u251c\u2500\u2500 game.js -> logic specific to RPS\n\u251c\u2500\u2500 utils.js -> utility functions and enums\n\u251c\u2500\u2500 package.json\n\u251c\u2500\u2500 README.md\n\u2514\u2500\u2500 .gitignore\n"
}
]
}
},
"Adding credentials": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#adding-credentials",
"content": [
"There's already some code in your app.js file, but you\u2019ll need your app\u2019s token and ID to make requests. All of your credentials can be stored directly in the .env file.",
"> warn\n> It bears repeating that you should never check any credentials or secrets into source control. The getting started project's .gitignore comes pre-loaded with .env to prevent it.",
"First, copy your bot user\u2019s token from earlier and paste it in the DISCORD_TOKEN variable in your .env file.",
"Next, navigate to your app settings in the developer portal and copy the App ID and Public Key from the General Overview page. Paste the values in your .env file as APP_ID and PUBLIC_KEY.",
"Finally, fetch your guild ID by navigating to the server where you installed your app. Copy the first number in the URL after channels/ (for example, in the URL https://discord.com/channels/12345/678910, the guild ID would be 12345). Save this value as GUILD_ID in your .env file.",
"With your credentials configured, let's install and handle slash commands."
]
},
"Installing slash commands": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#installing-slash-commands",
"content": [
"> info\n> To install slash commands, the app is using node-fetch. You can see the implementation for the installation in utils.js within the DiscordRequest() function. More information about Discord's REST API can be found in the API reference.",
"If you look in the listen callback at the bottom of app.js, you\u2019ll see that HasGuildCommands() is called. HasGuildCommands() is a utility function that checks whether specific slash commands are installed\u2014and if they aren't, installs them. The code for HasGuildCommands() is inside of the top-level commands.js file.",
"To install guild-scoped slash commands, apps can call the \u200b\u200b/applications/<APP_ID>/guilds/<GUILD_ID>/commands endpoint (which is what HasGuildCommands() does). An example focused on installing and handling slash commands can be found within the examples/ folder, in examples/command.js.",
"> info\n> Commands can either be installed to a specific guild, or installed globally, though guild commands are recommended for development since they update faster. Read more about the differences in the documentation.",
"If you go back to your guild and refresh it, you should see the slash command appear. But if you try to run it, nothing will happen because the request coming from Discord isn't being handled."
]
}
},
"Handling interactivity": {
"level": 2,
"url": "https://discord.com/developers/docs/docs/getting-started#handling-interactivity",
"content": [
"To enable your app to receive slash command requests (and other interactions), Discord needs a public URL to send them. This URL can be configured in your app settings as Interaction Endpoint URL.",
"|Adding interaction endpoint URL",
"|Handling slash command requests"
],
"Adding interaction endpoint URL": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#adding-interaction-endpoint-url",
"content": [
"Glitch projects have a public URL exposed by default. Copy your project's URL by clicking Share in the top right, then copying the live project link at the bottom of the modal.",
"In the following example, the link would be https://vast-thorn-plant.glitch.me:",
"Glitch share modal",
"> info\n> If you're developing locally, there are instructions for tunneling requests to your local environment on the Github README.",
"With that link copied, go to your app settings from the developer portal.",
"On your app\u2019s General Information page, there\u2019s an Interactive Endpoint URL option, where you can paste your app\u2019s URL and append /interactions to it, which is where the Express app is configured to listen for requests.",
"Interactions endpoint URL in app settings",
"Click Save Changes and ensure your endpoint is successfully verified.",
"> info\n> Verification requires your app to verify signature headers and respond to PING events. You can read more about preparing to receive interactions in the interactions documentation.",
"The sample app handles verification in two ways:",
[
"It uses the PUBLIC_KEY and discord-interactions package with a wrapper function (imported from utils.js) that makes it conform to Express\u2019s verify interface. This is run on every incoming request to your app.",
"It responds to incoming PING requests."
]
]
},
"Handling slash command requests": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#handling-slash-command-requests",
"content": [
"With the endpoint verified, go back to your code (in app.js) and look for the code block that handles the /test command:",
{
"language": "javascript",
"code": "// \"test\" guild command\nif (name === 'test') {\n // Send a message into the channel where command was triggered from\n return res.send({\n type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,\n data: {\n // Fetches a random emoji to send from a helper function\n content: 'hello world ' + getRandomEmoji(),\n },\n });\n}\n"
},
"The above code is responding to the interaction with a message in the channel it originated from. You can see all of the different possible response types, like responding with a modal, in the documentation.",
"> info\n> InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE is a constant exported from discord-interactions",
"Go to the server that matches your configured GUILD_ID and make sure your app\u2019s /test slash command works. When you trigger it, your app should send \u201chello world\u201d with a random emoji appended.",
"If you don\u2019t want to add any additional interactivity, you could skip to next steps. But in the following section, we\u2019ll add an additional command that uses slash command options, buttons, and select menus to build the rock-paper-scissors game."
]
}
},
"Adding message components": {
"level": 2,
"url": "https://discord.com/developers/docs/docs/getting-started#adding-message-components",
"content": [
"The /challenge command will be how our rock-paper-scissors-style game is initiated. When the command is triggered, the app will send message components to the channel, which will guide the users to complete the game.",
"|Adding a command with options",
"|Handling button interactions",
"|Handling select menu interactions"
],
"Adding a command with options": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#adding-a-command-with-options",
"content": [
"The /challenge command, exported as CHALLENGE_COMMAND in commands.js, has an array called options. For this app, the options are the different objects a user can select for our game, generated using keys of RPSChoices in game.js.",
"You can read more about command options and their structure in the documentation.",
"> info\n> While this guide won't touch much on the game.js file, feel free to poke around and change commands or the options in the commands.",
"To handle the /challenge command, add the following code after the if name === \u201ctest\u201d if block:",
{
"language": "javascript",
"code": "// \"challenge\" guild command\nif (name === 'challenge' && id) {\n const userId = req.body.member.user.id;\n // User's object choice\n const objectName = req.body.data.options[0].value;\n\n // Create active game using message ID as the game ID\n activeGames[id] = {\n id: userId,\n objectName,\n };\n\n return res.send({\n type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,\n data: {\n // Fetches a random emoji to send from a helper function\n content: `Rock papers scissors challenge from <@${userId}>`,\n components: [\n {\n type: MessageComponentTypes.ACTION_ROW,\n components: [\n {\n type: MessageComponentTypes.BUTTON,\n // Append the game ID to use later on\n custom_id: `accept_button_${req.body.id}`,\n label: 'Accept',\n style: ButtonStyleTypes.PRIMARY,\n },\n ],\n },\n ],\n },\n });\n}\n"
},
"> info\n> If you aren\u2019t sure where to paste the code, you can see the full code in examples/app.js in the Glitch project or the root app.js on Github.",
"The above code is doing a few things:",
[
"Parses the request body to get the ID of the user who triggered the slash command (userId), and the option (object choice) they selected (objectName).",
"Adds a new game to the activeGames object using the interaction ID. The active game records the userId and objectName.",
"Sends a message back to the channel with a button with a custom_id of accept_button_<SOME_ID>."
],
"> warn\n> The sample code uses an object as in-memory storage, but for production apps you should use a database.",
"When sending a message with message components, the individual payloads are appended to a components array. Actionable components (like buttons) need to be inside of an action row, which you can see in the code sample.",
"Note the unique custom_id sent with message components, in this case accept_button_ with the active game's ID appended to it. A custom_id can be used to handle requests that Discord sends you when someone interacts with the component, which you'll see in a moment.",
"Now when you run the /challenge command and pick an option, your app will send a message with an Accept button. Let's add code to handle the button press."
]
},
"Handling button interactions": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#handling-button-interactions",
"content": [
"When users interact with a message component, Discord will send a request with an interaction type of 3 (or the MESSAGE_COMPONENT value when using discord-interactions).",
"To set up a handler for the button, we\u2019ll check the type of interaction, followed by matching the custom_id. Paste the following code under the type handler for APPLICATION_COMMANDs:",
{
"language": "javascript",
"code": "if (type === InteractionType.MESSAGE_COMPONENT) {\n// custom_id set in payload when sending message component\nconst componentId = data.custom_id;\n\n if (componentId.startsWith('accept_button_')) {\n // get the associated game ID\n const gameId = componentId.replace('accept_button_', '');\n // Delete message with token in request body\n const endpoint = `webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`;\n try {\n await res.send({\n type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,\n data: {\n // Fetches a random emoji to send from a helper function\n content: 'What is your object of choice?',\n // Indicates it'll be an ephemeral message\n flags: InteractionResponseFlags.EPHEMERAL,\n components: [\n {\n type: MessageComponentTypes.ACTION_ROW,\n components: [\n {\n type: MessageComponentTypes.STRING_SELECT,\n // Append game ID\n custom_id: `select_choice_${gameId}`,\n options: getShuffledOptions(),\n },\n ],\n },\n ],\n },\n });\n // Delete previous message\n await DiscordRequest(endpoint, { method: 'DELETE' });\n } catch (err) {\n console.error('Error sending message:', err);\n }\n }\n}\n"
},
"To briefly go over what the above code is doing:",
[
"Checks for a custom_id that matches what we originally sent (in this case, it starts with accept_button_). The custom ID also has the active game ID appended, so we store that in gameID.",
"Deletes the original message calling a webhook using node-fetch and passing the unique interaction token in the request body. This is done to clean up the channel, and so other users can\u2019t click the button.",
"Responds to the request by sending a message that contains a select menu with the object choices for the game. The payload should look fairly similar to the previous one, with the exception of the options array and flags: 64, which indicates that the message is ephemeral."
],
"The options array is populated using the getShuffledOptions() method in game.js, which manipulates the RPSChoices values to conform to the shape of message component options."
]
},
"Handling select menu interactions": {
"level": 3,
"url": "https://discord.com/developers/docs/docs/getting-started#handling-select-menu-interactions",
"content": [
"The last thing to add is code to handle select menu interactions and send the result of the game to channel.",
"Since select menus are just another message component, the code to handle interactions with them will be similar to buttons. Modify the code above to handle the select menu:",
{
"language": "javascript",
"code": "if (type === InteractionType.MESSAGE_COMPONENT) {\n// custom_id set in payload when sending message component\nconst componentId = data.custom_id;\n\n if (componentId.startsWith('accept_button_')) {\n // get the associated game ID\n const gameId = componentId.replace('accept_button_', '');\n // Delete message with token in request body\n const endpoint = `webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`;\n try {\n await res.send({\n type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,\n data: {\n // Fetches a random emoji to send from a helper function\n content: 'What is your object of choice?',\n // Indicates it'll be an ephemeral message\n flags: InteractionResponseFlags.EPHEMERAL,\n components: [\n {\n type: MessageComponentTypes.ACTION_ROW,\n components: [\n {\n type: MessageComponentTypes.STRING_SELECT,\n // Append game ID\n custom_id: `select_choice_${gameId}`,\n options: getShuffledOptions(),\n },\n ],\n },\n ],\n },\n });\n // Delete previous message\n await DiscordRequest(endpoint, { method: 'DELETE' });\n } catch (err) {\n console.error('Error sending message:', err);\n }\n } else if (componentId.startsWith('select_choice_')) {\n // get the associated game ID\n const gameId = componentId.replace('select_choice_', '');\n\n if (activeGames[gameId]) {\n // Get user ID and object choice for responding user\n const userId = req.body.member.user.id;\n const objectName = data.values[0];\n // Calculate result from helper function\n const resultStr = getResult(activeGames[gameId], {\n id: userId,\n objectName,\n });\n\n // Remove game from storage\n delete activeGames[gameId];\n // Update message with token in request body\n const endpoint = `webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`;\n\n try {\n // Send results\n await res.send({\n type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,\n data: { content: resultStr },\n });\n // Update ephemeral message\n await DiscordRequest(endpoint, {\n method: 'PATCH',\n body: {\n content: 'Nice choice ' + getRandomEmoji(),\n components: []\n }\n });\n } catch (err) {\n console.error('Error sending message:', err);\n }\n }\n }\n}\n"
},
"Similar to earlier code, the code above is getting the user ID and their object selection from the interaction request.",
"That information, along with the original user's ID and selection from the activeGames object, are passed to the getResult() function. getResult() determines the winner, then builds a readable string to send back to channel.",
"We\u2019re also calling another webhook, this time to update the follow-up ephemeral message since it can't be deleted.",
"Finally, the results are sent in channel using the CHANNEL_MESSAGE_WITH_SOURCE interaction response type.",
"....and that's it \ud83c\udf8a Go ahead and test your app and make sure everything works."
]
}
},
"Next steps": {
"level": 2,
"url": "https://discord.com/developers/docs/docs/getting-started#next-steps",
"content": [
"Congrats on building your first Discord app! \ud83e\udd16",
"Hopefully you learned a bit about Discord apps, how to configure them, and how to make them interactive. From here, you can continue building out your app or explore what else is possible:",
[
"Read the documentation for in-depth information about API features",
"Browse the examples/ folder in this project for smaller, feature-specific code examples",
"Check out community resources for language-specific tools maintained by community members",
"Read our tutorial on hosting Discord apps on Cloudflare Workers",
"Join the Discord Developers server to ask questions about the API, attend events hosted by the Discord API team, and interact with other devs"
]
]
}
}
}