first commit
This commit is contained in:
commit
390fff9c42
58
.eslintrc.json
Normal file
58
.eslintrc.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"indent": [
|
||||
"error",
|
||||
2,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"warn",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"keyword-spacing": [
|
||||
"error", {
|
||||
"before": true,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"space-before-blocks": [
|
||||
"error", {
|
||||
"functions":"always",
|
||||
"keywords": "always",
|
||||
"classes": "always"
|
||||
}
|
||||
],
|
||||
"space-before-function-paren": [
|
||||
"error", {
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}
|
||||
],
|
||||
"prefer-const": [
|
||||
"error", {
|
||||
"destructuring": "any",
|
||||
"ignoreReadBeforeAssign": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
64
.gitignore
vendored
Normal file
64
.gitignore
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
config.json
|
||||
config.js
|
||||
data/
|
||||
test.js
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
/.vscode/
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 SNXRaven
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
106
commands/conf.js
Normal file
106
commands/conf.js
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
FOR GUILD SETTINGS SEE set.js !
|
||||
This command is used to modify the bot's default configuration values, which affects all guilds.
|
||||
If a default setting is not specifically overwritten by a guild, changing a default here will
|
||||
change it for that guild. The `add` action adds a key to the configuration of every guild in
|
||||
your bot. The `del` action removes the key also from every guild, and loses its value forever.
|
||||
*/
|
||||
|
||||
const { codeBlock } = require("@discordjs/builders");
|
||||
const config = require("../config.js");
|
||||
const { awaitReply } = require("../modules/functions.js");
|
||||
const { settings } = require("../modules/settings.js");
|
||||
|
||||
exports.run = async (client, message, [action, key, ...value], level) => { // eslint-disable-line no-unused-vars
|
||||
|
||||
// Retrieve Default Values from the default settings in the bot.
|
||||
const defaults = settings.get("default");
|
||||
const replying = settings.ensure(message.guild.id, config.defaultSettings).commandReply;
|
||||
|
||||
// Adding a new key adds it to every guild (it will be visible to all of them)
|
||||
if (action === "add") {
|
||||
if (!key) return message.reply({ content: "Please specify a key to add", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
if (defaults[key]) return message.reply({ content: "This key already exists in the default settings", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
if (value.length < 1) return message.reply({ content: "Please specify a value", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
|
||||
// `value` being an array, we need to join it first.
|
||||
defaults[key] = value.join(" ");
|
||||
|
||||
// One the settings is modified, we write it back to the collection
|
||||
settings.set("default", defaults);
|
||||
message.reply({ content: `${key} successfully added with the value of ${value.join(" ")}`, allowedMentions: { repliedUser: (replying === "true") }});
|
||||
} else
|
||||
|
||||
// Changing the default value of a key only modified it for guilds that did not change it to another value.
|
||||
if (action === "edit") {
|
||||
if (!key) return message.reply({ content: "Please specify a key to edit", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
if (!defaults[key]) return message.reply({ content: "This key does not exist in the settings", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
if (value.length < 1) return message.reply({ content: "Please specify a new value", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
|
||||
defaults[key] = value.join(" ");
|
||||
|
||||
settings.set("default", defaults);
|
||||
message.reply({ content: `${key} successfully edited to ${value.join(" ")}`, allowedMentions: { repliedUser: (replying === "true") }});
|
||||
} else
|
||||
|
||||
// WARNING: DELETING A KEY FROM THE DEFAULTS ALSO REMOVES IT FROM EVERY GUILD
|
||||
// MAKE SURE THAT KEY IS REALLY NO LONGER NEEDED!
|
||||
if (action === "del") {
|
||||
if (!key) return message.reply({ content: "Please specify a key to delete.", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
if (!defaults[key]) return message.reply({ content: "This key does not exist in the settings", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
|
||||
// Throw the 'are you sure?' text at them.
|
||||
const response = await awaitReply(message, `Are you sure you want to permanently delete ${key} from all guilds? This **CANNOT** be undone.`);
|
||||
|
||||
// If they respond with y or yes, continue.
|
||||
if (["y", "yes"].includes(response)) {
|
||||
|
||||
// We delete the default `key` here.
|
||||
delete defaults[key];
|
||||
settings.set("default", defaults);
|
||||
|
||||
// then we loop on all the guilds and remove this key if it exists.
|
||||
// "if it exists" is done with the filter (if the key is present and it's not the default config!)
|
||||
for (const [guildId, conf] of settings.filter((setting, id) => setting[key] && id !== "default")) {
|
||||
delete conf[key];
|
||||
settings.set(guildId, conf);
|
||||
}
|
||||
|
||||
message.reply({ content: `${key} was successfully deleted.`, allowedMentions: { repliedUser: (replying === "true") }});
|
||||
} else
|
||||
// If they respond with n or no, we inform them that the action has been cancelled.
|
||||
if (["n","no","cancel"].includes(response)) {
|
||||
message.reply({ content: "Action cancelled.", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
}
|
||||
} else
|
||||
|
||||
// Display a key's default value
|
||||
if (action === "get") {
|
||||
if (!key) return message.reply({ content: "Please specify a key to view", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
if (!defaults[key]) return message.reply({ content: "This key does not exist in the settings", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
message.reply({ content: `The value of ${key} is currently ${defaults[key]}`, allowedMentions: { repliedUser: (replying === "true") }});
|
||||
|
||||
// Display all default settings.
|
||||
} else {
|
||||
const array = [];
|
||||
Object.entries(settings.get("default")).forEach(([key, value]) => {
|
||||
array.push(`${key}${" ".repeat(20 - key.length)}:: ${value}`);
|
||||
});
|
||||
await message.channel.send(codeBlock("asciidoc", `= Bot Default Settings =
|
||||
${array.join("\n")}`));
|
||||
}
|
||||
};
|
||||
|
||||
exports.conf = {
|
||||
enabled: true,
|
||||
guildOnly: true,
|
||||
aliases: ["defaults"],
|
||||
permLevel: "Bot Admin"
|
||||
};
|
||||
|
||||
exports.help = {
|
||||
name: "conf",
|
||||
category: "System",
|
||||
description: "Modify the default configuration for all guilds.",
|
||||
usage: "conf <view/get/edit> <key> <value>"
|
||||
};
|
33
commands/deploy.js
Normal file
33
commands/deploy.js
Normal file
@ -0,0 +1,33 @@
|
||||
exports.run = async (client, message, args, level) => { // eslint-disable-line no-unused-vars
|
||||
|
||||
// We'll partition the slash commands based on the guildOnly boolean.
|
||||
// Separating them into the correct objects defined in the array below.
|
||||
const [globalCmds, guildCmds] = client.container.slashcmds.partition(c => !c.conf.guildOnly);
|
||||
|
||||
// Give the user a notification the commands are deploying.
|
||||
await message.channel.send("Deploying commands!");
|
||||
|
||||
// We'll use set but please keep in mind that `set` is overkill for a singular command.
|
||||
// Set the guild commands like
|
||||
await client.guilds.cache.get(message.guild.id)?.commands.set(guildCmds.map(c => c.commandData));
|
||||
|
||||
// Then set the global commands like
|
||||
await client.application?.commands.set(globalCmds.map(c => c.commandData)).catch(e => console.log(e));
|
||||
|
||||
// Reply to the user that the commands have been deployed.
|
||||
await message.channel.send("All commands deployed!");
|
||||
};
|
||||
|
||||
exports.conf = {
|
||||
enabled: true,
|
||||
guildOnly: true,
|
||||
aliases: [],
|
||||
permLevel: "Bot Owner"
|
||||
};
|
||||
|
||||
exports.help = {
|
||||
name: "deploy",
|
||||
category: "System",
|
||||
description: "This will deploy all slash commands.",
|
||||
usage: "deploy"
|
||||
};
|
52
commands/eval.js
Normal file
52
commands/eval.js
Normal file
@ -0,0 +1,52 @@
|
||||
// The EVAL command will execute **ANY** arbitrary javascript code given to it.
|
||||
// THIS IS PERMISSION LEVEL 10 FOR A REASON! It's perm level 10 because eval
|
||||
// can be used to do **anything** on your machine, from stealing information to
|
||||
// purging the hard drive. DO NOT LET ANYONE ELSE USE THIS
|
||||
|
||||
const { codeBlock } = require("@discordjs/builders");
|
||||
|
||||
/*
|
||||
MESSAGE CLEAN FUNCTION
|
||||
|
||||
"Clean" removes @everyone pings, as well as tokens, and makes code blocks
|
||||
escaped so they're shown more easily. As a bonus it resolves promises
|
||||
and stringifies objects!
|
||||
This is mostly only used by the Eval and Exec commands.
|
||||
*/
|
||||
async function clean(client, text) {
|
||||
if (text && text.constructor.name == "Promise")
|
||||
text = await text;
|
||||
if (typeof text !== "string")
|
||||
text = require("util").inspect(text, {depth: 1});
|
||||
|
||||
text = text
|
||||
.replace(/`/g, "`" + String.fromCharCode(8203))
|
||||
.replace(/@/g, "@" + String.fromCharCode(8203));
|
||||
|
||||
text = text.replaceAll(client.token, "[REDACTED]");
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
// However it's, like, super ultra useful for troubleshooting and doing stuff
|
||||
// you don't want to put in a command.
|
||||
exports.run = async (client, message, args, level) => { // eslint-disable-line no-unused-vars
|
||||
const code = args.join(" ");
|
||||
const evaled = eval(code);
|
||||
const cleaned = await clean(client, evaled);
|
||||
message.channel.send(codeBlock("js", cleaned));
|
||||
};
|
||||
|
||||
exports.conf = {
|
||||
enabled: true,
|
||||
guildOnly: false,
|
||||
aliases: [],
|
||||
permLevel: "Bot Owner"
|
||||
};
|
||||
|
||||
exports.help = {
|
||||
name: "eval",
|
||||
category: "System",
|
||||
description: "Evaluates arbitrary javascript.",
|
||||
usage: "eval [...code]"
|
||||
};
|
69
commands/help.js
Normal file
69
commands/help.js
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
The HELP command is used to display every command's name and description
|
||||
to the user, so that he may see what commands are available. The help
|
||||
command is also filtered by level, so if a user does not have access to
|
||||
a command, it is not shown to them. If a command name is given with the
|
||||
help command, its extended help is shown.
|
||||
*/
|
||||
const { codeBlock } = require("@discordjs/builders");
|
||||
const { toProperCase } = require("../modules/functions.js");
|
||||
|
||||
exports.run = (client, message, args, level) => {
|
||||
// Grab the container from the client to reduce line length.
|
||||
const { container } = client;
|
||||
// If no specific command is called, show all filtered commands.
|
||||
if (!args[0]) {
|
||||
// Load guild settings (for prefixes and eventually per-guild tweaks)
|
||||
const settings = message.settings;
|
||||
|
||||
// Filter all commands by which are available for the user's level, using the <Collection>.filter() method.
|
||||
const myCommands = message.guild ? container.commands.filter(cmd => container.levelCache[cmd.conf.permLevel] <= level) :
|
||||
container.commands.filter(cmd => container.levelCache[cmd.conf.permLevel] <= level && cmd.conf.guildOnly !== true);
|
||||
|
||||
// Then we will filter the myCommands collection again to get the enabled commands.
|
||||
const enabledCommands = myCommands.filter(cmd => cmd.conf.enabled);
|
||||
|
||||
// Here we have to get the command names only, and we use that array to get the longest name.
|
||||
const commandNames = [...enabledCommands.keys()];
|
||||
|
||||
// This make the help commands "aligned" in the output.
|
||||
const longest = commandNames.reduce((long, str) => Math.max(long, str.length), 0);
|
||||
|
||||
let currentCategory = "";
|
||||
let output = `= Command List =\n[Use ${settings.prefix}help <commandname> for details]\n`;
|
||||
const sorted = enabledCommands.sort((p, c) => p.help.category > c.help.category ? 1 :
|
||||
p.help.name > c.help.name && p.help.category === c.help.category ? 1 : -1 );
|
||||
|
||||
sorted.forEach( c => {
|
||||
const cat = toProperCase(c.help.category);
|
||||
if (currentCategory !== cat) {
|
||||
output += `\u200b\n== ${cat} ==\n`;
|
||||
currentCategory = cat;
|
||||
}
|
||||
output += `${settings.prefix}${c.help.name}${" ".repeat(longest - c.help.name.length)} :: ${c.help.description}\n`;
|
||||
});
|
||||
message.channel.send(codeBlock("asciidoc", output));
|
||||
|
||||
} else {
|
||||
// Show individual command's help.
|
||||
let command = args[0];
|
||||
if (container.commands.has(command) || container.commands.has(container.aliases.get(command))) {
|
||||
command = container.commands.get(command) ?? container.commands.get(container.aliases.get(command));
|
||||
if (level < container.levelCache[command.conf.permLevel]) return;
|
||||
message.channel.send(codeBlock("asciidoc", `= ${command.help.name} = \n${command.help.description}\nusage:: ${command.help.usage}\naliases:: ${command.conf.aliases.join(", ")}`));
|
||||
} else return message.channel.send("No command with that name, or alias exists.");
|
||||
}};
|
||||
|
||||
exports.conf = {
|
||||
enabled: true,
|
||||
guildOnly: false,
|
||||
aliases: ["h", "halp"],
|
||||
permLevel: "User"
|
||||
};
|
||||
|
||||
exports.help = {
|
||||
name: "help",
|
||||
category: "System",
|
||||
description: "Displays all the available commands for your permission level.",
|
||||
usage: "help [command]"
|
||||
};
|
21
commands/mylevel.js
Normal file
21
commands/mylevel.js
Normal file
@ -0,0 +1,21 @@
|
||||
const config = require("../config.js");
|
||||
const { settings } = require("../modules/settings.js");
|
||||
exports.run = async (client, message, args, level) => {
|
||||
const friendly = config.permLevels.find(l => l.level === level).name;
|
||||
const replying = settings.ensure(message.guild.id, config.defaultSettings).commandReply;
|
||||
message.reply({ content: `Your permission level is: ${level} - ${friendly}`, allowedMentions: { repliedUser: (replying === "true") }});
|
||||
};
|
||||
|
||||
exports.conf = {
|
||||
enabled: true,
|
||||
guildOnly: true,
|
||||
aliases: [],
|
||||
permLevel: "User"
|
||||
};
|
||||
|
||||
exports.help = {
|
||||
name: "mylevel",
|
||||
category: "Miscellaneous",
|
||||
description: "Tells you your permission level for the current message location.",
|
||||
usage: "mylevel"
|
||||
};
|
27
commands/reboot.js
Normal file
27
commands/reboot.js
Normal file
@ -0,0 +1,27 @@
|
||||
const config = require("../config.js");
|
||||
const { settings } = require("../modules/settings.js");
|
||||
exports.run = async (client, message, args, level) => { // eslint-disable-line no-unused-vars
|
||||
const replying = settings.ensure(message.guild.id, config.defaultSettings).commandReply;
|
||||
await message.reply({ content: "Bot is shutting down.", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
await Promise.all(client.container.commands.map(cmd => {
|
||||
// the path is relative to the *current folder*, so just ./filename.js
|
||||
delete require.cache[require.resolve(`./${cmd.help.name}.js`)];
|
||||
// We also need to delete and reload the command from the container.commands Enmap
|
||||
client.container.commands.delete(cmd.help.name);
|
||||
}));
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
exports.conf = {
|
||||
enabled: true,
|
||||
guildOnly: false,
|
||||
aliases: ["restart"],
|
||||
permLevel: "Bot Admin"
|
||||
};
|
||||
|
||||
exports.help = {
|
||||
name: "reboot",
|
||||
category: "System",
|
||||
description: "Shuts down the bot. If running under PM2, bot will restart automatically.",
|
||||
usage: "reboot"
|
||||
};
|
35
commands/reload.js
Normal file
35
commands/reload.js
Normal file
@ -0,0 +1,35 @@
|
||||
const config = require("../config.js");
|
||||
const { settings } = require("../modules/settings.js");
|
||||
exports.run = async (client, message, args, level) => { // eslint-disable-line no-unused-vars
|
||||
// Grab the container from the client to reduce line length.
|
||||
const { container } = client;
|
||||
const replying = settings.ensure(message.guild.id, config.defaultSettings).commandReply;
|
||||
if (!args || args.length < 1) return message.reply("Must provide a command name to reload.");
|
||||
const command = container.commands.get(args[0]) || container.commands.get(container.aliases.get(args[0]));
|
||||
// Check if the command exists and is valid
|
||||
if (!command) {
|
||||
return message.reply("That command does not exist");
|
||||
}
|
||||
// the path is relative to the *current folder*, so just ./filename.js
|
||||
delete require.cache[require.resolve(`./${command.help.name}.js`)];
|
||||
// We also need to delete and reload the command from the container.commands Enmap
|
||||
container.commands.delete(command.help.name);
|
||||
const props = require(`./${command.help.name}.js`);
|
||||
container.commands.set(command.help.name, props);
|
||||
|
||||
message.reply({ content: `The command \`${command.help.name}\` has been reloaded`, allowedMentions: { repliedUser: (replying === "true") }});
|
||||
};
|
||||
|
||||
exports.conf = {
|
||||
enabled: true,
|
||||
guildOnly: false,
|
||||
aliases: [],
|
||||
permLevel: "Bot Admin"
|
||||
};
|
||||
|
||||
exports.help = {
|
||||
name: "reload",
|
||||
category: "System",
|
||||
description: "Reloads a command that\"s been modified.",
|
||||
usage: "reload [command]"
|
||||
};
|
97
commands/set.js
Normal file
97
commands/set.js
Normal file
@ -0,0 +1,97 @@
|
||||
// This command is to modify/edit guild configuration. Perm Level 3 for admins
|
||||
// and owners only. Used for changing prefixes and role names and such.
|
||||
|
||||
// Note that there's no "checks" in this basic version - no config "types" like
|
||||
// Role, String, Int, etc... It's basic, to be extended with your deft hands!
|
||||
|
||||
// Note the **destructuring** here. instead of `args` we have :
|
||||
// [action, key, ...value]
|
||||
// This gives us the equivalent of either:
|
||||
// const action = args[0]; const key = args[1]; const value = args.slice(2);
|
||||
// OR the same as:
|
||||
// const [action, key, ...value] = args;
|
||||
const { codeBlock } = require("@discordjs/builders");
|
||||
const { settings } = require("../modules/settings.js");
|
||||
const { awaitReply } = require("../modules/functions.js");
|
||||
|
||||
exports.run = async (client, message, [action, key, ...value], level) => { // eslint-disable-line no-unused-vars
|
||||
|
||||
// Retrieve current guild settings (merged) and overrides only.
|
||||
const serverSettings = message.settings;
|
||||
const defaults = settings.get("default");
|
||||
const overrides = settings.get(message.guild.id);
|
||||
const replying = serverSettings.commandReply;
|
||||
if (!settings.has(message.guild.id)) settings.set(message.guild.id, {});
|
||||
|
||||
// Edit an existing key value
|
||||
if (action === "edit") {
|
||||
// User must specify a key.
|
||||
if (!key) return message.reply({ content: "Please specify a key to edit", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
// User must specify a key that actually exists!
|
||||
if (!defaults[key]) return message.reply({ content: "This key does not exist in the settings", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
const joinedValue = value.join(" ");
|
||||
// User must specify a value to change.
|
||||
if (joinedValue.length < 1) return message.reply({ content: "Please specify a new value", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
// User must specify a different value than the current one.
|
||||
if (joinedValue === serverSettings[key]) return message.reply({ content: "This setting already has that value!", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
|
||||
// If the guild does not have any overrides, initialize it.
|
||||
if (!settings.has(message.guild.id)) settings.set(message.guild.id, {});
|
||||
|
||||
// Modify the guild overrides directly.
|
||||
settings.set(message.guild.id, joinedValue, key);
|
||||
|
||||
// Confirm everything is fine!
|
||||
message.reply({ content: `${key} successfully edited to ${joinedValue}`, allowedMentions: { repliedUser: (replying === "true") }});
|
||||
} else
|
||||
|
||||
// Resets a key to the default value
|
||||
if (action === "del" || action === "reset") {
|
||||
if (!key) return message.reply({ content: "Please specify a key to reset.", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
if (!defaults[key]) return message.reply({ content: "This key does not exist in the settings", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
if (!overrides[key]) return message.reply({ content: "This key does not have an override and is already using defaults.", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
|
||||
// Good demonstration of the custom awaitReply method in `./modules/functions.js` !
|
||||
const response = await awaitReply(message, `Are you sure you want to reset ${key} to the default value?`);
|
||||
|
||||
// If they respond with y or yes, continue.
|
||||
if (["y", "yes"].includes(response.toLowerCase())) {
|
||||
// We delete the `key` here.
|
||||
settings.delete(message.guild.id, key);
|
||||
message.reply({ content: `${key} was successfully reset to default.`, allowedMentions: { repliedUser: (replying === "true") }});
|
||||
} else
|
||||
// If they respond with n or no, we inform them that the action has been cancelled.
|
||||
if (["n","no","cancel"].includes(response)) {
|
||||
message.reply({ content: `Your setting for \`${key}\` remains at \`${serverSettings[key]}\``, allowedMentions: { repliedUser: (replying === "true") }});
|
||||
}
|
||||
} else
|
||||
|
||||
if (action === "get") {
|
||||
if (!key) return message.reply({ content: "Please specify a key to view", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
if (!defaults[key]) return message.reply({ content: "This key does not exist in the settings", allowedMentions: { repliedUser: (replying === "true") }});
|
||||
const isDefault = !overrides[key] ? "\nThis is the default global default value." : "";
|
||||
message.reply({ content: `The value of ${key} is currently ${serverSettings[key]}${isDefault}`, allowedMentions: { repliedUser: (replying === "true") }});
|
||||
} else {
|
||||
// Otherwise, the default action is to return the whole configuration;
|
||||
const array = [];
|
||||
Object.entries(serverSettings).forEach(([key, value]) => {
|
||||
array.push(`${key}${" ".repeat(20 - key.length)}:: ${value}`);
|
||||
});
|
||||
await message.channel.send(codeBlock("asciidoc", `= Current Guild Settings =
|
||||
${array.join("\n")}`));
|
||||
}
|
||||
};
|
||||
|
||||
exports.conf = {
|
||||
enabled: true,
|
||||
guildOnly: true,
|
||||
aliases: ["setting", "settings", "conf"],
|
||||
permLevel: "Administrator"
|
||||
};
|
||||
|
||||
exports.help = {
|
||||
name: "set",
|
||||
category: "System",
|
||||
description: "View or change settings for your server.",
|
||||
usage: "set <view/get/edit> <key> <value>"
|
||||
};
|
31
commands/stats.js
Normal file
31
commands/stats.js
Normal file
@ -0,0 +1,31 @@
|
||||
const { version } = require("discord.js");
|
||||
const { codeBlock } = require("@discordjs/builders");
|
||||
const { DurationFormatter } = require("@sapphire/time-utilities");
|
||||
const durationFormatter = new DurationFormatter();
|
||||
|
||||
exports.run = (client, message, args, level) => { // eslint-disable-line no-unused-vars
|
||||
const duration = durationFormatter.format(client.uptime);
|
||||
const stats = codeBlock("asciidoc", `= STATISTICS =
|
||||
• Mem Usage :: ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB
|
||||
• Uptime :: ${duration}
|
||||
• Users :: ${client.guilds.cache.map(g => g.memberCount).reduce((a, b) => a + b).toLocaleString()}
|
||||
• Servers :: ${client.guilds.cache.size.toLocaleString()}
|
||||
• Channels :: ${client.channels.cache.size.toLocaleString()}
|
||||
• Discord.js :: v${version}
|
||||
• Node :: ${process.version}`);
|
||||
message.channel.send(stats);
|
||||
};
|
||||
|
||||
exports.conf = {
|
||||
enabled: true,
|
||||
guildOnly: false,
|
||||
aliases: [],
|
||||
permLevel: "User"
|
||||
};
|
||||
|
||||
exports.help = {
|
||||
name: "stats",
|
||||
category: "Miscellaneous",
|
||||
description: "Gives some useful bot statistics",
|
||||
usage: "stats"
|
||||
};
|
4
events/error.js
Normal file
4
events/error.js
Normal file
@ -0,0 +1,4 @@
|
||||
const logger = require("../modules/Logger.js");
|
||||
module.exports = async (client, error) => {
|
||||
logger.log(`An error event was sent by Discord.js: \n${JSON.stringify(error)}`, "error");
|
||||
};
|
6
events/guildCreate.js
Normal file
6
events/guildCreate.js
Normal file
@ -0,0 +1,6 @@
|
||||
const logger = require("../modules/Logger.js");
|
||||
// This event executes when a new guild (server) is joined.
|
||||
|
||||
module.exports = (client, guild) => {
|
||||
logger.log(`[GUILD JOIN] ${guild.id} added the bot. Owner: ${guild.ownerId}`);
|
||||
};
|
16
events/guildDelete.js
Normal file
16
events/guildDelete.js
Normal file
@ -0,0 +1,16 @@
|
||||
const logger = require("../modules/Logger.js");
|
||||
const { settings } = require("../modules/settings.js");
|
||||
|
||||
// This event executes when a new guild (server) is left.
|
||||
|
||||
module.exports = (client, guild) => {
|
||||
if (!guild.available) return; // If there is an outage, return.
|
||||
|
||||
logger.log(`[GUILD LEAVE] ${guild.id} removed the bot.`);
|
||||
|
||||
// If the settings Enmap contains any guild overrides, remove them.
|
||||
// No use keeping stale data!
|
||||
if (settings.has(guild.id)) {
|
||||
settings.delete(guild.id);
|
||||
}
|
||||
};
|
17
events/guildMemberAdd.js
Normal file
17
events/guildMemberAdd.js
Normal file
@ -0,0 +1,17 @@
|
||||
const { getSettings } = require("../modules/functions.js");
|
||||
// This event executes when a new member joins a server. Let's welcome them!
|
||||
|
||||
module.exports = (client, member) => {
|
||||
// Load the guild's settings
|
||||
const settings = getSettings(member.guild);
|
||||
|
||||
// If welcome is off, don't proceed (don't welcome the user)
|
||||
if (settings.welcomeEnabled !== "true") return;
|
||||
|
||||
// Replace the placeholders in the welcome message with actual data
|
||||
const welcomeMessage = settings.welcomeMessage.replace("{{user}}", member.user.tag);
|
||||
|
||||
// Send the welcome message to the welcome channel
|
||||
// There's a place for more configs here.
|
||||
member.guild.channels.cache.find(c => c.name === settings.welcomeChannel).send(welcomeMessage).catch(console.error);
|
||||
};
|
54
events/interactionCreate.js
Normal file
54
events/interactionCreate.js
Normal file
@ -0,0 +1,54 @@
|
||||
const logger = require("../modules/Logger.js");
|
||||
const { getSettings, permlevel } = require("../modules/functions.js");
|
||||
const config = require("../config.js");
|
||||
|
||||
module.exports = async (client, interaction) => {
|
||||
// If it's not a command, stop.
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
// Grab the settings for this server from Enmap.
|
||||
// If there is no guild, get default conf (DMs)
|
||||
const settings = interaction.settings = getSettings(interaction.guild);
|
||||
|
||||
// Get the user or member's permission level from the elevation
|
||||
const level = permlevel(interaction);
|
||||
|
||||
// Grab the command data from the client.container.slashcmds Collection
|
||||
const cmd = client.container.slashcmds.get(interaction.commandName);
|
||||
|
||||
// If that command doesn't exist, silently exit and do nothing
|
||||
if (!cmd) return;
|
||||
|
||||
// Since the permission system from Discord is rather limited in regarding to
|
||||
// Slash Commands, we'll just utilise our permission checker.
|
||||
if (level < client.container.levelCache[cmd.conf.permLevel]) {
|
||||
// Due to the nature of interactions we **must** respond to them otherwise
|
||||
// they will error out because we didn't respond to them.
|
||||
return await interaction.reply({
|
||||
content: `This command can only be used by ${cmd.conf.permLevel}'s only`,
|
||||
// This will basically set the ephemeral response to either announce
|
||||
// to everyone, or just the command executioner. But we **HAVE** to
|
||||
// respond.
|
||||
ephemeral: settings.systemNotice !== "true"
|
||||
});
|
||||
}
|
||||
|
||||
// If everything checks out, run the command
|
||||
try {
|
||||
await cmd.run(client, interaction);
|
||||
logger.log(`${config.permLevels.find(l => l.level === level).name} ${interaction.user.id} ran slash command ${interaction.commandName}`, "cmd");
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (interaction.replied)
|
||||
interaction.followUp({ content: `There was a problem with your request.\n\`\`\`${e.message}\`\`\``, ephemeral: true })
|
||||
.catch(e => console.error("An error occurred following up on an error", e));
|
||||
else
|
||||
if (interaction.deferred)
|
||||
interaction.editReply({ content: `There was a problem with your request.\n\`\`\`${e.message}\`\`\``, ephemeral: true })
|
||||
.catch(e => console.error("An error occurred following up on an error", e));
|
||||
else
|
||||
interaction.reply({ content: `There was a problem with your request.\n\`\`\`${e.message}\`\`\``, ephemeral: true })
|
||||
.catch(e => console.error("An error occurred replying on an error", e));
|
||||
}
|
||||
};
|
90
events/messageCreate.js
Normal file
90
events/messageCreate.js
Normal file
@ -0,0 +1,90 @@
|
||||
const logger = require("../modules/Logger.js");
|
||||
const { getSettings, permlevel } = require("../modules/functions.js");
|
||||
const config = require("../config.js");
|
||||
|
||||
// The MESSAGE event runs anytime a message is received
|
||||
// Note that due to the binding of client to every event, every event
|
||||
// goes `client, other, args` when this function is run.
|
||||
|
||||
module.exports = async (client, message) => {
|
||||
// Grab the container from the client to reduce line length.
|
||||
const { container } = client;
|
||||
// It's good practice to ignore other bots. This also makes your bot ignore itself
|
||||
// and not get into a spam loop (we call that "botception").
|
||||
if (message.author.bot) return;
|
||||
|
||||
// Grab the settings for this server from Enmap.
|
||||
// If there is no guild, get default conf (DMs)
|
||||
const settings = message.settings = getSettings(message.guild);
|
||||
|
||||
// Checks if the bot was mentioned via regex, with no message after it,
|
||||
// returns the prefix. The reason why we used regex here instead of
|
||||
// message.mentions is because of the mention prefix later on in the
|
||||
// code, would render it useless.
|
||||
const prefixMention = new RegExp(`^<@!?${client.user.id}> ?$`);
|
||||
if (message.content.match(prefixMention)) {
|
||||
return message.reply(`My prefix on this guild is \`${settings.prefix}\``);
|
||||
}
|
||||
|
||||
// It's also good practice to ignore any and all messages that do not start
|
||||
// with our prefix, or a bot mention.
|
||||
const prefix = new RegExp(`^<@!?${client.user.id}> |^\\${settings.prefix}`).exec(message.content);
|
||||
// This will return and stop the code from continuing if it's missing
|
||||
// our prefix (be it mention or from the settings).
|
||||
if (!prefix) return;
|
||||
|
||||
// Here we separate our "command" name, and our "arguments" for the command.
|
||||
// e.g. if we have the message "+say Is this the real life?" , we'll get the following:
|
||||
// command = say
|
||||
// args = ["Is", "this", "the", "real", "life?"]
|
||||
const args = message.content.slice(prefix[0].length).trim().split(/ +/g);
|
||||
const command = args.shift().toLowerCase();
|
||||
|
||||
// If the member on a guild is invisible or not cached, fetch them.
|
||||
if (message.guild && !message.member) await message.guild.members.fetch(message.author);
|
||||
|
||||
// Get the user or member's permission level from the elevation
|
||||
const level = permlevel(message);
|
||||
|
||||
// Check whether the command, or alias, exist in the collections defined
|
||||
// in app.js.
|
||||
const cmd = container.commands.get(command) || container.commands.get(container.aliases.get(command));
|
||||
// using this const varName = thing OR otherThing; is a pretty efficient
|
||||
// and clean way to grab one of 2 values!
|
||||
if (!cmd) return;
|
||||
|
||||
// Some commands may not be useable in DMs. This check prevents those commands from running
|
||||
// and return a friendly error message.
|
||||
if (cmd && !message.guild && cmd.conf.guildOnly)
|
||||
return message.channel.send("This command is unavailable via private message. Please run this command in a guild.");
|
||||
|
||||
if (!cmd.conf.enabled) return;
|
||||
|
||||
if (level < container.levelCache[cmd.conf.permLevel]) {
|
||||
if (settings.systemNotice === "true") {
|
||||
return message.channel.send(`You do not have permission to use this command.
|
||||
Your permission level is ${level} (${config.permLevels.find(l => l.level === level).name})
|
||||
This command requires level ${container.levelCache[cmd.conf.permLevel]} (${cmd.conf.permLevel})`);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// To simplify message arguments, the author's level is now put on level (not member so it is supported in DMs)
|
||||
// The "level" command module argument will be deprecated in the future.
|
||||
message.author.permLevel = level;
|
||||
|
||||
message.flags = [];
|
||||
while (args[0] && args[0][0] === "-") {
|
||||
message.flags.push(args.shift().slice(1));
|
||||
}
|
||||
// If the command exists, **AND** the user has permission, run it.
|
||||
try {
|
||||
await cmd.run(client, message, args, level);
|
||||
logger.log(`${config.permLevels.find(l => l.level === level).name} ${message.author.id} ran command ${cmd.help.name}`, "cmd");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
message.channel.send({ content: `There was a problem with your request.\n\`\`\`${e.message}\`\`\`` })
|
||||
.catch(e => console.error("An error occurred replying on an error", e));
|
||||
}
|
||||
};
|
9
events/ready.js
Normal file
9
events/ready.js
Normal file
@ -0,0 +1,9 @@
|
||||
const logger = require("../modules/Logger.js");
|
||||
const { getSettings } = require("../modules/functions.js");
|
||||
module.exports = async client => {
|
||||
// Log that the bot is online.
|
||||
logger.log(`${client.user.tag}, ready to serve ${client.guilds.cache.map(g => g.memberCount).reduce((a, b) => a + b)} users in ${client.guilds.cache.size} servers.`, "ready");
|
||||
|
||||
// Make the bot "play the game" which is the help command with default prefix.
|
||||
client.user.setActivity(`${getSettings("default").prefix}help`, { type: "PLAYING" });
|
||||
};
|
91
index.js
Normal file
91
index.js
Normal file
@ -0,0 +1,91 @@
|
||||
// This will check if the node version you are running is the required
|
||||
// Node version, if it isn't it will throw the following error to inform
|
||||
// you.
|
||||
if (Number(process.version.slice(1).split(".")[0]) < 16) throw new Error("Node 16.x or higher is required. Update Node on your system.");
|
||||
require("dotenv").config();
|
||||
|
||||
// Load up the discord.js library
|
||||
const { Client, Collection } = require("discord.js");
|
||||
// We also load the rest of the things we need in this file:
|
||||
const { readdirSync } = require("fs");
|
||||
const { intents, partials, permLevels } = require("./config.js");
|
||||
const logger = require("./modules/Logger.js");
|
||||
// This is your client. Some people call it `bot`, some people call it `self`,
|
||||
// some might call it `cootchie`. Either way, when you see `client.something`,
|
||||
// or `bot.something`, this is what we're referring to. Your client.
|
||||
const client = new Client({ intents, partials });
|
||||
|
||||
// Aliases, commands and slash commands are put in collections where they can be
|
||||
// read from, catalogued, listed, etc.
|
||||
const commands = new Collection();
|
||||
const aliases = new Collection();
|
||||
const slashcmds = new Collection();
|
||||
|
||||
// Generate a cache of client permissions for pretty perm names in commands.
|
||||
const levelCache = {};
|
||||
for (let i = 0; i < permLevels.length; i++) {
|
||||
const thisLevel = permLevels[i];
|
||||
levelCache[thisLevel.name] = thisLevel.level;
|
||||
}
|
||||
|
||||
// To reduce client pollution we'll create a single container property
|
||||
// that we can attach everything we need to.
|
||||
client.container = {
|
||||
commands,
|
||||
aliases,
|
||||
slashcmds,
|
||||
levelCache
|
||||
};
|
||||
|
||||
// We're doing real fancy node 8 async/await stuff here, and to do that
|
||||
// we need to wrap stuff in an anonymous function. It's annoying but it works.
|
||||
|
||||
const init = async () => {
|
||||
|
||||
// Here we load **commands** into memory, as a collection, so they're accessible
|
||||
// here and everywhere else.
|
||||
const commands = readdirSync("./commands/").filter(file => file.endsWith(".js"));
|
||||
for (const file of commands) {
|
||||
const props = require(`./commands/${file}`);
|
||||
logger.log(`Loading Command: ${props.help.name}. 👌`, "log");
|
||||
client.container.commands.set(props.help.name, props);
|
||||
props.conf.aliases.forEach(alias => {
|
||||
client.container.aliases.set(alias, props.help.name);
|
||||
});
|
||||
}
|
||||
|
||||
// Now we load any **slash** commands you may have in the ./slash directory.
|
||||
const slashFiles = readdirSync("./slash").filter(file => file.endsWith(".js"));
|
||||
for (const file of slashFiles) {
|
||||
const command = require(`./slash/${file}`);
|
||||
const commandName = file.split(".")[0];
|
||||
logger.log(`Loading Slash command: ${commandName}. 👌`, "log");
|
||||
|
||||
// Now set the name of the command with it's properties.
|
||||
client.container.slashcmds.set(command.commandData.name, command);
|
||||
}
|
||||
|
||||
// Then we load events, which will include our message and ready event.
|
||||
const eventFiles = readdirSync("./events/").filter(file => file.endsWith(".js"));
|
||||
for (const file of eventFiles) {
|
||||
const eventName = file.split(".")[0];
|
||||
logger.log(`Loading Event: ${eventName}. 👌`, "log");
|
||||
const event = require(`./events/${file}`);
|
||||
// Bind the client to any event, before the existing arguments
|
||||
// provided by the discord.js event.
|
||||
// This line is awesome by the way. Just sayin'.
|
||||
client.on(eventName, event.bind(null, client));
|
||||
}
|
||||
|
||||
// Threads are currently in BETA.
|
||||
// This event will fire when a thread is created, if you want to expand
|
||||
// the logic, throw this in it's own event file like the rest.
|
||||
client.on("threadCreate", (thread) => thread.join());
|
||||
|
||||
// Here we login the client.
|
||||
client.login();
|
||||
|
||||
// End top-level async/await function.
|
||||
};
|
||||
|
||||
init();
|
27
modules/Logger.js
Normal file
27
modules/Logger.js
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
Logger class for easy and aesthetically pleasing console logging
|
||||
*/
|
||||
const { cyan, red, magenta, gray, yellow, white, green } = require("colorette");
|
||||
const { Timestamp } = require("@sapphire/time-utilities");
|
||||
|
||||
exports.log = (content, type = "log") => {
|
||||
const timestamp = `[${cyan(new Timestamp("YYYY-MM-DD HH:mm:ss"))}]:`;
|
||||
|
||||
switch (type) {
|
||||
case "log": return console.log(`${timestamp} ${gray(type.toUpperCase())} ${content} `);
|
||||
case "warn": return console.log(`${timestamp} ${yellow(type.toUpperCase())} ${content} `);
|
||||
case "error": return console.log(`${timestamp} ${red(type.toUpperCase())} ${content} `);
|
||||
case "debug": return console.log(`${timestamp} ${magenta(type.toUpperCase())} ${content} `);
|
||||
case "cmd": return console.log(`${timestamp} ${white(type.toUpperCase())} ${content}`);
|
||||
case "ready": return console.log(`${timestamp} ${green(type.toUpperCase())} ${content}`);
|
||||
default: throw new TypeError("Logger type must be either warn, debug, log, ready, cmd or error.");
|
||||
}
|
||||
};
|
||||
|
||||
exports.error = (...args) => this.log(...args, "error");
|
||||
|
||||
exports.warn = (...args) => this.log(...args, "warn");
|
||||
|
||||
exports.debug = (...args) => this.log(...args, "debug");
|
||||
|
||||
exports.cmd = (...args) => this.log(...args, "cmd");
|
99
modules/functions.js
Normal file
99
modules/functions.js
Normal file
@ -0,0 +1,99 @@
|
||||
const logger = require("./Logger.js");
|
||||
const config = require("../config.js");
|
||||
const { settings } = require("./settings.js");
|
||||
// Let's start by getting some useful functions that we'll use throughout
|
||||
// the bot, like logs and elevation features.
|
||||
|
||||
/*
|
||||
PERMISSION LEVEL FUNCTION
|
||||
|
||||
This is a very basic permission system for commands which uses "levels"
|
||||
"spaces" are intentionally left black so you can add them if you want.
|
||||
NEVER GIVE ANYONE BUT OWNER THE LEVEL 10! By default this can run any
|
||||
command including the VERY DANGEROUS `eval` and `exec` commands!
|
||||
|
||||
*/
|
||||
function permlevel(message) {
|
||||
let permlvl = 0;
|
||||
|
||||
const permOrder = config.permLevels.slice(0).sort((p, c) => p.level < c.level ? 1 : -1);
|
||||
|
||||
while (permOrder.length) {
|
||||
const currentLevel = permOrder.shift();
|
||||
if (message.guild && currentLevel.guildOnly) continue;
|
||||
if (currentLevel.check(message)) {
|
||||
permlvl = currentLevel.level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return permlvl;
|
||||
}
|
||||
|
||||
/*
|
||||
GUILD SETTINGS FUNCTION
|
||||
|
||||
This function merges the default settings (from config.defaultSettings) with any
|
||||
guild override you might have for particular guild. If no overrides are present,
|
||||
the default settings are used.
|
||||
|
||||
*/
|
||||
|
||||
// getSettings merges the client defaults with the guild settings. guild settings in
|
||||
// enmap should only have *unique* overrides that are different from defaults.
|
||||
function getSettings(guild) {
|
||||
settings.ensure("default", config.defaultSettings);
|
||||
if (!guild) return settings.get("default");
|
||||
const guildConf = settings.get(guild.id) || {};
|
||||
// This "..." thing is the "Spread Operator". It's awesome!
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
|
||||
return ({...settings.get("default"), ...guildConf});
|
||||
}
|
||||
|
||||
/*
|
||||
SINGLE-LINE AWAIT MESSAGE
|
||||
|
||||
A simple way to grab a single reply, from the user that initiated
|
||||
the command. Useful to get "precisions" on certain things...
|
||||
|
||||
USAGE
|
||||
|
||||
const response = await awaitReply(msg, "Favourite Color?");
|
||||
msg.reply(`Oh, I really love ${response} too!`);
|
||||
|
||||
*/
|
||||
async function awaitReply(msg, question, limit = 60000) {
|
||||
const filter = m => m.author.id === msg.author.id;
|
||||
await msg.channel.send(question);
|
||||
try {
|
||||
const collected = await msg.channel.awaitMessages({ filter, max: 1, time: limit, errors: ["time"] });
|
||||
return collected.first().content;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* MISCELLANEOUS NON-CRITICAL FUNCTIONS */
|
||||
|
||||
// toProperCase(String) returns a proper-cased string such as:
|
||||
// toProperCase("Mary had a little lamb") returns "Mary Had A Little Lamb"
|
||||
function toProperCase(string) {
|
||||
return string.replace(/([^\W_]+[^\s-]*) */g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
|
||||
}
|
||||
|
||||
// These 2 process methods will catch exceptions and give *more details* about the error and stack trace.
|
||||
process.on("uncaughtException", (err) => {
|
||||
const errorMsg = err.stack.replace(new RegExp(`${__dirname}/`, "g"), "./");
|
||||
logger.error(`Uncaught Exception: ${errorMsg}`);
|
||||
console.error(err);
|
||||
// Always best practice to let the code crash on uncaught exceptions.
|
||||
// Because you should be catching them anyway.
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", err => {
|
||||
logger.error(`Unhandled rejection: ${err}`);
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
module.exports = { getSettings, permlevel, awaitReply, toProperCase };
|
9
modules/settings.js
Normal file
9
modules/settings.js
Normal file
@ -0,0 +1,9 @@
|
||||
const Enmap = require("enmap");
|
||||
// Now we integrate the use of Evie's awesome Enmap module, which
|
||||
// essentially saves a collection to disk. This is great for per-server configs,
|
||||
// and makes things extremely easy for this purpose.
|
||||
module.exports = {
|
||||
settings: new Enmap({
|
||||
name: "settings",
|
||||
}),
|
||||
};
|
6528
package-lock.json
generated
Normal file
6528
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
package.json
Normal file
45
package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "guidebot",
|
||||
"version": "2.1.0",
|
||||
"description": "A boilerplate example bot with command handler and reloadable commands. Updated and Maintained by the Idiot's Guide Community",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/AnIdiotsGuide/guidebot.git"
|
||||
},
|
||||
"author": "Evelyne Lachance <eslachance@gmail.com> (http://evie.codes)",
|
||||
"contributors": [
|
||||
"York (http://anidiots.guide)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/AnIdiotsGuide/guidebot/issues"
|
||||
},
|
||||
"homepage": "https://github.com/AnIdiotsGuide/guidebot#readme",
|
||||
"dependencies": {
|
||||
"@aahlw/peji": "^1.0.0",
|
||||
"@discordjs/builders": "^0.2.0",
|
||||
"@sapphire/time-utilities": "^1.3.8",
|
||||
"axios": "^0.25.0",
|
||||
"colorette": "^1.3.0",
|
||||
"discord.js": "^13.0.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"enmap": "^5.8.5",
|
||||
"pagination.djs": "^3.1.1",
|
||||
"unirest": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0"
|
||||
}
|
||||
}
|
41
slash/create_task.js
Normal file
41
slash/create_task.js
Normal file
@ -0,0 +1,41 @@
|
||||
// For HTTP Requests
|
||||
var http = require('unirest');
|
||||
let boardList=[]
|
||||
let embed = []
|
||||
let pages =[]
|
||||
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
exports.run = async (client, interaction) => {
|
||||
|
||||
// eslint-disable-line no-unused-vars
|
||||
await interaction.deferReply();
|
||||
// const reply = await interaction.editReply("Ping?");
|
||||
// await interaction.editReply(`Pong! Latency is ${reply.createdTimestamp - interaction.createdTimestamp}ms. API Latency is ${Math.round(client.ws.ping)}ms.`);
|
||||
var Request = http.post('https://board.grwh.work/jsonrpc.php').headers({ Accept: 'application/json', 'Content-Type': 'application/json' }).send({ "jsonrpc": "2.0", "method": "getAllProjects", "id": 0 });
|
||||
|
||||
Request.auth({
|
||||
user: 'jsonrpc',
|
||||
pass: process.env.KANBOARD_API_KEY,
|
||||
sendImmediately: false
|
||||
}).then(function (response) {
|
||||
let data = response.body.result
|
||||
console.log(data)
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
exports.commandData = {
|
||||
name: "test",
|
||||
description: "create a task",
|
||||
options: [],
|
||||
defaultPermission: true,
|
||||
};
|
||||
|
||||
// Set guildOnly to true if you want it to be available on guilds only.
|
||||
// Otherwise false is global.
|
||||
exports.conf = {
|
||||
permLevel: "User",
|
||||
guildOnly: false
|
||||
};
|
24
slash/leave.js
Normal file
24
slash/leave.js
Normal file
@ -0,0 +1,24 @@
|
||||
const { Permissions } = require("discord.js");
|
||||
|
||||
exports.run = async (client, interaction) => { // eslint-disable-line no-unused-vars
|
||||
await interaction.deferReply();
|
||||
if (!interaction.guild.me.permissions.has(Permissions.FLAGS.KICK_MEMBERS))
|
||||
return await interaction.editReply("I do not have permission to kick members in this server.");
|
||||
await interaction.member.send("You requested to leave the server, if you change your mind you can rejoin at a later date.");
|
||||
await interaction.member.kick(`${interaction.member.displayName} wanted to leave.`);
|
||||
await interaction.editReply(`${interaction.member.displayName} left in a hurry!`);
|
||||
};
|
||||
|
||||
exports.commandData = {
|
||||
name: "leave",
|
||||
description: "Make's the user leave the guild.",
|
||||
options: [],
|
||||
defaultPermission: true,
|
||||
};
|
||||
|
||||
// Set guildOnly to true if you want it to be available on guilds only.
|
||||
// Otherwise false is global.
|
||||
exports.conf = {
|
||||
permLevel: "User",
|
||||
guildOnly: true
|
||||
};
|
70
slash/list_projects.js
Normal file
70
slash/list_projects.js
Normal file
@ -0,0 +1,70 @@
|
||||
// For HTTP Requests
|
||||
var http = require('unirest');
|
||||
let boardList=[]
|
||||
let embed = []
|
||||
let pages =[]
|
||||
const { MessageEmbed, MessageButton } = require("discord.js");
|
||||
const { Pagination } = require("pagination.djs");
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
exports.run = async (client, interaction) => {
|
||||
|
||||
const pagination = new Pagination(interaction, {
|
||||
firstEmoji: "⏮", // First button emoji
|
||||
prevEmoji: "◀️", // Previous button emoji
|
||||
nextEmoji: "▶️", // Next button emoji
|
||||
lastEmoji: "⏭", // Last button emoji
|
||||
limit: 2, // number of entries per page
|
||||
idle: 30000, // idle time in ms before the pagination closes
|
||||
ephemeral: false, // ephemeral reply
|
||||
prevDescription: "",
|
||||
postDescription: "",
|
||||
buttonStyle: "SECONDARY",
|
||||
loop: false, // loop through the pages
|
||||
});
|
||||
|
||||
// eslint-disable-line no-unused-vars
|
||||
await interaction.deferReply();
|
||||
// const reply = await interaction.editReply("Ping?");
|
||||
// await interaction.editReply(`Pong! Latency is ${reply.createdTimestamp - interaction.createdTimestamp}ms. API Latency is ${Math.round(client.ws.ping)}ms.`);
|
||||
var Request = http.get('https://board.grwh.work/jsonrpc.php').headers({ Accept: 'application/json', 'Content-Type': 'application/json' }).send({ "jsonrpc": "2.0", "method": "getAllProjects", "id": 0 });
|
||||
|
||||
Request.auth({
|
||||
user: 'jsonrpc',
|
||||
pass: process.env.KANBOARD_API_KEY,
|
||||
sendImmediately: false
|
||||
}).then(function (response) {
|
||||
let data = response.body.result
|
||||
const pusherFunc = board=>boardList.push({name:"Project Name", value:board.name + "\nID: " + board.id + "\n" + "https://board.grwh.work/board/" + board.id});
|
||||
data.forEach(pusherFunc);
|
||||
|
||||
console.log(boardList)
|
||||
pagination.setTitle("Project List");
|
||||
pagination.setDescription("This is a list of all currently active projects.");
|
||||
|
||||
pagination.setColor("#00ff00");
|
||||
pagination.setFooter("Boards:");
|
||||
pagination.setTimestamp();
|
||||
|
||||
pagination.addFields(boardList);
|
||||
pagination.paginateFields(true);
|
||||
pagination.render();
|
||||
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
exports.commandData = {
|
||||
name: "list",
|
||||
description: "Lists all of the current projects",
|
||||
options: [],
|
||||
defaultPermission: true,
|
||||
};
|
||||
|
||||
// Set guildOnly to true if you want it to be available on guilds only.
|
||||
// Otherwise false is global.
|
||||
exports.conf = {
|
||||
permLevel: "User",
|
||||
guildOnly: false
|
||||
};
|
19
slash/ping.js
Normal file
19
slash/ping.js
Normal file
@ -0,0 +1,19 @@
|
||||
exports.run = async (client, interaction) => { // eslint-disable-line no-unused-vars
|
||||
await interaction.deferReply();
|
||||
const reply = await interaction.editReply("Ping?");
|
||||
await interaction.editReply(`Pong! Latency is ${reply.createdTimestamp - interaction.createdTimestamp}ms. API Latency is ${Math.round(client.ws.ping)}ms.`);
|
||||
};
|
||||
|
||||
exports.commandData = {
|
||||
name: "ping",
|
||||
description: "Pongs when pinged.",
|
||||
options: [],
|
||||
defaultPermission: true,
|
||||
};
|
||||
|
||||
// Set guildOnly to true if you want it to be available on guilds only.
|
||||
// Otherwise false is global.
|
||||
exports.conf = {
|
||||
permLevel: "User",
|
||||
guildOnly: false
|
||||
};
|
31
slash/stats.js
Normal file
31
slash/stats.js
Normal file
@ -0,0 +1,31 @@
|
||||
const { version } = require("discord.js");
|
||||
const { codeBlock } = require("@discordjs/builders");
|
||||
const { DurationFormatter } = require("@sapphire/time-utilities");
|
||||
const durationFormatter = new DurationFormatter();
|
||||
|
||||
exports.run = async (client, interaction) => { // eslint-disable-line no-unused-vars
|
||||
const duration = durationFormatter.format(client.uptime);
|
||||
const stats = codeBlock("asciidoc", `= STATISTICS =
|
||||
• Mem Usage :: ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB
|
||||
• Uptime :: ${duration}
|
||||
• Users :: ${client.guilds.cache.map(g => g.memberCount).reduce((a, b) => a + b).toLocaleString()}
|
||||
• Servers :: ${client.guilds.cache.size.toLocaleString()}
|
||||
• Channels :: ${client.channels.cache.size.toLocaleString()}
|
||||
• Discord.js :: v${version}
|
||||
• Node :: ${process.version}`);
|
||||
await interaction.reply(stats);
|
||||
};
|
||||
|
||||
exports.commandData = {
|
||||
name: "stats",
|
||||
description: "Show's the bots stats.",
|
||||
options: [],
|
||||
defaultPermission: true,
|
||||
};
|
||||
|
||||
// Set guildOnly to true if you want it to be available on guilds only.
|
||||
// Otherwise false is global.
|
||||
exports.conf = {
|
||||
permLevel: "User",
|
||||
guildOnly: false
|
||||
};
|
Loading…
Reference in New Issue
Block a user