diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..222861c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/README.md b/README.md index f2d9c84..f511db8 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,11 @@ ## For Miss Sneak's Discord community. **Please Read All These** + + + +# Credits + +[Discord.js](https://github.com/discordjs) for the [discord.js](https://github.com/discordjs/discord.js) library. + +[Hidden Devs](https://github.com/HiddenDevs) for the [discord-prompts](https://github.com/HiddenDevs/discord-prompts.js) library we use. \ No newline at end of file diff --git a/docs/Utils.md b/docs/Utils.md new file mode 100644 index 0000000..e8acbfb --- /dev/null +++ b/docs/Utils.md @@ -0,0 +1,31 @@ +# Shork Utilities + +Shork makes use of several utility files to make some parts of development easier. They can be found in the ../src/utils/ directory. + + +## Channel (TS) + +The `getChannel` function is used to get a text channel within the guild. It returns a Discord.js type of TextChannel. + +```ts +getChannel(): TextChannel +``` + +## Commands (TS) (SINGLETON) + +The `getCommandFiles` function is used to get all the command files within the /commands/ directory. +Due to the fact that it makes use of a redis cache this should only be used ONCE. All other needs to access command files should be done by grabbing from the Redis cache + +```ts +async getCommandFiles() +``` + +## Files (TS) + +The `getFilesRecursively` function should be used to get all the files of a specific directory. You shouldn't need to use this unless you're improving it or trying to get files from a directory (obviously) + +TODO: Make this more efficient or something. + +```ts +getFilesRecursively() +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8d1b3f6..3faf37b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@hidden-devs/discord-prompts.js": "^1.2.3", "@types/node": "^20.12.7", "bad-words-next": "^2.3.1", "chalk": "^4.1.2", @@ -912,6 +913,14 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hidden-devs/discord-prompts.js": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@hidden-devs/discord-prompts.js/-/discord-prompts.js-1.2.3.tgz", + "integrity": "sha512-zOtiRg4KSr2oteQXd3vp77Ys+7GVp/OUI/uniPcQrWlGt3aglWsl837k9LizgYQGL0sLIIrohu5Ft8zlI3LgaQ==", + "dependencies": { + "discord.js": "^14.14.1" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", diff --git a/package.json b/package.json index d94c8b7..3dfce83 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "author": "", "license": "ISC", "dependencies": { + "@hidden-devs/discord-prompts.js": "^1.2.3", "@types/node": "^20.12.7", "bad-words-next": "^2.3.1", "chalk": "^4.1.2", diff --git a/src/Shork.ts b/src/Shork.ts index 2a47faf..4b3cb75 100644 --- a/src/Shork.ts +++ b/src/Shork.ts @@ -1,24 +1,64 @@ import { Client, ClientOptions, PermissionFlagsBits } from "discord.js"; import { registerEvents } from "./events/register"; +import { getCommandFiles } from "./utils/commands"; +import { CommandReferenceType } from './registerCommands'; +import { Logger, LogType } from "./utils/logging"; class Shork extends Client { - eventsRegistered: number; + eventsRegistered: string[]; + commandsRegistered: CommandReferenceType[]; + logger: Logger constructor (options: ClientOptions) { super(options); - this.eventsRegistered = 0; - this.registerEvents() + this.logger = new Logger(__filename, LogType.DEBUG) + this.eventsRegistered = []; + this.commandsRegistered = []; + + this.registerEvents(); + this.registerCommands(); return this } - registerEvents() { - registerEvents(this) + async registerEvents() { + const eventsOr = await registerEvents(this) + if (eventsOr == false) { + this.logger.log(`Events unable to be registered for some reason.`, LogType.ERROR) + throw new Error("Fatal error: Events not registered.") + } + + this.eventsRegistered = eventsOr as string[]; + + } + + async registerCommands() { + const commandFiles = await getCommandFiles() + + commandFiles.forEach(async (file) => { + this.logger.log(file) + const cmdFile = await import(file); + const importedCmd = cmdFile.default as CommandReferenceType; + console.log(importedCmd) + if (importedCmd.data !== null && importedCmd.execute !== null) { + this.commandsRegistered.push(importedCmd); + } + }) + + this.logger.log(`Commands Registered (${this.commandsRegistered.length})`); } getRegisteredEvents() { - return this.eventsRegistered; + return {events: this.eventsRegistered, count: this.eventsRegistered.length} + } + + getRegisteredCommands() { + const commands: string[] = [] + this.commandsRegistered.forEach((command) => { + commands.push(command.data.name) + }) + return {commands: commands, count: commands.length} } } diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts index d4f98e8..217e54b 100644 --- a/src/commands/moderation/warn.ts +++ b/src/commands/moderation/warn.ts @@ -1,23 +1,33 @@ -import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder, SlashCommandMentionableOption, SlashCommandStringOption } from "discord.js"; +import { + ChatInputCommandInteraction, + PermissionFlagsBits, + SlashCommandBuilder, + SlashCommandMentionableOption, + SlashCommandStringOption, +} from "discord.js"; import { LogType, Logger } from "../../utils/logging"; export default { - data: new SlashCommandBuilder() - .setName('warn') - .setDescription('Warn a user.') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) - .addMentionableOption(option => - option.setName('user') - .setDescription('The user to warn.') - .setRequired(true)) - .addStringOption(option => - option.setName('reason') - .setDescription('The reason you\'re warning the user for') - .setRequired(false)), + data: new SlashCommandBuilder() + .setName("warn") + .setDescription("Warn a user.") + .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) + .addMentionableOption((option) => + option + .setName("user") + .setDescription("The user to warn.") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("reason") + .setDescription("The reason you're warning the user for") + .setRequired(false) + ), - async execute(interaction: ChatInputCommandInteraction) { - const logger = new Logger(__filename, LogType.DEBUG) + async execute(interaction: ChatInputCommandInteraction) { + const logger = new Logger(__filename, LogType.DEBUG); - logger.log('warn command') - } -} + logger.log("warn command"); + }, +}; diff --git a/src/events/interactions/interactionCreate.ts b/src/events/interactions/interactionCreate.ts index 863ebf7..99d7c75 100644 --- a/src/events/interactions/interactionCreate.ts +++ b/src/events/interactions/interactionCreate.ts @@ -1,19 +1,21 @@ -import { BaseInteraction, Events, InteractionType, TextChannel } from "discord.js"; +import { + BaseInteraction, + Events, + InteractionType, + TextChannel, +} from "discord.js"; import { Shork } from "../../Shork"; import { getChannel } from "../../utils/channel"; import { shorkCache } from "../../db/cache"; +import { CommandReferenceType } from "../../registerCommands"; import { getCommandFiles } from "../../utils/commands"; export default { - name: Events.InteractionCreate, - execute: async (interaction: BaseInteraction) => { - if (interaction.type == InteractionType.ApplicationCommand) { - const redisCmdFiles = await shorkCache.get('commandFiles') - if (!redisCmdFiles) { - getCommandFiles() - } - - } + name: Events.InteractionCreate, + execute: async (interaction: BaseInteraction) => { + const client = interaction.client as Shork; + if (interaction.type == InteractionType.ApplicationCommand) { } -} \ No newline at end of file + }, +}; diff --git a/src/events/register.ts b/src/events/register.ts index 8b2cdee..1256758 100644 --- a/src/events/register.ts +++ b/src/events/register.ts @@ -7,7 +7,7 @@ import { LogType, Logger } from "../utils/logging"; const logger = new Logger(__filename, LogType.DEBUG) -async function bindEvent(client: Client, eventPath: string): Promise { +async function bindEvent(client: Shork, eventPath: string): Promise { let eventModule: ShorkEvent; try { @@ -42,23 +42,23 @@ async function getFilesRecursively(directory: string): Promise { return filePaths.flat(); } -export const registerEvents = async (client: Shork): Promise => { +export const registerEvents = async (client: Shork): Promise => { logger.log("Registering Events..."); try { const allFiles = await getFilesRecursively(__dirname); const filtered = allFiles.filter(file => (file.endsWith('.js') || file.endsWith('.ts')) && !file.endsWith('.d.ts')); - let eventCount = 0; + let events = [] for (const file of filtered) { const success = await bindEvent(client, file); if (success) { - eventCount++; + events.push(file) } } - logger.log(`Registered Events (${eventCount})`, LogType.SUCCESS); - client.eventsRegistered = eventCount; - return true; + logger.log(`Registered Events (${events.length})`, LogType.SUCCESS); + client.eventsRegistered = events; + return events } catch (err) { logger.log("Error registering events:", LogType.ERROR); return false; diff --git a/src/main.ts b/src/main.ts index 0ad3cd2..1585ef5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,8 @@ dotenv.config(); const logger = new Logger(__filename, LogType.DEBUG); +export const rootDir = __dirname; + const SHORK = new Shork({ intents: [ GatewayIntentBits.Guilds, @@ -21,6 +23,4 @@ process.on('exit', (errCode) => { logger.log(`Process Exiting (${errCode})`, LogType.ERROR); }) -export const rootDir = __dirname; - SHORK.login(process.env.TOKEN); \ No newline at end of file diff --git a/src/registerCommands.ts b/src/registerCommands.ts index 859888a..a5849c3 100644 --- a/src/registerCommands.ts +++ b/src/registerCommands.ts @@ -18,7 +18,7 @@ const foldersPath = path.join(__dirname, 'commands'); console.log(foldersPath) const commandFolders = fs.readdirSync(foldersPath); -interface Command { +export interface CommandReferenceType { data: any; // Adjust this to match the type of data execute(interaction: ChatInputCommandInteraction): Promise; } @@ -29,7 +29,7 @@ for (const folder of commandFolders) { for (const file of commandFiles) { const filePath = path.join(commandsPath, file); - const command: Command = require(filePath).default; // Use .default to import the default export + const command: CommandReferenceType = require(filePath).default; // Use .default to import the default export if ('data' in command && 'execute' in command) { commands.push(command.data.toJSON()); } else { diff --git a/src/tests/mocks/MockClient.ts b/src/tests/mocks/MockClient.ts new file mode 100644 index 0000000..40d8191 --- /dev/null +++ b/src/tests/mocks/MockClient.ts @@ -0,0 +1,15 @@ +import { ClientOptions, REST } from "discord.js"; + +/** + * A mock client used for testing, derived from the official Client class from discord.js + * @extends {null} + */ +class MockClient { + rest: REST; + constructor(options: ClientOptions) { + // Rest manager of the MockClient + this.rest = new REST() + } +} + +export {MockClient} \ No newline at end of file diff --git a/src/utils/commands.ts b/src/utils/commands.ts index 624edc6..c2f7de5 100644 --- a/src/utils/commands.ts +++ b/src/utils/commands.ts @@ -9,6 +9,7 @@ import { getFilesRecursively } from "./files"; const logger = new Logger(__filename, LogType.DEBUG) export async function getCommandFiles() { + console.log(rootDir, typeof rootDir) const cmdDir = path.join(rootDir, 'commands') if (!fs.existsSync(cmdDir)) { logger.log(`Directory '${cmdDir}' does not exist. Failing.`, LogType.ERROR) @@ -19,5 +20,5 @@ export async function getCommandFiles() { const filteredCommands = commandFiles.filter(file => (file.endsWith('.js') || file.endsWith('.ts')) && !file.endsWith('.d.ts')); - shorkCache.set('commandFiles', JSON.stringify(filteredCommands)) + return filteredCommands; } \ No newline at end of file