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
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # Initialize discord.https Project
2
+
3
+ ## Install:
4
+
5
+ ```bash
6
+ npm create discord-https@latest
7
+ ```
8
+
9
+ Then follow the prompts to initialize your Discord.https project.
10
+
11
+ ## Currently Supported Server Environments
12
+
13
+ - NodeJS(persistent)
14
+ - Vercel Serverless
15
+ - Cloudflare Worker
package/index.js ADDED
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import mri from "mri";
6
+ import * as prompts from "@clack/prompts";
7
+ import colors from "picocolors";
8
+ const { blueBright, greenBright, magenta, reset, yellow, white, yellowBright, cyanBright, } = colors;
9
+ const argv = mri(process.argv.slice(2), {
10
+ alias: { h: "help", t: "template" },
11
+ boolean: ["help", "overwrite"],
12
+ string: ["template"],
13
+ });
14
+ const cwd = process.cwd();
15
+ // prettier-ignore
16
+ const helpMessage = `\
17
+ Usage: create-discord-https [OPTION]... [DIRECTORY]
18
+
19
+ Create a new discord.https project in JavaScript or TypeScript.
20
+ With no arguments, start the CLI in interactive mode.
21
+
22
+ Options:
23
+ -t, --template NAME use a specific template
24
+
25
+ Available templates:
26
+ ${greenBright('node-js')}
27
+ ${white('vercel-js')}
28
+ ${yellowBright('cloudflare-js')}
29
+ ${blueBright('node-ts')}
30
+ ${magenta('vercel-ts')}
31
+ ${cyanBright('cloudflare-ts')}`;
32
+ const SERVERTYPE = [
33
+ {
34
+ name: "node",
35
+ display: "⬡ Nodejs (persistent)",
36
+ color: greenBright,
37
+ flavour: [
38
+ {
39
+ name: "node",
40
+ display: "JavaScript",
41
+ color: yellow,
42
+ secret: {
43
+ token: [10 - 1, 11 - 1],
44
+ publicKey: [11 - 1, 15 - 1],
45
+ },
46
+ },
47
+ {
48
+ name: "node-ts",
49
+ display: "TypeScript (recommend)",
50
+ color: blueBright,
51
+ secret: {
52
+ token: [10 - 1, 11 - 1],
53
+ publicKey: [11 - 1, 15 - 1],
54
+ },
55
+ },
56
+ ],
57
+ },
58
+ {
59
+ name: "vercel",
60
+ display: "▲ Vercel serverless",
61
+ color: white,
62
+ flavour: [
63
+ {
64
+ name: "vercel",
65
+ display: "JavaScript",
66
+ color: yellow,
67
+ secret: {
68
+ token: [10 - 1, 11 - 1],
69
+ publicKey: [11 - 1, 15 - 1],
70
+ },
71
+ },
72
+ {
73
+ name: "vercel-ts",
74
+ display: "TypeScript (recommend)",
75
+ color: blueBright,
76
+ secret: {
77
+ token: [10 - 1, 11 - 1],
78
+ publicKey: [11 - 1, 15 - 1],
79
+ },
80
+ },
81
+ ],
82
+ },
83
+ {
84
+ name: "cloudflare",
85
+ // https://www.compart.com/en/unicode/U+2601
86
+ display: "☁ Cloudflare worker",
87
+ color: yellowBright,
88
+ flavour: [
89
+ {
90
+ name: "cloudflare",
91
+ display: "JavaScript",
92
+ color: yellow,
93
+ secret: {
94
+ token: [12 - 1, 15 - 1],
95
+ publicKey: [13 - 1, 19 - 1],
96
+ },
97
+ },
98
+ {
99
+ name: "cloudflare-ts",
100
+ display: "TypeScript (recommend)",
101
+ color: blueBright,
102
+ secret: {
103
+ token: [12 - 1, 15 - 1],
104
+ publicKey: [13 - 1, 19 - 1],
105
+ },
106
+ },
107
+ ],
108
+ },
109
+ ];
110
+ const TEMPLATES = SERVERTYPE.map((f) => f.flavour.map((v) => v.name)).reduce((a, b) => a.concat(b), []);
111
+ const renameFiles = {
112
+ _gitignore: ".gitignore",
113
+ };
114
+ const defaultTargetDir = "mybot";
115
+ async function init() {
116
+ const argTargetDir = argv._[0]
117
+ ? formatTargetDir(String(argv._[0]))
118
+ : undefined;
119
+ const argTemplate = argv.template;
120
+ const argOverwrite = argv.overwrite;
121
+ const help = argv.help;
122
+ if (help) {
123
+ console.log(helpMessage);
124
+ return;
125
+ }
126
+ const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
127
+ const cancel = () => prompts.cancel("Operation cancelled");
128
+ // 1. Get project name and target dir
129
+ let targetDir = argTargetDir;
130
+ if (!targetDir) {
131
+ const projectName = await prompts.text({
132
+ message: "Project name:",
133
+ defaultValue: defaultTargetDir,
134
+ placeholder: defaultTargetDir,
135
+ validate: (value) => {
136
+ return value.length === 0 || formatTargetDir(value).length > 0
137
+ ? undefined
138
+ : "Invalid project name";
139
+ },
140
+ });
141
+ if (prompts.isCancel(projectName))
142
+ return cancel();
143
+ targetDir = formatTargetDir(projectName);
144
+ }
145
+ // 2. Handle directory if exist and not empty
146
+ if (fs.existsSync(targetDir) && !isEmpty(targetDir)) {
147
+ const overwrite = argOverwrite
148
+ ? "yes"
149
+ : await prompts.select({
150
+ message: (targetDir === "."
151
+ ? "Current directory"
152
+ : `Target directory "${targetDir}"`) +
153
+ ` is not empty. choose how to proceed:`,
154
+ options: [
155
+ {
156
+ label: "Cancel operation",
157
+ value: "no",
158
+ },
159
+ {
160
+ label: "Remove existing files and continue",
161
+ value: "yes",
162
+ },
163
+ {
164
+ label: "Ignore files and continue",
165
+ value: "ignore",
166
+ },
167
+ ],
168
+ });
169
+ if (prompts.isCancel(overwrite))
170
+ return cancel();
171
+ switch (overwrite) {
172
+ case "yes":
173
+ emptyDir(targetDir);
174
+ break;
175
+ case "no":
176
+ cancel();
177
+ return;
178
+ }
179
+ }
180
+ // 3. Get package name
181
+ let packageName = path.basename(path.resolve(targetDir));
182
+ if (!isValidPackageName(packageName)) {
183
+ const packageNameResult = await prompts.text({
184
+ message: "Package name:",
185
+ defaultValue: toValidPackageName(packageName),
186
+ placeholder: toValidPackageName(packageName),
187
+ validate(dir) {
188
+ if (!isValidPackageName(dir)) {
189
+ return "Invalid package.json name";
190
+ }
191
+ },
192
+ });
193
+ if (prompts.isCancel(packageNameResult))
194
+ return cancel();
195
+ packageName = packageNameResult;
196
+ }
197
+ // 4. Choose a framework and flavour
198
+ let template = argTemplate;
199
+ let hasInvalidArgTemplate = false;
200
+ if (argTemplate && !TEMPLATES.includes(argTemplate)) {
201
+ template = undefined;
202
+ hasInvalidArgTemplate = true;
203
+ }
204
+ if (!template) {
205
+ const framework = await prompts.select({
206
+ message: hasInvalidArgTemplate
207
+ ? `"${argTemplate}" isn't a valid template. choose from below: `
208
+ : "Select a environment:",
209
+ options: SERVERTYPE.map((framework) => {
210
+ const frameworkColor = framework.color;
211
+ return {
212
+ label: frameworkColor(framework.display || framework.name),
213
+ value: framework,
214
+ };
215
+ }),
216
+ });
217
+ if (prompts.isCancel(framework))
218
+ return cancel();
219
+ const flavour = await prompts.select({
220
+ message: "Select a flavour:",
221
+ options: framework.flavour.map((flavour) => {
222
+ const flavourColor = flavour.color;
223
+ return {
224
+ label: flavourColor(flavour.display || flavour.name),
225
+ value: flavour.name,
226
+ };
227
+ }),
228
+ });
229
+ if (prompts.isCancel(flavour))
230
+ return cancel();
231
+ template = flavour;
232
+ }
233
+ const root = path.join(cwd, targetDir);
234
+ fs.mkdirSync(root, { recursive: true });
235
+ const pkgManager = pkgInfo ? pkgInfo.name : "npm";
236
+ const { secret } = SERVERTYPE.flatMap((f) => f.flavour).find((v) => v.name === template);
237
+ prompts.log.step(`Initializing project...`);
238
+ const templateDir = path.resolve(fileURLToPath(import.meta.url), "../..", `templates/${template}`);
239
+ const write = (file, content) => {
240
+ const targetPath = path.join(root, renameFiles[file] ?? file);
241
+ if (content) {
242
+ fs.writeFileSync(targetPath, content);
243
+ }
244
+ else {
245
+ copy(path.join(templateDir, file), targetPath);
246
+ }
247
+ };
248
+ const files = fs.readdirSync(templateDir);
249
+ for (const file of files.filter((f) => f !== "package.json")) {
250
+ write(file);
251
+ }
252
+ const pkg = JSON.parse(fs.readFileSync(path.join(templateDir, `package.json`), "utf-8"));
253
+ pkg.name = packageName;
254
+ write("package.json", JSON.stringify(pkg, null, 2) + "\n");
255
+ const ext = template.split("-")[1] ? "ts" : "js";
256
+ await setupSecret(root, `src/index.${ext}`, secret);
257
+ await setupSubdomain(root, `src/DevLayer.${ext}`);
258
+ let doneMessage = "";
259
+ const cdProjectName = path.relative(cwd, root);
260
+ doneMessage += `All done! Execute:\n`;
261
+ if (root !== cwd) {
262
+ doneMessage += `\n cd ${cdProjectName.includes(" ") ? `"${cdProjectName}"` : cdProjectName}`;
263
+ }
264
+ switch (pkgManager) {
265
+ case "yarn":
266
+ doneMessage += "\n yarn";
267
+ doneMessage += "\n yarn dev";
268
+ break;
269
+ default:
270
+ doneMessage += `\n ${pkgManager} install`;
271
+ doneMessage += `\n ${pkgManager} run dev`;
272
+ break;
273
+ }
274
+ prompts.outro(doneMessage);
275
+ }
276
+ function formatTargetDir(targetDir) {
277
+ return targetDir.trim().replace(/\/+$/g, "");
278
+ }
279
+ function copy(src, dest) {
280
+ const stat = fs.statSync(src);
281
+ if (stat.isDirectory()) {
282
+ copyDir(src, dest);
283
+ }
284
+ else {
285
+ fs.copyFileSync(src, dest);
286
+ }
287
+ }
288
+ function isValidPackageName(projectName) {
289
+ return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(projectName);
290
+ }
291
+ function toValidPackageName(projectName) {
292
+ return projectName
293
+ .trim()
294
+ .toLowerCase()
295
+ .replace(/\s+/g, "-")
296
+ .replace(/^[._]/, "")
297
+ .replace(/[^a-z\d\-~]+/g, "-");
298
+ }
299
+ function copyDir(srcDir, destDir) {
300
+ fs.mkdirSync(destDir, { recursive: true });
301
+ for (const file of fs.readdirSync(srcDir)) {
302
+ const srcFile = path.resolve(srcDir, file);
303
+ const destFile = path.resolve(destDir, file);
304
+ copy(srcFile, destFile);
305
+ }
306
+ }
307
+ function isEmpty(path) {
308
+ const files = fs.readdirSync(path);
309
+ return files.length === 0 || (files.length === 1 && files[0] === ".git");
310
+ }
311
+ function emptyDir(dir) {
312
+ if (!fs.existsSync(dir)) {
313
+ return;
314
+ }
315
+ for (const file of fs.readdirSync(dir)) {
316
+ if (file === ".git") {
317
+ continue;
318
+ }
319
+ fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
320
+ }
321
+ }
322
+ function pkgFromUserAgent(userAgent) {
323
+ if (!userAgent)
324
+ return undefined;
325
+ const pkgSpec = userAgent.split(" ")[0];
326
+ const pkgSpecArr = pkgSpec.split("/");
327
+ return {
328
+ name: pkgSpecArr[0],
329
+ version: pkgSpecArr[1],
330
+ };
331
+ }
332
+ async function setupSecret(root, entryPoint, secret) {
333
+ const botToken = await prompts.text({
334
+ message: "Enter your Discord Bot Token:",
335
+ validate: (value) => value.length === 0 ? "Token cannot be empty" : undefined,
336
+ });
337
+ if (prompts.isCancel(botToken))
338
+ return prompts.cancel("Operation cancelled");
339
+ const publicKey = await prompts.text({
340
+ message: "Enter your Discord Public Key:",
341
+ validate: (value) => value.length === 0 ? "Public Key cannot be empty" : undefined,
342
+ });
343
+ if (prompts.isCancel(publicKey))
344
+ return prompts.cancel("Operation cancelled");
345
+ const entryFile = path.join(root, entryPoint);
346
+ if (!fs.existsSync(entryFile)) {
347
+ prompts.cancel("Error while writing token: file not found.");
348
+ return; // stop execution
349
+ }
350
+ let fileContent = fs.readFileSync(entryFile, "utf-8").split("\n");
351
+ const tokenPlaceholder = "PUT_YOUR_TOKEN_HERE";
352
+ const publickeyPlaceholder = "PUT_YOUR_PUBLIC_KEY_HERE";
353
+ const tokenLine = fileContent[secret.token[0]];
354
+ fileContent[secret.token[0]] =
355
+ tokenLine.slice(0, secret.token[1]) +
356
+ botToken +
357
+ tokenLine.slice(secret.token[1] + tokenPlaceholder.length);
358
+ const publicKeyLine = fileContent[secret.publicKey[0]];
359
+ fileContent[secret.publicKey[0]] =
360
+ publicKeyLine.slice(0, secret.publicKey[1]) +
361
+ publicKey +
362
+ publicKeyLine.slice(secret.publicKey[1] + publickeyPlaceholder.length);
363
+ fs.writeFileSync(entryFile, fileContent.join("\n"));
364
+ prompts.log.success("Done, Secrets have been hardcoded however, it is recommended to switch to environment variables!");
365
+ }
366
+ async function setupSubdomain(root, DevLayerEntryPoint) {
367
+ const entryFile = path.join(root, DevLayerEntryPoint);
368
+ if (!fs.existsSync(entryFile)) {
369
+ prompts.cancel("Error while writing sub domain: file not found.");
370
+ return; // stop execution
371
+ }
372
+ let fileContent = fs.readFileSync(entryFile, "utf-8").split("\n");
373
+ const SubDomainPrefix = "discord-https";
374
+ // discord-https-timestamp-number
375
+ const subdomain = `${SubDomainPrefix}-${Date.now()}-${Math.floor(Math.random() * 9)}`;
376
+ const row = 48 - 1; //0th index
377
+ const column = 19 - 1; //0th index
378
+ const tokenLine = fileContent[row];
379
+ fileContent[row] =
380
+ tokenLine.slice(0, column) +
381
+ subdomain +
382
+ tokenLine.slice(column + SubDomainPrefix.length);
383
+ fs.writeFileSync(entryFile, fileContent.join("\n"));
384
+ prompts.log.info("Use `npm run dev` over directly invoking the command, as this will create a tunnel layer except for server environment `nodejs` with a flavor of `typescript`.");
385
+ prompts.log.info("For more information, see the README.md file.");
386
+ }
387
+ init().catch((e) => {
388
+ console.error(e);
389
+ });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "create-discord-https",
3
+ "description": "Create Discord.https-powered bots with one command.",
4
+ "version": "1.0.0",
5
+ "bin": {
6
+ "create-discord-https": "index.js"
7
+ },
8
+ "author": "discord.https",
9
+ "type": "module",
10
+ "files": [
11
+ "index.js",
12
+ "templates"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc --build"
16
+ },
17
+ "engines": {
18
+ "node": "^20.19.0 || >=22.12.0"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/discordhttps/create-discord-https.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/discordhttps/create-discord-https/issues"
26
+ },
27
+ "license": "MIT",
28
+ "homepage": "https://github.com/discordhttps/create-discord-https#readme",
29
+ "devDependencies": {
30
+ "@clack/prompts": "^0.11.0",
31
+ "@types/node": "^24.3.1",
32
+ "mri": "^1.2.0",
33
+ "picocolors": "^1.1.1"
34
+ }
35
+ }
@@ -0,0 +1,32 @@
1
+ ## Discord.https
2
+
3
+ Cloudflare Workers let you run serverless functions at the edge. Your Discord interactions endpoint must be deployed as a Worker script.
4
+
5
+ By default, the URL is: <your_worker_subdomain>.workers.dev/interactions
6
+
7
+ **Note:** You can ignore any warnings in src\DevLayer.js, as the code will not run in production as long as the environment is set to production, which npm run deploy does automatically.
8
+
9
+ More details:
10
+
11
+ - [cloudflare](https://cloudflare.com)
12
+ - [cloudflare Workers](https://workers.cloudflare.com)
13
+
14
+ ### How to deploy
15
+
16
+ Run this command to deploy to production:
17
+
18
+ ```bash
19
+ npm run deploy
20
+ ```
21
+
22
+ ### Local development
23
+
24
+ Run your project locally with:
25
+
26
+ ```bash
27
+ npx wrangler dev
28
+ ```
29
+
30
+ **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.**
31
+
32
+ For more information, view `DevLayer.ts` or, `DevLayer.js`
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ .git
3
+ .env
@@ -0,0 +1,26 @@
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
+ "deploy": "npx wrangler deploy --env production",
9
+ "test": "echo \"Error: no test specified\" && exit 1"
10
+ },
11
+ "type": "module",
12
+ "author": "",
13
+ "license": "ISC",
14
+ "dependencies": {
15
+ "@discordhttps/cloudflare-adapter": "^1.0.2",
16
+ "@discordhttps/vercel-adapter": "^1.0.4",
17
+ "@vercel/functions": "^3.1.1",
18
+ "chalk": "^5.6.2",
19
+ "discord.https": "^3.0.15"
20
+ },
21
+ "devDependencies": {
22
+ "fast-deep-equal": "^3.1.3",
23
+ "localtunnel": "^2.0.2",
24
+ "wrangler": "^4.42.0"
25
+ }
26
+ }
@@ -0,0 +1,139 @@
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 = (
46
+ await import("localtunnel").catch((e) => {
47
+ throw new Error("localtunnel module not found");
48
+ })
49
+ ).default;
50
+ const tunnel = await localtunnel({
51
+ port: 3000,
52
+ subdomain: "discord-https",
53
+ });
54
+ console.log(
55
+ chalk.default.yellow(
56
+ "[DevLayer] Warning: This URL is intended only for testing and developing your bot.\n" +
57
+ "Never use it in production or hosting.\n" +
58
+ "1. It may be slower, but this is not an issue for bot development.\n" +
59
+ "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" +
60
+ "For more information, visit https://discord.com/invite/pSgfJ4K5ej\n\n"
61
+ )
62
+ );
63
+ console.log(
64
+ chalk.default.blue(
65
+ `[DevLayer] Interactions Endpoint URL: ${tunnel.url}/interactions\n`
66
+ )
67
+ );
68
+
69
+ console.log(
70
+ chalk.default.yellow(
71
+ `[DevLayer] Modify your interaction url with the above\n`
72
+ )
73
+ );
74
+
75
+ process.on("SIGINT", async () => {
76
+ tunnel.close();
77
+ console.log("[DevLayer] Tunnel has been shut down");
78
+ process.exit();
79
+ });
80
+ }
81
+ }
82
+
83
+ export async function commandRegistrarLayer(client, guildId) {
84
+ if (process.env.NODE_ENV !== "production") {
85
+ const FILENAME = "__dev_layer_cache__";
86
+ const chalk = (await import("chalk")).default;
87
+ const isEqual = (await import("fast-deep-equal")).default;
88
+ const { writeFile, access, readFile, constants } = await import(
89
+ "node:fs/promises"
90
+ );
91
+ const { join } = await import("node:path");
92
+
93
+ let localDump = [];
94
+ const filePath = join(import.meta.dirname, FILENAME);
95
+
96
+ try {
97
+ await access(filePath, constants.F_OK);
98
+ const contains = await readFile(filePath, {
99
+ encoding: "utf-8",
100
+ });
101
+ const json = JSON.parse(contains);
102
+ localDump = json;
103
+ } catch {
104
+ await writeFile(filePath, "[]");
105
+ }
106
+
107
+ try {
108
+ // https://github.com/discordhttps/discord.https/blob/main/src/interactionRouter/internal.ts#L271
109
+ if (
110
+ !isEqual(
111
+ localDump,
112
+ // To remove undefined properties
113
+ JSON.parse(JSON.stringify(client.router.CommandDefinitions))
114
+ )
115
+ ) {
116
+ console.log(
117
+ chalk.yellowBright(
118
+ `[DevLayer] Command change detected, re-registering with discord in ${
119
+ guildId ? `Guild - ${guildId}` : "Global"
120
+ }...`
121
+ )
122
+ );
123
+ const registar = await client.getRegistar();
124
+
125
+ if (guildId) {
126
+ await registar.localSlashRegistar(guildId);
127
+ } else {
128
+ await registar.globalSlashRegistar();
129
+ }
130
+ writeFile(filePath, JSON.stringify(client.router.CommandDefinitions));
131
+ console.log(
132
+ chalk.greenBright("[DevLayer] Commands synced successfully!")
133
+ );
134
+ }
135
+ } catch (err) {
136
+ console.error("[DevLayer] Failed to sync commands:", err);
137
+ }
138
+ }
139
+ }