new stuff

This commit is contained in:
Emily 2020-08-18 14:58:36 +10:00
commit a5b6425af1
22 changed files with 1375 additions and 0 deletions

19
bot/base/Command.js Normal file
View file

@ -0,0 +1,19 @@
class Command {
constructor (client, {
name = null,
description = "No description provided.",
category = "Miscellaneous",
usage = "No usage provided.",
enabled = true,
guildOnly = false,
devOnly = false,
aliases = new Array(),
permLevel = "User"
}) {
this.client = client;
this.conf = { enabled, guildOnly, devOnly, aliases, permLevel };
this.help = { name, description, category, usage };
}
}
module.exports = Command;

25
bot/commands/Core/ping.js Normal file
View file

@ -0,0 +1,25 @@
const Command = require("../../base/Command.js");
class Ping extends Command {
constructor (client) {
super(client, {
name: "ping",
description: "Latency and API response times.",
usage: "ping",
aliases: ["pong"]
});
}
async run (message, args, level) { // eslint-disable-line no-unused-vars
try {
const msg = await message.channel.send('Pinging...')
msg.edit(
`Pong! \`${msg.createdTimestamp - message.createdTimestamp}ms\` (💗 \`${Math.round(this.client.ws.ping)}ms\`)`
)
} catch (err) {
this.client.logger.err(err)
}
}
}
module.exports = Ping;

View file

@ -0,0 +1,37 @@
const Command = require("../../base/Command.js");
const Discord = require("discord.js");
class Eval extends Command {
constructor (client) {
super(client, {
description: "Evaluates arbitrary Javascript.",
usage: "eval <expression>",
aliases: ["ev"],
permLevel: "Bot Owner",
devOnly: true
});
}
async run (message, args, data) { // eslint-disable-line no-unused-vars
const code = args.join(" ");
try {
const evaled = eval(code);
const clean = await this.client.util.clean(evaled);
const MAX_CHARS = 3 + 2 + clean.length + 3;
if (MAX_CHARS > 2000) {
message.channel.send({ files: [new Discord.MessageAttachment(Buffer.from(clean), "output.txt")] });
}
message.channel.send(`\`\`\`js\n${clean}\n\`\`\``);
} catch (err) {
const e = await this.client.util.clean(err);
const MAX_CHARS = 1 + 5 + 1 + 3 + e.length + 3;
console.log(MAX_CHARS);
if (MAX_CHARS > 2000) {
return message.channel.send({ files: [new Discord.MessageAttachment(Buffer.from(e), "error.txt")] });
}
message.channel.send(`\`ERROR\` \`\`\`xl\n${e}\n\`\`\``);
}
}
}
module.exports = Eval;

9
bot/events/error.js Normal file
View file

@ -0,0 +1,9 @@
module.exports = class {
constructor (client) {
this.client = client;
}
async run (error) {
this.client.logger.log(`An error event was sent by Discord.js: \n${JSON.stringify(error)}`, "error");
}
};

46
bot/events/message.js Normal file
View file

@ -0,0 +1,46 @@
// 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 = class {
constructor (client) {
this.client = client;
}
async run (message) {
if (message.author.bot) return;
let data = new Object();
if (message.content.indexOf(this.client.config.defaultPrefix) !== 0) return;
const args = message.content.slice(this.client.config.defaultPrefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
// Cache uncached members
if (message.guild && !message.member) await message.guild.fetchMember(message.author);
const cmd = this.client.commands.get(command) || this.client.commands.get(this.client.aliases.get(command));
if (!cmd) return;
if (cmd && cmd.conf.devOnly && this.client.util.isDeveloper(message.author.id) !== true) {
return message.channel.send("devs only!");
}
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.");
}
message.flags = [];
while (args[0] &&args[0][0] === "-") {
message.flags.push(args.shift().slice(1));
}
message.util = this.client.messageUtil;
cmd.run(message, args, data);
this.client.logger.cmd(`Command ran: ${message.content}`);
// TODO: Command caching if it"s worth it
}
};

10
bot/events/ready.js Normal file
View file

@ -0,0 +1,10 @@
module.exports = class {
constructor (client) {
this.client = client;
}
async run () {
await this.client.wait(1000);
this.client.logger.ready(`Connected to Discord as ${this.client.user.tag}`);
}
};

46
bot/index.js Normal file
View file

@ -0,0 +1,46 @@
// Check that the runtime is up to date
if (Number(process.version.slice(1).split(".")[0]) < 12) {
console.error(`Node v12.0.0 or higher is required. You have Node ${process.version}. Please update Node on your system.`);
process.exit(1);
}
// Load up the discord.js library
const { Collection, Client } = require("discord.js");
// Our custom client, extends the standard Discord client with things we will need.
class Custom extends Client {
constructor (options) {
super(options);
this.path = __dirname;
this.dev = true;
this.config = require("../config.json");
this.logger = require("./util/logger");
this.util = new (require("./util/util"))(this);
this.messageUtil = new (require("./util/messageUtil"))(this);
// Create collections to store loaded commands and aliases in
this.commands = new Collection();
this.aliases = new Collection();
const handlers = require("./util/handlers");
this.commandHandler = new handlers.CommandHandler(this);
this.eventHandler = new handlers.EventHandler(this);
// Basically just an async shortcut to using a setTimeout. Nothing fancy!
this.wait = require("util").promisify(setTimeout);
}
}
// Initialize client
const client = new Custom();
client.commandHandler.loadAll();
client.eventHandler.loadAll();
if (client.dev === true) {
client.logger.warn("Development mode is on.");
client.login(client.config.devtoken);
} else {
client.login(client.config.token);
}

13
bot/shardLauncher.js Normal file
View file

@ -0,0 +1,13 @@
const discord = require("discord.js");
const config = require("../config.json");
const manager = new discord.ShardingManager("./index.js", {
totalShards: "auto",
token: config.token
});
manager.on("shardCreate", (shard) => {
console.log("Shard " + shard.id + " launched");
});
manager.spawn();

94
bot/util/handlers.js Normal file
View file

@ -0,0 +1,94 @@
const fs = require("fs");
class CommandHandler {
constructor (client) {
this.client = client;
}
load (name, category) {
try {
const path = this.client.path + "/commands/" + category + "/" + name + ".js";
const props = new (require(path))(this.client);
this.client.logger.debug(`Loading command ${category}/${name}`);
props.help.name = name;
props.help.category = category;
if (props.init) {
props.init(this.client);
}
this.client.commands.set(props.help.name, props);
props.conf.aliases.forEach(alias => {
this.client.aliases.set(alias, props.help.name);
});
return;
} catch (err) {
return `Failed to load command ${name}: ${err}`;
}
}
unload (name) {
}
loadAll () {
const commandDirectories = fs.readdirSync("./commands/");
this.client.logger.debug(`Found ${commandDirectories.length} command directories.`);
commandDirectories.forEach((dir) => {
const commandFiles = fs.readdirSync("./commands/" + dir + "/");
commandFiles.filter((cmd) => cmd.split(".").pop() === "js").forEach((cmd) => {
cmd = cmd.substring(0, cmd.length - 3);
const resp = this.load(cmd, dir);
if (resp) {
this.client.logger.error(resp);
}
});
});
}
}
class EventHandler {
constructor (client) {
this.client = client;
}
load (name) {
try {
this.client.logger.debug(`Loading event ${name}`);
const path = this.client.path + "/events/" + name + ".js";
const event = new (require(path))(this.client);
this.client.on(name, (...args) => event.run(...args));
delete require.cache[require.resolve(path)];
return;
} catch (err) {
return `Failed to load event ${name}: ${err}`;
}
}
unload (name) {
}
loadAll () {
const eventFiles = fs.readdirSync(this.client.path + "/events");
eventFiles.forEach(file => {
const name = file.split(".")[0];
const resp = this.load(name);
if (resp) {
this.client.logger.error(resp);
}
});
}
}
module.exports = {
CommandHandler: CommandHandler,
EventHandler: EventHandler
};

54
bot/util/logger.js Normal file
View file

@ -0,0 +1,54 @@
const { createLogger, format, transports, addColors } = require("winston");
const { combine, timestamp, printf, colorize } = format;
const fmt = printf(({ level, message, timestamp }) => {
return `${timestamp} - ${level}: ${message}`;
});
const customLevels = {
levels: {
debug: 0,
cmd: 1,
info: 2,
ready: 3,
warn: 4,
error: 5
},
colours: {
debug: "magenta",
cmd: "white",
info: "cyan",
ready: "green",
warn: "yellow",
error: "red"
}
};
const logger = createLogger({
levels: customLevels.levels,
level: "error",
format: combine(
timestamp({
format: "YYYY-MM-DD hh:mm:ss"
}),
fmt
),
transports: [
new transports.Console({
level: "error",
format: combine(
timestamp({
format: "YYYY-MM-DD hh:mm:ss"
}),
colorize(),
fmt
)
})
]
});
addColors(customLevels.colours);
module.exports = logger;

8
bot/util/messageUtil.js Normal file
View file

@ -0,0 +1,8 @@
class MessageUtil {
constructor (client) {
this.client = client;
}
}
module.exports = MessageUtil;

35
bot/util/util.js Normal file
View file

@ -0,0 +1,35 @@
class Util {
constructor (client) {
this.client = client;
}
isDeveloper (userID) {
let isDev = false;
const developers = this.client.config.ownerIDs;
developers.forEach(devID => {
if (devID === userID) {
isDev = true;
}
});
console.log(isDev);
return isDev;
}
async clean (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))
.replace(this.client.token, "mfa.VkO_2G4Qv3T--NO--lWetW_tjND--TOKEN--QFTm6YGtzq9PH--4U--tG0");
return text;
}
}
module.exports = Util;