Skip to content
This repository was archived by the owner on Sep 18, 2024. It is now read-only.

Commit 90d78e1

Browse files
authored
2.0.0 (#147)
* 2.0.0 Removed the installer and all required packages. Removed the `loadCommand` and `unloadCommand` functions. Removed the `wait` function, was no longer used. Removed the `owners` functionality as the concept was too advanced for an example bot. Required the `logger` file where it was needed instead of binding it to `client`. Required the `settings` database and `config` file where needed. Moved the `clean` function into the `eval` command. Updated all of the code to reflect these changes. Moved the discord bot token into an `.env` file for better security. Added `owner` to the `.env` file. To-do; Change `levelCache` into a function to pull it off the `client` property. * Missed one whoops, missed one. * Fixed events Removed `client` from `getSettings()` as it expects one variable, not two. * Clean up Removed an unused prototype pollutant, fixed a padding mistake and cleaned up the client object. * Further clean up Found some stray `client.awaitReply` functions, updated the reload command to work with aliases. * Final draft of simplification. Significantly reduced the `client` pollution by including a single `container` to the client and attaching every thing else, such as the `commands`, `aliases`, etc collections to that instead. Updated the code to reflect these changes. * Dependency update Replaced the dead `moment`, and `moment-duration-format` dependencies with `@sapphire/time-utilities`. Replaced `chalk` with `colorette`. * Formatting consistency. Updated the timestamp for the logger to be consistent with previous versions. * Whoopsy I forgot to revert a testing change. * Removed default params Both `stat` commands included redundant parameters, they have been removed. * Fixing error logging * Fixed Issues in Commands (#150) * removed token lines from config.js (#151) * removed token lines from config.js * Update .gitignore I'm making an executive decision to merge this without full testing from multiple sources; It works for me and two other individuals.
1 parent e00ca48 commit 90d78e1

32 files changed

+413
-1348
lines changed

.env-example

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
DISCORD_TOKEN=
2+
OWNER=

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,4 @@ typings/
6161
# dotenv environment variables file
6262
.env
6363

64-
/.vscode/
64+
/.vscode/

README.md

+5-6
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@ Need support? Join the [Idiot's Guide Community](https://discord.gg/vXVxsAjSMF)!
2626
- The node-gyp build tools. This is a pre-requisite for Enmap, but also for a **lot** of other modules. See [The Enmap Guide](https://enmap.evie.codes/install#pre-requisites) for details and requirements for your OS. Just follow what's in the tabbed block only, then come back here!
2727

2828
You also need your bot's token. This is obtained by creating an application in
29-
the Developer section of discord.com. Check the [first section of this page](https://anidiots.guide/getting-started/the-long-version.html)
29+
the Developer section of discord.com. Check the [first section of this page](https://anidiots.guide/getting-started/getting-started-long-version)
3030
for more info.
3131

3232
## Intents
3333

34-
Guidebot uses intents which are required as of October 7, 2020.
3534
You can enable privileged intents in your bot page
3635
(the one you got your token from) under `Privileged Gateway Intents`.
3736

@@ -46,16 +45,16 @@ For more info about intents checkout the [official Discord.js guide page](https:
4645

4746
## Downloading
4847

49-
Create a folder within your projects directory and run the following:
48+
Create a folder within your projects directory and run the following inside it:
5049

5150
`git clone https://github.com/anidiotsguide/guidebot.git .`
5251

5352
Once finished:
5453

55-
- In the folder from where you ran the git command, run `npm install`, which will install the required packages,
56-
and it will then run the installer, make sure you have your token at hand to paste into the console.
54+
- In the folder from where you ran the git command, run `npm install`, which will install the required packages.
5755
- **If you get any error about python or msibuild.exe or binding, read the requirements section again!**
58-
- The installer will create the `config.js` file for you, if it doesn't work we have supplied an example config file.
56+
- Rename `config.js.example` to `config.js`, and give it the required intents and any partials you may require.
57+
- Rename `.env-example` to `.env` and put in your bot token in it and save.
5958

6059
## Starting the bot
6160

commands/conf.js

+12-9
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ your bot. The `del` action removes the key also from every guild, and loses its
77
*/
88

99
const { codeBlock } = require("@discordjs/builders");
10+
const config = require("../config.js");
11+
const { awaitReply } = require("../modules/functions.js");
12+
const { settings } = require("../modules/settings.js");
1013

1114
exports.run = async (client, message, [action, key, ...value], level) => { // eslint-disable-line no-unused-vars
1215

1316
// Retrieve Default Values from the default settings in the bot.
14-
const defaults = client.settings.get("default");
15-
const replying = client.settings.ensure(message.guild.id, client.config.defaultSettings).commandReply;
17+
const defaults = settings.get("default");
18+
const replying = settings.ensure(message.guild.id, config.defaultSettings).commandReply;
1619

1720
// Adding a new key adds it to every guild (it will be visible to all of them)
1821
if (action === "add") {
@@ -24,7 +27,7 @@ exports.run = async (client, message, [action, key, ...value], level) => { // es
2427
defaults[key] = value.join(" ");
2528

2629
// One the settings is modified, we write it back to the collection
27-
client.settings.set("default", defaults);
30+
settings.set("default", defaults);
2831
message.reply({ content: `${key} successfully added with the value of ${value.join(" ")}`, allowedMentions: { repliedUser: (replying === "true") }});
2932
} else
3033

@@ -36,7 +39,7 @@ exports.run = async (client, message, [action, key, ...value], level) => { // es
3639

3740
defaults[key] = value.join(" ");
3841

39-
client.settings.set("default", defaults);
42+
settings.set("default", defaults);
4043
message.reply({ content: `${key} successfully edited to ${value.join(" ")}`, allowedMentions: { repliedUser: (replying === "true") }});
4144
} else
4245

@@ -47,20 +50,20 @@ exports.run = async (client, message, [action, key, ...value], level) => { // es
4750
if (!defaults[key]) return message.reply({ content: "This key does not exist in the settings", allowedMentions: { repliedUser: (replying === "true") }});
4851

4952
// Throw the 'are you sure?' text at them.
50-
const response = await client.awaitReply(message, `Are you sure you want to permanently delete ${key} from all guilds? This **CANNOT** be undone.`);
53+
const response = await awaitReply(message, `Are you sure you want to permanently delete ${key} from all guilds? This **CANNOT** be undone.`);
5154

5255
// If they respond with y or yes, continue.
5356
if (["y", "yes"].includes(response)) {
5457

5558
// We delete the default `key` here.
5659
delete defaults[key];
57-
client.settings.set("default", defaults);
60+
settings.set("default", defaults);
5861

5962
// then we loop on all the guilds and remove this key if it exists.
6063
// "if it exists" is done with the filter (if the key is present and it's not the default config!)
61-
for (const [guildId, conf] of client.settings.filter((setting, id) => setting[key] && id !== "default")) {
64+
for (const [guildId, conf] of settings.filter((setting, id) => setting[key] && id !== "default")) {
6265
delete conf[key];
63-
client.settings.set(guildId, conf);
66+
settings.set(guildId, conf);
6467
}
6568

6669
message.reply({ content: `${key} was successfully deleted.`, allowedMentions: { repliedUser: (replying === "true") }});
@@ -80,7 +83,7 @@ exports.run = async (client, message, [action, key, ...value], level) => { // es
8083
// Display all default settings.
8184
} else {
8285
const array = [];
83-
Object.entries(client.settings.get("default")).forEach(([key, value]) => {
86+
Object.entries(settings.get("default")).forEach(([key, value]) => {
8487
array.push(`${key}${" ".repeat(20 - key.length)}:: ${value}`);
8588
});
8689
await message.channel.send(codeBlock("asciidoc", `= Bot Default Settings =

commands/deploy.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
exports.run = async (client, message, args, level) => { // eslint-disable-line no-unused-vars
22
// Filter the slash commands to find guild only ones.
3-
const guildCmds = client.slashcmds.filter(c => c.guildOnly).map(c => c.commandData);
3+
const guildCmds = client.container.slashcmds.filter(c => c.guildOnly).map(c => c.commandData);
44

55
// Now we filter out global commands by inverting the filter.
6-
const globalCmds = client.slashcmds.filter(c => !c.guildOnly).map(c => c.commandData);
6+
const globalCmds = client.container.slashcmds.filter(c => !c.guildOnly).map(c => c.commandData);
77

88
// Give the user a notification the commands are deploying.
99
await message.channel.send("Deploying commands!");

commands/eval.js

+26-7
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,36 @@
55

66
const { codeBlock } = require("@discordjs/builders");
77

8+
/*
9+
MESSAGE CLEAN FUNCTION
10+
11+
"Clean" removes @everyone pings, as well as tokens, and makes code blocks
12+
escaped so they're shown more easily. As a bonus it resolves promises
13+
and stringifies objects!
14+
This is mostly only used by the Eval and Exec commands.
15+
*/
16+
async function clean(client, text) {
17+
if (text && text.constructor.name == "Promise")
18+
text = await text;
19+
if (typeof text !== "string")
20+
text = require("util").inspect(text, {depth: 1});
21+
22+
text = text
23+
.replace(/`/g, "`" + String.fromCharCode(8203))
24+
.replace(/@/g, "@" + String.fromCharCode(8203));
25+
26+
text = text.replaceAll(client.token, "[REDACTED]");
27+
28+
return text;
29+
}
30+
831
// However it's, like, super ultra useful for troubleshooting and doing stuff
932
// you don't want to put in a command.
1033
exports.run = async (client, message, args, level) => { // eslint-disable-line no-unused-vars
1134
const code = args.join(" ");
12-
try {
13-
const evaled = eval(code);
14-
const clean = await client.clean(client, evaled);
15-
message.channel.send(codeBlock("js", clean));
16-
} catch (err) {
17-
message.channel.send(codeBlock("xl", `ERROR ${await client.clean(client, err)}`));
18-
}
35+
const evaled = eval(code);
36+
const cleaned = await clean(client, evaled);
37+
message.channel.send(codeBlock("js", cleaned));
1938
};
2039

2140
exports.conf = {

commands/help.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ help command, its extended help is shown.
88
const { codeBlock } = require("@discordjs/builders");
99

1010
exports.run = (client, message, args, level) => {
11+
// Grab the container from the client to reduce line length.
12+
const { container } = client;
1113
// If no specific command is called, show all filtered commands.
1214
if (!args[0]) {
1315
// Load guild settings (for prefixes and eventually per-guild tweaks)
1416
const settings = message.settings;
1517

1618
// Filter all commands by which are available for the user's level, using the <Collection>.filter() method.
17-
const myCommands = message.guild ? client.commands.filter(cmd => client.levelCache[cmd.conf.permLevel] <= level) :
18-
client.commands.filter(cmd => client.levelCache[cmd.conf.permLevel] <= level && cmd.conf.guildOnly !== true);
19+
const myCommands = message.guild ? container.commands.filter(cmd => container.levelCache[cmd.conf.permLevel] <= level) :
20+
container.commands.filter(cmd => container.levelCache[cmd.conf.permLevel] <= level && cmd.conf.guildOnly !== true);
1921

2022
// Then we will filter the myCommands collection again to get the enabled commands.
2123
const enabledCommands = myCommands.filter(cmd => cmd.conf.enabled);
@@ -44,9 +46,9 @@ exports.run = (client, message, args, level) => {
4446
} else {
4547
// Show individual command's help.
4648
let command = args[0];
47-
if (client.commands.has(command)) {
48-
command = client.commands.get(command);
49-
if (level < client.levelCache[command.conf.permLevel]) return;
49+
if (container.commands.has(command)) {
50+
command = container.commands.get(command);
51+
if (level < container.levelCache[command.conf.permLevel]) return;
5052
message.channel.send(codeBlock("asciidoc", `= ${command.help.name} = \n${command.help.description}\nusage:: ${command.help.usage}\naliases:: ${command.conf.aliases.join(", ")}`));
5153
}
5254
}};

commands/mylevel.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
const config = require("../config.js");
2+
const { settings } = require("../modules/settings.js");
13
exports.run = async (client, message, args, level) => {
2-
const friendly = client.config.permLevels.find(l => l.level === level).name;
3-
const replying = client.settings.ensure(message.guild.id, client.config.defaultSettings).commandReply;
4+
const friendly = config.permLevels.find(l => l.level === level).name;
5+
const replying = settings.ensure(message.guild.id, config.defaultSettings).commandReply;
46
message.reply({ content: `Your permission level is: ${level} - ${friendly}`, allowedMentions: { repliedUser: (replying === "true") }});
57
};
68

commands/reboot.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
const config = require("../config.js");
2+
const { settings } = require("../modules/settings.js");
13
exports.run = async (client, message, args, level) => { // eslint-disable-line no-unused-vars
2-
const replying = client.settings.ensure(message.guild.id, client.config.defaultSettings).commandReply;
4+
const replying = settings.ensure(message.guild.id, config.defaultSettings).commandReply;
35
await message.reply({ content: "Bot is shutting down.", allowedMentions: { repliedUser: (replying === "true") }});
4-
await Promise.all(client.commands.map(cmd =>
5-
client.unloadCommand(cmd)
6-
));
6+
await Promise.all(client.container.commands.map(cmd => {
7+
// the path is relative to the *current folder*, so just ./filename.js
8+
delete require.cache[require.resolve(`./${cmd.help.name}.js`)];
9+
// We also need to delete and reload the command from the container.commands Enmap
10+
client.container.commands.delete(cmd.help.name);
11+
}));
712
process.exit(0);
813
};
914

commands/reload.js

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1+
const config = require("../config.js");
2+
const { settings } = require("../modules/settings.js");
13
exports.run = async (client, message, args, level) => { // eslint-disable-line no-unused-vars
2-
const replying = client.settings.ensure(message.guild.id, client.config.defaultSettings).commandReply;
3-
if (!args || args.length < 1) return message.reply({ content: "Must provide a command to reload. Derp.", allowedMentions: { repliedUser: (replying === "true") }});
4-
const command = client.commands.get(args[0]) || client.commands.get(client.aliases.get(args[0]));
5-
let response = await client.unloadCommand(args[0]);
6-
if (response) return message.reply({ content: `Error Unloading: ${response}`, allowedMentions: { repliedUser: (replying === "true") }});
7-
8-
response = client.loadCommand(command.help.name);
9-
if (response) return message.reply({ content: `Error Loading: ${response}`, allowedMentions: { repliedUser: (replying === "true") }});
4+
// Grab the container from the client to reduce line length.
5+
const { container } = client;
6+
const replying = settings.ensure(message.guild.id, config.defaultSettings).commandReply;
7+
if (!args || args.length < 1) return message.reply("Must provide a command name to reload.");
8+
const command = container.commands.get(args[0]) || container.commands.get(container.aliases.get(args[0]));
9+
// Check if the command exists and is valid
10+
if (!command) {
11+
return message.reply("That command does not exist");
12+
}
13+
// the path is relative to the *current folder*, so just ./filename.js
14+
delete require.cache[require.resolve(`./${command.help.name}.js`)];
15+
// We also need to delete and reload the command from the container.commands Enmap
16+
container.commands.delete(command.help.name);
17+
const props = require(`./${command.help.name}.js`);
18+
container.commands.set(command.help.name, props);
1019

1120
message.reply({ content: `The command \`${command.help.name}\` has been reloaded`, allowedMentions: { repliedUser: (replying === "true") }});
1221
};

commands/set.js

+15-13
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@
1111
// OR the same as:
1212
// const [action, key, ...value] = args;
1313
const { codeBlock } = require("@discordjs/builders");
14+
const { settings } = require("../modules/settings.js");
15+
const { awaitReply } = require("../modules/functions.js");
1416

1517
exports.run = async (client, message, [action, key, ...value], level) => { // eslint-disable-line no-unused-vars
1618

1719
// Retrieve current guild settings (merged) and overrides only.
18-
const settings = message.settings;
19-
const defaults = client.settings.get("default");
20-
const overrides = client.settings.get(message.guild.id);
21-
const replying = settings.commandReply;
22-
if (!client.settings.has(message.guild.id)) client.settings.set(message.guild.id, {});
20+
const serverSettings = message.settings;
21+
const defaults = settings.get("default");
22+
const overrides = settings.get(message.guild.id);
23+
const replying = serverSettings.commandReply;
24+
if (!settings.has(message.guild.id)) settings.set(message.guild.id, {});
2325

2426
// Edit an existing key value
2527
if (action === "edit") {
@@ -31,13 +33,13 @@ exports.run = async (client, message, [action, key, ...value], level) => { // es
3133
// User must specify a value to change.
3234
if (joinedValue.length < 1) return message.reply({ content: "Please specify a new value", allowedMentions: { repliedUser: (replying === "true") }});
3335
// User must specify a different value than the current one.
34-
if (joinedValue === settings[key]) return message.reply({ content: "This setting already has that value!", allowedMentions: { repliedUser: (replying === "true") }});
36+
if (joinedValue === serverSettings[key]) return message.reply({ content: "This setting already has that value!", allowedMentions: { repliedUser: (replying === "true") }});
3537

3638
// If the guild does not have any overrides, initialize it.
37-
if (!client.settings.has(message.guild.id)) client.settings.set(message.guild.id, {});
39+
if (!settings.has(message.guild.id)) settings.set(message.guild.id, {});
3840

3941
// Modify the guild overrides directly.
40-
client.settings.set(message.guild.id, joinedValue, key);
42+
settings.set(message.guild.id, joinedValue, key);
4143

4244
// Confirm everything is fine!
4345
message.reply({ content: `${key} successfully edited to ${joinedValue}`, allowedMentions: { repliedUser: (replying === "true") }});
@@ -50,29 +52,29 @@ exports.run = async (client, message, [action, key, ...value], level) => { // es
5052
if (!overrides[key]) return message.reply({ content: "This key does not have an override and is already using defaults.", allowedMentions: { repliedUser: (replying === "true") }});
5153

5254
// Good demonstration of the custom awaitReply method in `./modules/functions.js` !
53-
const response = await client.awaitReply(message, `Are you sure you want to reset ${key} to the default value?`);
55+
const response = await awaitReply(message, `Are you sure you want to reset ${key} to the default value?`);
5456

5557
// If they respond with y or yes, continue.
5658
if (["y", "yes"].includes(response.toLowerCase())) {
5759
// We delete the `key` here.
58-
client.settings.delete(message.guild.id, key);
60+
settings.delete(message.guild.id, key);
5961
message.reply({ content: `${key} was successfully reset to default.`, allowedMentions: { repliedUser: (replying === "true") }});
6062
} else
6163
// If they respond with n or no, we inform them that the action has been cancelled.
6264
if (["n","no","cancel"].includes(response)) {
63-
message.reply({ content: `Your setting for \`${key}\` remains at \`${settings[key]}\``, allowedMentions: { repliedUser: (replying === "true") }});
65+
message.reply({ content: `Your setting for \`${key}\` remains at \`${serverSettings[key]}\``, allowedMentions: { repliedUser: (replying === "true") }});
6466
}
6567
} else
6668

6769
if (action === "get") {
6870
if (!key) return message.reply({ content: "Please specify a key to view", allowedMentions: { repliedUser: (replying === "true") }});
6971
if (!defaults[key]) return message.reply({ content: "This key does not exist in the settings", allowedMentions: { repliedUser: (replying === "true") }});
7072
const isDefault = !overrides[key] ? "\nThis is the default global default value." : "";
71-
message.reply({ content: `The value of ${key} is currently ${settings[key]}${isDefault}`, allowedMentions: { repliedUser: (replying === "true") }});
73+
message.reply({ content: `The value of ${key} is currently ${serverSettings[key]}${isDefault}`, allowedMentions: { repliedUser: (replying === "true") }});
7274
} else {
7375
// Otherwise, the default action is to return the whole configuration;
7476
const array = [];
75-
Object.entries(settings).forEach(([key, value]) => {
77+
Object.entries(serverSettings).forEach(([key, value]) => {
7678
array.push(`${key}${" ".repeat(20 - key.length)}:: ${value}`);
7779
});
7880
await message.channel.send(codeBlock("asciidoc", `= Current Guild Settings =

commands/stats.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
const { version } = require("discord.js");
22
const { codeBlock } = require("@discordjs/builders");
3-
const moment = require("moment");
4-
require("moment-duration-format");
5-
3+
const { DurationFormatter } = require("@sapphire/time-utilities");
4+
const durationFormatter = new DurationFormatter();
65

76
exports.run = (client, message, args, level) => { // eslint-disable-line no-unused-vars
8-
const duration = moment.duration(client.uptime).format(" D [days], H [hrs], m [mins], s [secs]");
7+
const duration = durationFormatter.format(client.uptime);
98
const stats = codeBlock("asciidoc", `= STATISTICS =
109
• Mem Usage :: ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB
1110
• Uptime :: ${duration}
12-
• Users :: ${client.users.cache.size.toLocaleString()}
11+
• Users :: ${client.guilds.cache.map(g => g.memberCount).reduce((a, b) => a + b).toLocaleString()}
1312
• Servers :: ${client.guilds.cache.size.toLocaleString()}
1413
• Channels :: ${client.channels.cache.size.toLocaleString()}
1514
• Discord.js :: v${version}

0 commit comments

Comments
 (0)