Update template by merging fork 'DiscordJS-Example'

This commit is contained in:
Baipyrus 2024-03-25 01:54:13 +01:00
commit f88e14b34a
12 changed files with 1382 additions and 338 deletions

View File

@ -4,6 +4,7 @@ node_modules
.env .env
.env.* .env.*
!.env.example !.env.example
docs
# Ignore files for PNPM, NPM and YARN # Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml pnpm-lock.yaml

View File

@ -8,5 +8,8 @@
"ecmaVersion": "latest", "ecmaVersion": "latest",
"sourceType": "module" "sourceType": "module"
}, },
"rules": {} "plugins": ["jsdoc"],
"rules": {
"jsdoc/no-undefined-types": 1
}
} }

4
.gitignore vendored
View File

@ -2,4 +2,6 @@
node_modules node_modules
.env .env
.env.* .env.*
!.env.example !.env.example
commands/examples
docs

7
.jsdoc.conf.json Normal file
View File

@ -0,0 +1,7 @@
{
"source": {
"include": ["."],
"exclude": ["node_modules", "commands/examples"],
"includePattern": ".+\\.js(doc|x)?$"
}
}

View File

@ -4,6 +4,7 @@ node_modules
.env .env
.env.* .env.*
!.env.example !.env.example
docs
# Ignore files for PNPM, NPM and YARN # Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml pnpm-lock.yaml

View File

@ -3,13 +3,17 @@ import { REST, Routes } from 'discord.js';
import { join, dirname } from 'path'; import { join, dirname } from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { config } from 'dotenv'; import { config } from 'dotenv';
import Module from 'module';
config(); config();
// Construct and prepare an instance of the REST module // Construct and prepare an instance of the REST module
const rest = new REST().setToken(process.env.TOKEN); const rest = new REST().setToken(process.env.TOKEN);
// and deploy your commands! /**
* Calls HTTP PUT to register commands in discord.
* @param {Array<Object>} commands
*/
const putCommands = async (commands) => { const putCommands = async (commands) => {
try { try {
console.info(`[INFO] Started refreshing ${commands.length} application (/) commands.`); console.info(`[INFO] Started refreshing ${commands.length} application (/) commands.`);
@ -32,6 +36,7 @@ getFiles(cmdPath)
// For each command file // For each command file
.then(async (files) => .then(async (files) =>
(await Promise.all(files.map(importAndCheck))) (await Promise.all(files.map(importAndCheck)))
.filter((module) => module !== 0) .filter(/** @param {(Module|0)} module */ (module) => module !== 0)
.map((module) => module.data.toJSON()) .map(/** @param {Module} module */ (module) => module.data.toJSON())
).then(putCommands); )
.then(putCommands);

View File

@ -1,5 +1,11 @@
import { Events } from 'discord.js'; import { Events } from 'discord.js';
import Module from 'module';
/**
* A more precise execution function specifically to call the main property of a module.
* @param {import('discord.js').Interaction} interaction
* @param {Module} command
*/
const executeCommand = async (interaction, command) => { const executeCommand = async (interaction, command) => {
// Try executing command // Try executing command
try { try {
@ -21,9 +27,21 @@ const executeCommand = async (interaction, command) => {
} }
}; };
/**
* A generic execution function to call command methods.
* @param {import('discord.js').Interaction} interaction
* @param {Module} command
* @param {string} name
* @param {string=} description
* @param {string=} cmdName
*/
const genericExecute = async (interaction, command, name, description, cmdName) => { const genericExecute = async (interaction, command, name, description, cmdName) => {
try { try {
console.info(`[INFO] Command ${(cmdName ?? interaction.commandName) ?? 'anonymous'} ${description ?? `used "${name}"`}.`); console.info(
`[INFO] Command ${cmdName ?? interaction.commandName ?? 'anonymous'} ${
description ?? `used "${name}"`
}.`
);
await command[name](interaction); await command[name](interaction);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -31,7 +49,9 @@ const genericExecute = async (interaction, command, name, description, cmdName)
}; };
export const name = Events.InteractionCreate; export const name = Events.InteractionCreate;
/** @param {import('discord.js').Interaction} interaction */
export async function execute(interaction) { export async function execute(interaction) {
/** @type {Module} */
let command = interaction.client.commands.get(interaction.commandName); let command = interaction.client.commands.get(interaction.commandName);
// Execute slash- and context-menu-commands // Execute slash- and context-menu-commands

View File

@ -1,7 +1,8 @@
import { Events } from 'discord.js'; import { Events, Client } from 'discord.js';
export const name = Events.ClientReady; export const name = Events.ClientReady;
export const once = true; export const once = true;
/** @param {Client} client */
export function execute(client) { export function execute(client) {
console.info(`[INFO] Ready! Logged in as ${client.user.tag}`); console.info(`[INFO] Ready! Logged in as ${client.user.tag}`);
} }

View File

@ -1,23 +1,38 @@
import { Client, Collection, GatewayIntentBits } from 'discord.js'; import { Client, Collection, GatewayIntentBits, Partials } from 'discord.js';
import { getFiles, importAndCheck } from './shared.js'; import { getFiles, importAndCheck } from './shared.js';
import { Partials } from 'discord.js';
import { join, dirname } from 'path'; import { join, dirname } from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { config } from 'dotenv'; import { config } from 'dotenv';
import Module from 'module';
config(); config();
/**
* Main entry point, the bot logs on to discord.
* @param {Array<Module>} commands
* @param {Array<Module>} events
*/
const runClient = (commands, events) => { const runClient = (commands, events) => {
// Create a new client instance // Create a new client instance
const client = new Client({ const client = new Client({
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildMessageReactions
], ],
partials: [Partials.Message, Partials.Reaction] partials: [Partials.Message, Partials.Reaction]
}); });
/**
* The commands registered for this client.
* @type {Collection}
*/
client.commands = new Collection(); client.commands = new Collection();
commands.forEach((c) => client.commands.set(c.data.name, c)); commands.forEach((c) => client.commands.set(c.data.name, c));
// Register client events
events.forEach((e) => events.forEach((e) =>
e.once e.once
? client.once(e.name, (...args) => e.execute(...args)) ? client.once(e.name, (...args) => e.execute(...args))
@ -35,13 +50,10 @@ const evtPath = join(__dirname, 'events');
getFiles(cmdPath) getFiles(cmdPath)
// For each command file // For each command file
.then(async (files) => .then(async (files) =>
(await Promise.all(files.map(importAndCheck))) (await Promise.all(files.map(importAndCheck))).filter((module) => module !== 0)
.filter((module) => module !== 0) )
).then(async (commands) => { .then(async (commands) => {
const files = await getFiles(evtPath); const files = await getFiles(evtPath);
const events = await Promise.all( const events = await Promise.all(files.map(async (filePath) => await import(filePath)));
files.map(async (filePath) =>
await import(filePath)
));
runClient(commands, events); runClient(commands, events);
}); });

1536
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,18 +8,20 @@
"start": "node .", "start": "node .",
"deploy": "node deploy.js", "deploy": "node deploy.js",
"lint": "prettier --check . && eslint .", "lint": "prettier --check . && eslint .",
"format": "prettier --write ." "format": "prettier --write .",
"jsdoc": "rm -rf docs/ && jsdoc -c ./.jsdoc.conf.json -d docs/ -r ."
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"discord.js": "^14.14.1", "discord.js": "^14.14.1",
"dotenv": "^16.3.1", "dotenv": "^16.4.5",
"proxy-agent": "^6.3.1" "sqlite3": "^5.1.7"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-jsdoc": "^48.0.6",
"prettier": "^3.1.1" "prettier": "^3.1.1"
}, },
"type": "module" "type": "module"

View File

@ -1,38 +1,52 @@
import { join } from 'path'; import { readdir } from 'fs/promises';
import { readdir } from 'fs/promises'; import { join } from 'path';
import Module from 'module';
const required = ['data', 'execute'];
const optional = ['autocomplete', 'modalSubmit']; // Lists of required and optional attributes of command modules
const required = ['data', 'execute'];
export const getFiles = async (dir) => { const optional = ['autocomplete', 'modalSubmit'];
const dirents = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(dirents.map((dirent) => { /**
const res = join(dir, dirent.name); * Recursively scans a directory for all files in it.
return dirent.isDirectory() ? getFiles(res) : res; * @param {string} dir
})); * @returns {Promise<Array<string>>} Array of paths to the files within.
return Array.prototype.concat(...files); */
}; export const getFiles = async (dir) => {
const dirents = await readdir(dir, { withFileTypes: true });
export const importAndCheck = async (filePath) => { const files = await Promise.all(
if (!filePath.endsWith('.js') || filePath.endsWith('.example.js')) { dirents.map((dirent) => {
// Skip this file const res = join(dir, dirent.name);
return 0; return dirent.isDirectory() ? getFiles(res) : res;
} })
const command = await import(filePath); );
// Warn incomplete commands return Array.prototype.concat(...files);
if (!required.every((name) => name in command)) { };
console.error(
`[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.` /**
); * Imports and checks a command from a path as a module.
return 0; * @param {string} filePath
} * @returns {Promise<Module|0>}
const properties = optional.filter((name) => !(name in command)); */
if (properties.length > 0) export const importAndCheck = async (filePath) => {
properties.forEach((name) => if (!filePath.endsWith('.js') || filePath.endsWith('.example.js')) {
console.warn( // Skip this file
`[WARNING] The command at ${filePath} is missing an optional "${name}" property.` return 0;
) }
); const command = await import(filePath);
// Add command to collection // Warn incomplete commands
return command; if (!required.every((name) => name in command)) {
}; console.error(
`[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.`
);
return 0;
}
const properties = optional.filter((name) => !(name in command));
if (properties.length > 0)
properties.forEach((name) =>
console.warn(
`[WARNING] The command at ${filePath} is missing an optional "${name}" property.`
)
);
// Add command to collection
return command;
};