Added example of temporary solution for slash commands

This commit is contained in:
WatDuhHekBro 2021-12-10 04:22:50 -06:00
parent 69a8452574
commit 533ce58647
No known key found for this signature in database
GPG Key ID: E128514902DF8A05
10 changed files with 309 additions and 30 deletions

View File

@ -5,6 +5,7 @@
.husky/
Dockerfile
LICENSE
*.txt
# Specific to this repository
dist/

65
package-lock.json generated
View File

@ -10,9 +10,11 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@discordjs/builders": "^0.8.2",
"canvas": "^2.8.0",
"chalk": "^4.1.2",
"discord.js": "^13.3.0",
"dotenv": "^10.0.0",
"figlet": "^1.5.2",
"glob": "^7.2.0",
"inquirer": "^8.2.0",
@ -653,13 +655,13 @@
"dev": true
},
"node_modules/@discordjs/builders": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.8.1.tgz",
"integrity": "sha512-kYJMvZ/BjRD1/6G2t1pQop2yoJNUmYvvKeG4mOBUCHFmfb7WIeBFmN/eSiP3cVSfRx3lbNiyxkdd5JzhjQnGbg==",
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.8.2.tgz",
"integrity": "sha512-/YRd11SrcluqXkKppq/FAVzLIPRVlIVmc6X8ZklspzMIHDtJ+A4W37D43SHvLdH//+NnK+SHW/WeOF4Ts54PeQ==",
"dependencies": {
"@sindresorhus/is": "^4.2.0",
"discord-api-types": "^0.24.0",
"ow": "^0.28.1",
"ow": "^0.27.0",
"ts-mixer": "^6.0.0",
"tslib": "^2.3.1"
},
@ -2172,6 +2174,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
"engines": {
"node": ">=10"
}
},
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@ -4523,15 +4533,15 @@
}
},
"node_modules/ow": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.28.1.tgz",
"integrity": "sha512-1EZTywPZeUKac9gD7q8np3Aj+V54kvfIcjNEVNDSbG2Ys5xA5foW2HquvMMqgyWGLqIFMlc0Iq/HmyMHqN48sA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz",
"integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==",
"dependencies": {
"@sindresorhus/is": "^4.2.0",
"@sindresorhus/is": "^4.0.1",
"callsites": "^3.1.0",
"dot-prop": "^6.0.1",
"lodash.isequal": "^4.5.0",
"type-fest": "^2.3.4",
"type-fest": "^1.2.1",
"vali-date": "^1.0.0"
},
"engines": {
@ -4542,11 +4552,11 @@
}
},
"node_modules/ow/node_modules/type-fest": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.5.2.tgz",
"integrity": "sha512-WMbytmAs5PUTqwGJRE+WoRrD2S0bYFtHX8k4Y/1l18CG5kqA3keJud9pPQ/r30FE9n8XRFCXF9BbccHIZzRYJw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
"engines": {
"node": ">=12.20"
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@ -6378,13 +6388,13 @@
"dev": true
},
"@discordjs/builders": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.8.1.tgz",
"integrity": "sha512-kYJMvZ/BjRD1/6G2t1pQop2yoJNUmYvvKeG4mOBUCHFmfb7WIeBFmN/eSiP3cVSfRx3lbNiyxkdd5JzhjQnGbg==",
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.8.2.tgz",
"integrity": "sha512-/YRd11SrcluqXkKppq/FAVzLIPRVlIVmc6X8ZklspzMIHDtJ+A4W37D43SHvLdH//+NnK+SHW/WeOF4Ts54PeQ==",
"requires": {
"@sindresorhus/is": "^4.2.0",
"discord-api-types": "^0.24.0",
"ow": "^0.28.1",
"ow": "^0.27.0",
"ts-mixer": "^6.0.0",
"tslib": "^2.3.1"
}
@ -7576,6 +7586,11 @@
"is-obj": "^2.0.0"
}
},
"dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="
},
"duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@ -9265,22 +9280,22 @@
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"ow": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.28.1.tgz",
"integrity": "sha512-1EZTywPZeUKac9gD7q8np3Aj+V54kvfIcjNEVNDSbG2Ys5xA5foW2HquvMMqgyWGLqIFMlc0Iq/HmyMHqN48sA==",
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz",
"integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==",
"requires": {
"@sindresorhus/is": "^4.2.0",
"@sindresorhus/is": "^4.0.1",
"callsites": "^3.1.0",
"dot-prop": "^6.0.1",
"lodash.isequal": "^4.5.0",
"type-fest": "^2.3.4",
"type-fest": "^1.2.1",
"vali-date": "^1.0.0"
},
"dependencies": {
"type-fest": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.5.2.tgz",
"integrity": "sha512-WMbytmAs5PUTqwGJRE+WoRrD2S0bYFtHX8k4Y/1l18CG5kqA3keJud9pPQ/r30FE9n8XRFCXF9BbccHIZzRYJw=="
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="
}
}
},

View File

@ -5,19 +5,21 @@
"main": "dist/index.js",
"scripts": {
"build": "rimraf dist && tsc --project tsconfig.prod.json && npm prune --production",
"start": "node .",
"start": "node -r dotenv/config .",
"once": "tsc && npm start",
"dev": "tsc-watch --onSuccess \"npm run dev-instance\"",
"dev-fast": "tsc-watch --onSuccess \"node . dev\"",
"dev-instance": "rimraf dist && tsc && node . dev",
"dev-fast": "tsc-watch --onSuccess \"node -r dotenv/config . dev\"",
"dev-instance": "rimraf dist && tsc && node -r dotenv/config . dev",
"test": "jest",
"format": "prettier --write **/*",
"postinstall": "husky install"
},
"dependencies": {
"@discordjs/builders": "^0.8.2",
"canvas": "^2.8.0",
"chalk": "^4.1.2",
"discord.js": "^13.3.0",
"dotenv": "^10.0.0",
"figlet": "^1.5.2",
"glob": "^7.2.0",
"inquirer": "^8.2.0",

View File

@ -8,7 +8,7 @@ module.exports = {
jsxSingleQuote: false,
trailingComma: "none",
bracketSpacing: false,
jsxBracketSameLine: false,
bracketSameLine: false,
arrowParens: "always",
endOfLine: "auto" // Apparently, the GitHub repository still uses CRLF. I don't know how to force it to use LF, and until someone figures that out, I'm changing this to auto because I don't want more than one line ending commit.
};

View File

@ -0,0 +1,91 @@
-= [Fun] =-
/8ball <question: string>
/cookie all
/cookie (<@user>)
/eco show (<@user>)
/eco daily - "... Same as /eco get."
/eco get - "... Same as /eco daily."
/eco pay <@user> <amount: int>
/eco guild
/eco leaderboard - "... Same as /eco top."
/eco top - "... Same as /eco leaderboard."
/eco buy <item: string>
/eco shop
/eco monday
/eco bet <@user> <amount: int> <duration: string>
/eco award <@user> (<amount: int>)
/eco post
/eco delete --- "Operation successful. 5000 rows affected."
/figlet <input: string>
/insult
/love
/ok
/owoify <input>
/party
/pat (<@user>)
/poll <question: string> (<seconds: int>)
/ravi (<index: int>)
/thonk (<text: string>)
/urban <word: string>
/vaporwave <text: string>
/weather <city: string>
/whoami
/whois <@user>
-= [System] =- ("/admin other ..." if needed)
/admin prefix (<prefix: string>)
/admin messageembeds <enabled: boolean>
/admin welcome type <[none, text, graphical]: string>
/admin welcome channel <#channel>
/admin stream <#channel>
/admin defaultname (<name: string>)
/admin logs (<verbosity: string>)
/admin status
/admin clear <amount: int>
/admin nickname <nickname: string>
/admin guilds
/admin activity <type: string> (<description: string>)
/admin syslog
/admin autoroles list
/admin autoroles reset
/admin autoroles add <@role>
/admin autoroles remove <@role>
/admin streamroles set <@role> <category: string>
/admin streamroles remove <@role>
/webhook register <url: string>
/webhook delete <url: string>
-= [Utility] =-
/calc <expression: string>
/code
/desc <name: string>
/docs <query: string>
/emote <emotes: string>
/info
/info avatar (<@user>)
/info bot
/info guild <name: string>
/info guild <id: int>
/info <@user>
/invite (<flag: int>)
/lsemotes <regex: string> (<isCaseSensitive: boolean>)
/purge
/react <emotes: string> (<target: string>)
/say <message: string>
/scanemotes (<forceReset: boolean>)
/shorten <url: string>
/stream description set (<description: string>)
/stream description remove
/stream thumbnail set (<link: string>)
/stream thumbnail remove
/stream category set (<category: string>)
/stream category remove
/time show (<@user>)
/time setup
/time delete
/time utc
/time daylight
/todo show
/todo add <note: string>
/todo remove <note: string>
/todo clear

View File

@ -0,0 +1,16 @@
import {SlashCommandBuilder} from "@discordjs/builders";
import {CommandInteraction} from "discord.js";
import {registry} from "./whois";
export const header = new SlashCommandBuilder().setDescription("Tells you who you are");
export function handler(interaction: CommandInteraction) {
const {user} = interaction;
const id = user.id;
if (id in registry) {
interaction.reply({content: `${user} ${registry[id]}`, allowedMentions: {parse: []}});
} else {
interaction.reply("You haven't been added to the registry yet!");
}
}

View File

@ -1,8 +1,10 @@
import {User} from "discord.js";
import {Command, NamedCommand, getUserByNickname, RestCommand} from "onion-lasers";
import {SlashCommandBuilder} from "@discordjs/builders";
import {CommandInteraction} from "discord.js";
// Quotes must be used here or the numbers will change
const registry: {[id: string]: string} = {
export const registry: {[id: string]: string} = {
"465662909645848577": "You're an idiot, that's what.",
"306499531665833984":
"Kuma, you eldritch fuck, I demand you to release me from this Discord bot and let me see my Chromebook!",
@ -50,6 +52,24 @@ const registry: {[id: string]: string} = {
"138840343855497216": "your face is a whois entry"
};
export const header = new SlashCommandBuilder()
.setDescription("Tells you who the specified user is")
.addUserOption((option) =>
option.setName("target").setDescription("The person to inquire about").setRequired(true)
);
export function handler(interaction: CommandInteraction) {
const {options} = interaction;
const user = options.getUser("target", true);
const id = user.id;
if (id in registry) {
interaction.reply({content: `${user} ${registry[id]}`, allowedMentions: {parse: []}});
} else {
interaction.reply({content: `${user} hasn't been added to the registry yet!`, allowedMentions: {parse: []}});
}
}
export default new NamedCommand({
description: "Tells you who you or the specified user is.",
aliases: ["whoami"],

View File

@ -14,6 +14,9 @@ export const client = new Client({
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
Intents.FLAGS.DIRECT_MESSAGES
],
partials: [
"CHANNEL" // Needed so the bot can receive DM messages
]
});
@ -76,6 +79,7 @@ launch(client, path.join(__dirname, "commands"), {
});
// Initialize Modules //
import "./modules/slashCommands";
import "./modules/ready";
import "./modules/presence";
// TODO: Reimplement entire music system, contact Sink

View File

@ -0,0 +1,54 @@
import {client} from "../index";
import {join} from "path";
import {loadCommands} from "../modules/slashCommandsLoader";
loadCommands(join(__dirname, "..", "commands")).then((result) => {
const [headers, handlers] = result;
const useDevGuild = IS_DEV_MODE && !!process.env.DEV_GUILD;
// Send slash command data to Discord
client.on("ready", async () => {
try {
if (useDevGuild) {
await client.guilds.cache
.get(process.env.DEV_GUILD!)!
.commands.set(headers.map((header) => header.toJSON()));
} else {
await client.application!.commands.set(headers.map((header) => header.toJSON()));
}
console.log(
`Successfully loaded command definitions into Discord using ${
useDevGuild ? "development" : "production"
} mode.`
);
} catch (error) {
console.error(error);
}
});
// Listen for slash commands
client.on("interactionCreate", async (interaction) => {
if (interaction.isCommand()) {
if (handlers.has(interaction.commandName)) {
try {
await handlers.get(interaction.commandName)!(interaction);
} catch (error) {
console.error(error);
}
// Use these when implementing subcommands and subcommand groups
// interaction.options.getSubcommandGroup(false); // string if exists, null if not
// interaction.options.getSubcommand(false); // string if exists, null if not
} else {
interaction.reply({
content:
"**Error:** Invalid command name! This probably means that the command definitions forgot to be updated.",
ephemeral: true
});
}
}
});
});

View File

@ -0,0 +1,76 @@
import {Collection, CommandInteraction} from "discord.js";
import {SlashCommandBuilder} from "@discordjs/builders";
import glob from "glob";
import path from "path";
export async function loadCommands(
commandsDir: string
): Promise<
[Collection<string, SlashCommandBuilder>, Collection<string, (interaction: CommandInteraction) => Promise<any>>]
> {
// Add a trailing separator so that the reduced filename list will reliably cut off the starting part.
// "C:/some/path/to/commands" --> "C:/some/path/to/commands/" (and likewise for \)
commandsDir = path.normalize(commandsDir);
if (!commandsDir.endsWith(path.sep)) commandsDir += path.sep;
const headers = new Collection<string, SlashCommandBuilder>();
const handlers = new Collection<string, () => Promise<any>>();
const files = await globP(path.join(commandsDir, "**", "*.js")); // This stage filters out source maps (.js.map).
// Because glob will use / regardless of platform, the following regex pattern can rely on / being the case.
const filesClean = files.map((filename) => filename.substring(commandsDir.length));
// Extract the usable parts from commands directory if the path is 1 to 2 subdirectories (a or a/b, not a/b/c).
// No further checks will be made to exclude template command files or test command files, keeping it structure-agnostic.
const pattern = /^([^/]+(?:\/[^/]+)?)\.js$/;
for (let i = 0; i < files.length; i++) {
const match = pattern.exec(filesClean[i]);
if (!match) continue;
const commandID = match[1]; // e.g. "utilities/info"
const slashIndex = commandID.indexOf("/");
const isMiscCommand = slashIndex !== -1;
const commandName = isMiscCommand ? commandID.substring(slashIndex + 1) : commandID; // e.g. "info"
// This try-catch block MUST be here or Node.js' dynamic require() will silently fail.
try {
// If the dynamic import works, it must be an object at the very least. Then, just test to see if it's a proper instance.
const {header, handler} = (await import(files[i])) as {header: unknown; handler: unknown};
if (header instanceof SlashCommandBuilder && handler instanceof Function) {
if (headers.has(commandName) || handlers.has(commandName)) {
console.warn(
`Command "${commandID}" already exists! Make sure to make each command uniquely identifiable across categories!`
);
} else {
// Set the slash command name to the filename only if there isn't already a name set
if (header.name === undefined) {
header.setName(commandName);
}
headers.set(header.name, header);
handlers.set(header.name, handler as any); // Just got to hope that the user puts in good data
console.log(`Loaded Command: "${commandID}" as "${header.name}"`); // Use header.name to show what the slash command name is (should be the same)
}
} else {
console.warn(
`Command "${commandID}" doesn't export a "header" property (SlashCommandBuilder instance) or a "handler" property (function)!`
);
}
} catch (error) {
console.error(error);
}
}
return [headers, handlers];
}
function globP(path: string) {
return new Promise<string[]>((resolve, reject) => {
glob(path, (error, files) => {
if (error) {
reject(error);
} else {
resolve(files);
}
});
});
}