create-discord-https 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +15 -0
  2. package/index.js +389 -0
  3. package/package.json +35 -0
  4. package/templates/cloudflare/README.md +32 -0
  5. package/templates/cloudflare/_gitignore +3 -0
  6. package/templates/cloudflare/package.json +26 -0
  7. package/templates/cloudflare/src/DevLayer.js +139 -0
  8. package/templates/cloudflare/src/commands/fun/joke.js +118 -0
  9. package/templates/cloudflare/src/commands/reply/ping.js +12 -0
  10. package/templates/cloudflare/src/commands/utility/help.js +73 -0
  11. package/templates/cloudflare/src/commands/utility/info.js +60 -0
  12. package/templates/cloudflare/src/commands/utility/profile.js +36 -0
  13. package/templates/cloudflare/src/spawner.js +17 -0
  14. package/templates/cloudflare/wrangler.jsonc +16 -0
  15. package/templates/cloudflare-ts/README.md +33 -0
  16. package/templates/cloudflare-ts/_gitignore +3 -0
  17. package/templates/cloudflare-ts/package.json +27 -0
  18. package/templates/cloudflare-ts/src/DevLayer.ts +140 -0
  19. package/templates/cloudflare-ts/src/commands/fun/joke.ts +125 -0
  20. package/templates/cloudflare-ts/src/commands/index.ts +14 -0
  21. package/templates/cloudflare-ts/src/commands/reply/ping.ts +12 -0
  22. package/templates/cloudflare-ts/src/commands/utility/help.ts +73 -0
  23. package/templates/cloudflare-ts/src/commands/utility/index.ts +14 -0
  24. package/templates/cloudflare-ts/src/commands/utility/info.ts +56 -0
  25. package/templates/cloudflare-ts/src/commands/utility/profile.ts +35 -0
  26. package/templates/cloudflare-ts/src/index.ts +60 -0
  27. package/templates/cloudflare-ts/src/spawner.js +17 -0
  28. package/templates/cloudflare-ts/tsconfig.json +18 -0
  29. package/templates/cloudflare-ts/wrangler.jsonc +19 -0
  30. package/templates/node/README.md +13 -0
  31. package/templates/node/_gitignore +3 -0
  32. package/templates/node/package.json +22 -0
  33. package/templates/node/src/DevLayer.js +135 -0
  34. package/templates/node/src/commands/fun/joke.js +118 -0
  35. package/templates/node/src/commands/reply/ping.js +12 -0
  36. package/templates/node/src/commands/utility/help.js +73 -0
  37. package/templates/node/src/commands/utility/info.js +60 -0
  38. package/templates/node/src/commands/utility/profile.js +36 -0
  39. package/templates/node-ts/README.md +10 -0
  40. package/templates/node-ts/_gitignore +3 -0
  41. package/templates/node-ts/package.json +24 -0
  42. package/templates/node-ts/src/DevLayer.ts +135 -0
  43. package/templates/node-ts/src/commands/fun/joke.ts +125 -0
  44. package/templates/node-ts/src/commands/index.ts +14 -0
  45. package/templates/node-ts/src/commands/reply/ping.ts +12 -0
  46. package/templates/node-ts/src/commands/utility/help.ts +73 -0
  47. package/templates/node-ts/src/commands/utility/index.ts +14 -0
  48. package/templates/node-ts/src/commands/utility/info.ts +56 -0
  49. package/templates/node-ts/src/commands/utility/profile.ts +35 -0
  50. package/templates/node-ts/src/index.ts +50 -0
  51. package/templates/vercel/.vercelignore +4 -0
  52. package/templates/vercel/README.md +30 -0
  53. package/templates/vercel/_gitignore +4 -0
  54. package/templates/vercel/api/interactions.js +5 -0
  55. package/templates/vercel/index.html +235 -0
  56. package/templates/vercel/package.json +23 -0
  57. package/templates/vercel/src/DevLayer.js +135 -0
  58. package/templates/vercel/src/commands/fun/joke.js +118 -0
  59. package/templates/vercel/src/commands/reply/ping.js +12 -0
  60. package/templates/vercel/src/commands/utility/help.js +73 -0
  61. package/templates/vercel/src/commands/utility/info.js +60 -0
  62. package/templates/vercel/src/commands/utility/profile.js +36 -0
  63. package/templates/vercel/src/spawner.js +18 -0
  64. package/templates/vercel-ts/.vercelignore +4 -0
  65. package/templates/vercel-ts/README.md +32 -0
  66. package/templates/vercel-ts/_gitignore +4 -0
  67. package/templates/vercel-ts/api/interactions.js +5 -0
  68. package/templates/vercel-ts/index.html +235 -0
  69. package/templates/vercel-ts/package.json +25 -0
  70. package/templates/vercel-ts/src/DevLayer.ts +135 -0
  71. package/templates/vercel-ts/src/commands/fun/joke.ts +125 -0
  72. package/templates/vercel-ts/src/commands/index.ts +14 -0
  73. package/templates/vercel-ts/src/commands/reply/ping.ts +12 -0
  74. package/templates/vercel-ts/src/commands/utility/help.ts +73 -0
  75. package/templates/vercel-ts/src/commands/utility/index.ts +14 -0
  76. package/templates/vercel-ts/src/commands/utility/info.ts +56 -0
  77. package/templates/vercel-ts/src/commands/utility/profile.ts +35 -0
  78. package/templates/vercel-ts/src/index.ts +48 -0
  79. package/templates/vercel-ts/src/spawner.js +18 -0
@@ -0,0 +1,60 @@
1
+ import {
2
+ MessageFlags,
3
+ MediaGalleryBuilder,
4
+ MediaGalleryItemBuilder,
5
+ TextDisplayBuilder,
6
+ SeparatorBuilder,
7
+ SeparatorSpacingSize,
8
+ ButtonBuilder,
9
+ ButtonStyle,
10
+ ActionRowBuilder,
11
+ } from "discord.https";
12
+
13
+ // https://discordhttps.js.org/classes/interactionRouter.InteractionRouter.html
14
+ import { InteractionRouter } from "discord.https/router";
15
+
16
+ const router = new InteractionRouter();
17
+
18
+ // https://discord.com/developers/docs/change-log/2025-04-22-components-v2
19
+ const components = [
20
+ new TextDisplayBuilder().setContent("Served via `src/utility/info.js`"),
21
+ new TextDisplayBuilder().setContent("Related Links"),
22
+ new ActionRowBuilder().addComponents(
23
+ new ButtonBuilder()
24
+ .setStyle(ButtonStyle.Link)
25
+ .setLabel("Github")
26
+ .setEmoji({
27
+ name: "⭐",
28
+ })
29
+ .setURL("https://google.com"),
30
+ new ButtonBuilder()
31
+ .setStyle(ButtonStyle.Link)
32
+ .setLabel("Node Package")
33
+ .setEmoji({
34
+ name: "📦",
35
+ })
36
+ .setURL("https://www.npmjs.com/package/discord.https"),
37
+ new ButtonBuilder()
38
+ .setStyle(ButtonStyle.Link)
39
+ .setLabel("Documentation")
40
+ .setEmoji({
41
+ name: "📘",
42
+ })
43
+ .setURL("https://discordhttps.js.org/")
44
+ ),
45
+ ];
46
+
47
+ // https://discordhttps.js.org/classes/interactionRouter.InteractionRouter.html#command
48
+ router.command(
49
+ (builder) =>
50
+ builder
51
+ .setName("info")
52
+ .setDescription("Gives you information about discord.https"),
53
+ async (interaction) =>
54
+ await interaction.reply({
55
+ flags: MessageFlags.IsComponentsV2,
56
+ components: components,
57
+ })
58
+ );
59
+
60
+ export default router;
@@ -0,0 +1,36 @@
1
+ // https://discordhttps.js.org/classes/interactionRouter.InteractionRouter.html
2
+ import { InteractionRouter } from "discord.https/router";
3
+
4
+ const router = new InteractionRouter();
5
+
6
+ // https://discordhttps.js.org/classes/interactionRouter.InteractionRouter.html#usercontextmenu
7
+
8
+ // https://discord.com/developers/docs/reference#image-formatting
9
+
10
+ router.userContextMenu(
11
+ (builder) => builder.setName("profile"),
12
+ async (interaction) => {
13
+ const user = interaction.targetUser;
14
+
15
+ // In the case of endpoints that support GIFs, the hash will begin with a_ if it is available in GIF format.
16
+ const avatarFormat =
17
+ user.avatar && user.avatar.startsWith("a_") ? "gif" : "png";
18
+
19
+ const avatarUrl = user.avatar
20
+ ? `/avatars/${user.id}/${user.avatar}.${avatarFormat}`
21
+
22
+ : // In the case of the Default User Avatar endpoint, the value for index depends on whether the user is migrated to the new username system.
23
+ // For users on the new username system, index will be (user_id >> 22) % 6.
24
+ // For users on the legacy username system, index will be discriminator % 5.
25
+ `/embed/avatars/${
26
+ Number(user.discriminator)
27
+ ? Number(user.discriminator) % 5
28
+ : (BigInt(user.id) >> 22n) % 6n
29
+ }.png`;
30
+
31
+ // https://discord.com/developers/docs/reference#image-formatting
32
+ await interaction.reply(`https://cdn.discordapp.com/${avatarUrl}`);
33
+ }
34
+ );
35
+
36
+ export default router;
@@ -0,0 +1,18 @@
1
+ // ------------It is recommended not to modify this spawner code------------
2
+
3
+ import { spawn } from "child_process";
4
+ import chalk from "chalk"; // npm install chalk
5
+
6
+ import { tunnelLayer } from "./DevLayer";
7
+
8
+ await tunnelLayer();
9
+ console.log(chalk.greenBright("Tunnel spawned and ready!\n\n"));
10
+
11
+ const vercel = spawn("vercel", ["dev"], {
12
+ stdio: "inherit", // we’ll handle colors ourselves
13
+ });
14
+ 3;
15
+
16
+ vercel.on("close", (code) => {
17
+ console.log(chalk.yellow(`Vercel dev exited with code ${code}`));
18
+ });
@@ -0,0 +1,4 @@
1
+ /*
2
+ !api
3
+ !vercel.json
4
+ !*.html
@@ -0,0 +1,32 @@
1
+ ## Discord.https
2
+
3
+ Vercel’s serverless functions must be placed inside the `api` directory.
4
+
5
+ By default, the URL is: `<vercel_url>/api/interactions`
6
+
7
+ More details:
8
+
9
+ * [Vercel](https://vercel.com)
10
+ * [Vercel Functions Documentation](https://vercel.com/docs/functions)
11
+
12
+ ### How to deploy
13
+
14
+ Run this command to deploy to production:
15
+
16
+ ```bash
17
+ npx vercel --prod
18
+ ```
19
+
20
+ ### Local development
21
+
22
+ Run your project locally with:
23
+
24
+ ```bash
25
+ npm run dev
26
+ ```
27
+
28
+ **A layer of local tunnel is embedded into the code for easier development**. This means requests pass through multiple layers: Discord → local tunnel → local Vercel server on your computer → your functions. The Induced latency is usually just a few milliseconds and depends on your internet connection. **This local tunneling layer only runs on your machine during development and can be removed if desired.**
29
+
30
+ For more information, view `DevLayer.ts` or, `DevLayer.js`
31
+
32
+ You don’t need a separate TypeScript build step—Vercel handles it automatically.
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ .git
3
+ .env
4
+ .vercel
@@ -0,0 +1,5 @@
1
+ import client from "../src/index.js";
2
+
3
+ export default async function handler(req, res) {
4
+ return await client.listen("api/interactions", req, res);
5
+ }
@@ -0,0 +1,235 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Redirecting...</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
16
+ "Oxygen", "Ubuntu", "Cantarell", sans-serif;
17
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
18
+ color: #ffffff;
19
+ min-height: 100vh;
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ padding: 2rem;
24
+ overflow: hidden;
25
+ }
26
+
27
+ .container {
28
+ text-align: center;
29
+ max-width: 600px;
30
+ animation: fadeIn 0.6s ease-out;
31
+ }
32
+
33
+ @keyframes fadeIn {
34
+ from {
35
+ opacity: 0;
36
+ transform: translateY(20px);
37
+ }
38
+ to {
39
+ opacity: 1;
40
+ transform: translateY(0);
41
+ }
42
+ }
43
+
44
+ .icon {
45
+ width: 80px;
46
+ height: 80px;
47
+ margin: 0 auto 2rem;
48
+ background: rgba(255, 255, 255, 0.05);
49
+ border-radius: 50%;
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: center;
53
+ border: 2px solid rgba(255, 255, 255, 0.1);
54
+ animation: pulse 2s ease-in-out infinite;
55
+ }
56
+
57
+ @keyframes pulse {
58
+ 0%,
59
+ 100% {
60
+ transform: scale(1);
61
+ opacity: 1;
62
+ }
63
+ 50% {
64
+ transform: scale(1.05);
65
+ opacity: 0.8;
66
+ }
67
+ }
68
+
69
+ .icon svg {
70
+ width: 40px;
71
+ height: 40px;
72
+ stroke: #ffffff;
73
+ }
74
+
75
+ h1 {
76
+ font-size: 2rem;
77
+ font-weight: 600;
78
+ margin-bottom: 1rem;
79
+ line-height: 1.2;
80
+ letter-spacing: -0.02em;
81
+ }
82
+
83
+ .message {
84
+ font-size: 1.125rem;
85
+ color: #a0a0a0;
86
+ margin-bottom: 2rem;
87
+ line-height: 1.6;
88
+ }
89
+
90
+ .redirect-info {
91
+ display: inline-block;
92
+ background: rgba(255, 255, 255, 0.05);
93
+ padding: 1rem 1.5rem;
94
+ border-radius: 12px;
95
+ border: 1px solid rgba(255, 255, 255, 0.1);
96
+ margin-bottom: 2rem;
97
+ }
98
+
99
+ .redirect-text {
100
+ font-size: 0.875rem;
101
+ color: #808080;
102
+ margin-bottom: 0.5rem;
103
+ }
104
+
105
+ .redirect-path {
106
+ font-family: "Courier New", monospace;
107
+ font-size: 1rem;
108
+ color: #ffffff;
109
+ font-weight: 500;
110
+ }
111
+
112
+ .countdown-container {
113
+ margin-top: 2rem;
114
+ }
115
+
116
+ .countdown-label {
117
+ font-size: 0.875rem;
118
+ color: #808080;
119
+ margin-bottom: 1rem;
120
+ text-transform: uppercase;
121
+ letter-spacing: 0.1em;
122
+ }
123
+
124
+ .countdown {
125
+ font-size: 4rem;
126
+ font-weight: 700;
127
+ color: #ffffff;
128
+ font-variant-numeric: tabular-nums;
129
+ animation: countdownPulse 1s ease-in-out infinite;
130
+ }
131
+
132
+ @keyframes countdownPulse {
133
+ 0%,
134
+ 100% {
135
+ transform: scale(1);
136
+ }
137
+ 50% {
138
+ transform: scale(1.1);
139
+ }
140
+ }
141
+
142
+ .progress-bar {
143
+ width: 100%;
144
+ height: 4px;
145
+ background: rgba(255, 255, 255, 0.1);
146
+ border-radius: 2px;
147
+ margin-top: 2rem;
148
+ overflow: hidden;
149
+ }
150
+
151
+ .progress-fill {
152
+ height: 100%;
153
+ background: linear-gradient(90deg, #ffffff 0%, #a0a0a0 100%);
154
+ border-radius: 2px;
155
+ animation: progress 5s linear forwards;
156
+ }
157
+
158
+ @keyframes progress {
159
+ from {
160
+ width: 100%;
161
+ }
162
+ to {
163
+ width: 0%;
164
+ }
165
+ }
166
+
167
+ @media (max-width: 640px) {
168
+ h1 {
169
+ font-size: 1.5rem;
170
+ }
171
+
172
+ .message {
173
+ font-size: 1rem;
174
+ }
175
+
176
+ .countdown {
177
+ font-size: 3rem;
178
+ }
179
+ }
180
+ </style>
181
+ </head>
182
+ <body>
183
+ <div class="container">
184
+ <div class="icon">
185
+ <svg
186
+ viewBox="0 0 24 24"
187
+ fill="none"
188
+ stroke-width="2"
189
+ stroke-linecap="round"
190
+ stroke-linejoin="round"
191
+ >
192
+ <circle cx="12" cy="12" r="10"></circle>
193
+ <line x1="12" y1="8" x2="12" y2="12"></line>
194
+ <line x1="12" y1="16" x2="12.01" y2="16"></line>
195
+ </svg>
196
+ </div>
197
+
198
+ <h1>The path you visited is incorrect</h1>
199
+
200
+ <p class="message">
201
+ Don't worry, we're automatically redirecting you to the correct
202
+ location.
203
+ </p>
204
+
205
+ <div class="redirect-info">
206
+ <div class="redirect-text">Redirecting to</div>
207
+ <div class="redirect-path">/api/interactions</div>
208
+ </div>
209
+
210
+ <div class="countdown-container">
211
+ <div class="countdown-label">Redirecting in</div>
212
+ <div class="countdown" id="countdown">5</div>
213
+ </div>
214
+
215
+ <div class="progress-bar">
216
+ <div class="progress-fill"></div>
217
+ </div>
218
+ </div>
219
+
220
+ <script>
221
+ let count = 5;
222
+ const countdownElement = document.getElementById("countdown");
223
+
224
+ const timer = setInterval(() => {
225
+ count--;
226
+ countdownElement.textContent = count;
227
+
228
+ if (count === 0) {
229
+ clearInterval(timer);
230
+ window.location.href = "/api/interactions";
231
+ }
232
+ }, 1000);
233
+ </script>
234
+ </body>
235
+ </html>
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "vercel",
3
+ "version": "1.0.0",
4
+ "description": "Discord HTTPS interaction callback bot powered by discord.https",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "dev": "node src/spawner.js",
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "type": "module",
11
+ "author": "",
12
+ "license": "ISC",
13
+ "dependencies": {
14
+ "@discordhttps/vercel-adapter": "^1.0.4",
15
+ "@vercel/functions": "^3.1.1",
16
+ "chalk": "^5.6.2",
17
+ "discord.https": "^3.0.15"
18
+ },
19
+ "devDependencies": {
20
+ "@types/localtunnel": "^2.0.4",
21
+ "@types/node": "^24.7.0",
22
+ "fast-deep-equal": "^3.1.3",
23
+ "localtunnel": "^2.0.2"
24
+ }
25
+ }
@@ -0,0 +1,135 @@
1
+ /*
2
+
3
+ ------------It is recommended not to modify this layer of code without proper knowledge------------
4
+
5
+
6
+ For Info:
7
+
8
+ Summary: This code is a development layer designed to ease development, with automation features such as auto local tunnel and automatic command registration.
9
+
10
+
11
+ 1. Automatic command registration
12
+
13
+ This layer provides support for automatic command registration by storing the last run command definitions in a file.
14
+ Later, these definitions are read and compared with the current command definitions.
15
+ If they are the same, there is no need to re-register, as nothing has changed.
16
+ If they are different, the command definitions have changed and require re-registration.
17
+
18
+
19
+ 2.Local Tunnel
20
+
21
+ Local tunnels establish secure pathways between your localhost and the Internet, making it possible to expose web applications for access from anywhere.
22
+
23
+ What LocalTunnel does:
24
+
25
+ - Imagine you’re running a website on your computer at http://localhost:3000.
26
+ - Normally, only you can see it because it’s “local” — it is only on your computer.
27
+ - LocalTunnel gives it a public URL, like https://awesome-name.domain.com, so discord can send a request to your computer.
28
+
29
+
30
+ Why this matters for Discord:
31
+
32
+ - Normally, when you open Discord, your computer sends requests to Discord’s servers — everything works because Discord is public.
33
+ - But if you want Discord to **send data to your computer** (like a webhook), your computer needs a public address.
34
+ - LocalTunnel provides that public URL, so Discord knows where to send the requests.
35
+
36
+ Why it’s useful:
37
+
38
+ - Testing webhooks (like Discord)
39
+ - Debugging APIs without deploying to a server
40
+ */
41
+
42
+ export async function tunnelLayer() {
43
+ if (process.env.NODE_ENV !== "production") {
44
+ const chalk = await import("chalk");
45
+ const localtunnel = (await import("localtunnel")).default;
46
+ const tunnel = await localtunnel({
47
+ port: 3000,
48
+ subdomain: "discord-https",
49
+ });
50
+ console.log(
51
+ chalk.default.yellow(
52
+ "[DevLayer] Warning: This URL is intended only for testing and developing your bot.\n" +
53
+ "Never use it in production or hosting.\n" +
54
+ "1. It may be slower, but this is not an issue for bot development.\n" +
55
+ "2. This message should not appear in a production/hosting environment. If it does, your environment variables are incorrect and need to be fixed.\n" +
56
+ "For more information, visit https://discord.com/invite/pSgfJ4K5ej\n\n"
57
+ )
58
+ );
59
+ console.log(
60
+ chalk.default.blue(
61
+ `[DevLayer] Interactions Endpoint URL: ${tunnel.url}/api/interactions\n`
62
+ )
63
+ );
64
+
65
+ console.log(
66
+ chalk.default.yellow(
67
+ `[DevLayer] Modify your interaction url with the above\n`
68
+ )
69
+ );
70
+
71
+ process.on("SIGINT", async () => {
72
+ tunnel.close();
73
+ console.log("[DevLayer] Tunnel has been shut down");
74
+ process.exit();
75
+ });
76
+ }
77
+ }
78
+
79
+ export async function commandRegistrarLayer(client: any, guildId?: string) {
80
+ if (process.env.NODE_ENV !== "production") {
81
+ const FILENAME = "__dev_layer_cache__";
82
+ const chalk = (await import("chalk")).default;
83
+ const isEqual = (await import("fast-deep-equal")).default;
84
+ const { writeFile, access, readFile, constants } = await import(
85
+ "node:fs/promises"
86
+ );
87
+ const { join } = await import("node:path");
88
+
89
+ let localDump = [];
90
+ const filePath = join(import.meta.dirname, FILENAME);
91
+
92
+ try {
93
+ await access(filePath, constants.F_OK);
94
+ const contains = await readFile(filePath, {
95
+ encoding: "utf-8",
96
+ });
97
+ const json = JSON.parse(contains);
98
+ localDump = json;
99
+ } catch {
100
+ await writeFile(filePath, "[]");
101
+ }
102
+
103
+ try {
104
+ // https://github.com/discordhttps/discord.https/blob/main/src/interactionRouter/internal.ts#L271
105
+ if (
106
+ !isEqual(
107
+ localDump,
108
+ // To remove undefined properties
109
+ JSON.parse(JSON.stringify(client.router.CommandDefinitions))
110
+ )
111
+ ) {
112
+ console.log(
113
+ chalk.yellowBright(
114
+ `[DevLayer] Command change detected, re-registering with discord in ${
115
+ guildId ? `Guild - ${guildId}` : "Global"
116
+ }...`
117
+ )
118
+ );
119
+ const registar = await client.getRegistar();
120
+
121
+ if (guildId) {
122
+ await registar.localSlashRegistar(guildId);
123
+ } else {
124
+ await registar.globalSlashRegistar();
125
+ }
126
+ writeFile(filePath, JSON.stringify(client.router.CommandDefinitions));
127
+ console.log(
128
+ chalk.greenBright("[DevLayer] Commands synced successfully!")
129
+ );
130
+ }
131
+ } catch (err) {
132
+ console.error("[DevLayer] Failed to sync commands:", err);
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,125 @@
1
+ // This code includes some upper-basic level concepts. If you are an absolute beginner, you may struggle to understand it.
2
+
3
+ import {
4
+ MessageFlags,
5
+ MediaGalleryBuilder,
6
+ MediaGalleryItemBuilder,
7
+ TextDisplayBuilder,
8
+ SeparatorBuilder,
9
+ SeparatorSpacingSize,
10
+ ButtonBuilder,
11
+ ButtonStyle,
12
+ ActionRowBuilder,
13
+ } from "discord.https";
14
+
15
+ // https://discordhttps.js.org/classes/interactionRouter.InteractionRouter.html
16
+ import { InteractionRouter } from "discord.https/router";
17
+
18
+ const router = new InteractionRouter();
19
+
20
+ // https://discord.com/developers/docs/change-log/2025-04-22-components-v2
21
+ const header = [
22
+ new MediaGalleryBuilder().addItems(
23
+ new MediaGalleryItemBuilder().setURL(
24
+ "https://raw.githubusercontent.com/discordhttps/discord.https/refs/heads/main/assets/logo.png"
25
+ )
26
+ ),
27
+ new TextDisplayBuilder().setContent(
28
+ "**Discord.https** is a robust, modular library for implementing Discord HTTP interactions."
29
+ ),
30
+ new SeparatorBuilder()
31
+ .setSpacing(SeparatorSpacingSize.Large)
32
+ .setDivider(true),
33
+ ];
34
+
35
+ const footer = [
36
+ new SeparatorBuilder()
37
+ .setSpacing(SeparatorSpacingSize.Large)
38
+ .setDivider(true),
39
+ new ActionRowBuilder().addComponents(
40
+ new ButtonBuilder()
41
+ .setStyle(ButtonStyle.Link)
42
+ .setLabel("Github")
43
+ .setEmoji({
44
+ name: "⭐",
45
+ })
46
+ .setURL("https://google.com"),
47
+ new ButtonBuilder()
48
+ .setStyle(ButtonStyle.Link)
49
+ .setLabel("Node Package")
50
+ .setEmoji({
51
+ name: "📦",
52
+ })
53
+ .setURL("https://www.npmjs.com/package/discord.https"),
54
+ new ButtonBuilder()
55
+ .setStyle(ButtonStyle.Link)
56
+ .setLabel("Documentation")
57
+ .setEmoji({
58
+ name: "📘",
59
+ })
60
+ .setURL("https://discordhttps.js.org/")
61
+ ),
62
+ ];
63
+ // https://discordhttps.js.org/classes/interactionRouter.InteractionRouter.html#command
64
+ router.command(
65
+ (builder) =>
66
+ builder
67
+ .setName("jokes")
68
+ .setDescription("Gives you random joke")
69
+ .addStringOption((option) =>
70
+ option
71
+ .setName("type")
72
+ .setDescription("Type of joke")
73
+ .addChoices(
74
+ { name: "general", value: "general" },
75
+ { name: "knock-knock", value: "knock-knock" },
76
+ { name: "programming", value: "programming" },
77
+ { name: "dad", value: "dad" }
78
+ )
79
+ .setRequired(true)
80
+ ),
81
+ async (interaction) => {
82
+ // Get the value of the "type" option from the slash command.
83
+ // The 'true' argument makes this required; if it's missing, an error is thrown.
84
+ const option = interaction.options.getString("type", true);
85
+
86
+ // Fetch a joke from the API based on the type (e.g., "general", "programming", etc.)
87
+ // This returns a Promise, so we await it to get the response.
88
+ // Simplest explanation for beginners, https://www.geeksforgeeks.org/node-js/rest-api-introduction
89
+ const response = await fetch(
90
+ `https://official-joke-api.appspot.com/jokes/${option}/random`
91
+ );
92
+
93
+ // Convert the response into JSON format.
94
+ // This allows us to access the joke's content as JavaScript objects.
95
+ // This process is called deserilization,
96
+
97
+ // The received data is in the format of an array: [{ setup: "", punchline: "" }]
98
+ // [body] uses array destructuring. A simple equivalent would be:
99
+ // var body = await response.json();
100
+ // body = body[0];
101
+
102
+ const [body] = (await response.json()) as [
103
+ {
104
+ type: string;
105
+ setup: string;
106
+ punchline: string;
107
+ id: number;
108
+ }
109
+ ];
110
+
111
+ // Create a message component (or text display) with the joke content.
112
+ const middlePart = new TextDisplayBuilder().setContent(
113
+ `**Joke**: ${body.setup}\n**Punchline**: ${body.punchline}`
114
+ );
115
+ // send the joke to the user.
116
+ await interaction.reply({
117
+ // - flags: special options for the message (e.g., IsComponentsV2 indicate newer component handling)
118
+ flags: MessageFlags.IsComponentsV2,
119
+ // Here we spread the arrays with `...` to flatten them into a single array.
120
+ components: [...header, middlePart, ...footer],
121
+ });
122
+ }
123
+ );
124
+
125
+ export default router;