Compare commits

..

No commits in common. "f88e14b34ac35e8be44cde95ca4142cc157df42e" and "d3d9a305d0b877974da1fc5389e0175a06b2e48d" have entirely different histories.

12 changed files with 332 additions and 1376 deletions

View File

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

View File

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

4
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,5 @@
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) => {
// Try executing command
try {
@ -27,21 +21,9 @@ 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) => {
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);
} catch (error) {
console.error(error);
@ -49,9 +31,7 @@ const genericExecute = async (interaction, command, name, description, cmdName)
};
export const name = Events.InteractionCreate;
/** @param {import('discord.js').Interaction} interaction */
export async function execute(interaction) {
/** @type {Module} */
let command = interaction.client.commands.get(interaction.commandName);
// Execute slash- and context-menu-commands

View File

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

View File

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

1524
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,52 +1,38 @@
import { readdir } from 'fs/promises';
import { join } from 'path';
import Module from 'module';
// Lists of required and optional attributes of command modules
const required = ['data', 'execute'];
const optional = ['autocomplete', 'modalSubmit'];
/**
* Recursively scans a directory for all files in it.
* @param {string} dir
* @returns {Promise<Array<string>>} Array of paths to the files within.
*/
export const getFiles = async (dir) => {
const dirents = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(
dirents.map((dirent) => {
const res = join(dir, dirent.name);
return dirent.isDirectory() ? getFiles(res) : res;
})
);
return Array.prototype.concat(...files);
};
/**
* Imports and checks a command from a path as a module.
* @param {string} filePath
* @returns {Promise<Module|0>}
*/
export const importAndCheck = async (filePath) => {
if (!filePath.endsWith('.js') || filePath.endsWith('.example.js')) {
// Skip this file
return 0;
}
const command = await import(filePath);
// Warn incomplete commands
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;
};
import { join } from 'path';
import { readdir } from 'fs/promises';
const required = ['data', 'execute'];
const optional = ['autocomplete', 'modalSubmit'];
export const getFiles = async (dir) => {
const dirents = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(dirents.map((dirent) => {
const res = join(dir, dirent.name);
return dirent.isDirectory() ? getFiles(res) : res;
}));
return Array.prototype.concat(...files);
};
export const importAndCheck = async (filePath) => {
if (!filePath.endsWith('.js') || filePath.endsWith('.example.js')) {
// Skip this file
return 0;
}
const command = await import(filePath);
// Warn incomplete commands
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;
};