Initial commit

This commit is contained in:
TheEssem 2019-09-13 15:02:41 -05:00
commit c33a86eb4c
132 changed files with 5860 additions and 0 deletions

7
utils/client.js Normal file
View file

@ -0,0 +1,7 @@
// separate the client from app.js so we can call it later
const { Client } = require("eris");
const config = require("../config.json");
const client = new Client(config.token, {
defaultImageSize: 1024
});
module.exports = client;

4
utils/collections.js Normal file
View file

@ -0,0 +1,4 @@
const { Collection } = require("eris");
exports.commands = new Collection();
exports.aliases = new Collection();

6
utils/database.js Normal file
View file

@ -0,0 +1,6 @@
// database stuff
const Enmap = require("enmap");
const settings = new Enmap({ name: "settings" });
exports.settings = settings;
const tags = new Enmap({ name: "tags" });
exports.tags = tags;

10
utils/dbl.js Normal file
View file

@ -0,0 +1,10 @@
// dbl api client
const DBL = require("dblapi.js");
const logger = require("./logger.js");
const config = require("../config.json");
const client = require("./client.js");
const dbl = new DBL(config.dblToken, client);
dbl.on("error", e => {
logger.error(e);
});
module.exports = dbl;

15
utils/gmbuffer.js Normal file
View file

@ -0,0 +1,15 @@
// workaround for a gm bug where it doesn't output buffers properly
// https://github.com/aheckmann/gm/issues/572#issuecomment-293768810
module.exports = (data) => {
return new Promise((resolve, reject) => {
data.stream((err, stdout, stderr) => {
if (err) return reject(err);
const chunks = [];
stdout.on("data", (chunk) => { chunks.push(chunk); });
// these are 'once' because they can and do fire multiple times for multiple errors,
// but this is a promise so you'll have to deal with them one at a time
stdout.once("end", () => { resolve(Buffer.concat(chunks)); });
stderr.once("data", (data) => { reject(String(data)); });
});
});
};

31
utils/handler.js Normal file
View file

@ -0,0 +1,31 @@
const collections = require("./collections.js");
exports.load = async (command) => {
const props = require(`../commands/${command}`);
collections.commands.set(command.split(".")[0], props.run);
if (props.aliases) {
props.aliases.forEach(alias => {
collections.aliases.set(alias, command.split(".")[0]);
});
}
return false;
};
exports.unload = async (command) => {
let cmd;
if (collections.commands.has(command)) {
cmd = collections.commands.get(command);
} else if (collections.aliases.has(command)) {
cmd = collections.commands.get(collections.aliases.get(command));
}
if (!cmd) return `The command \`${command}\` doesn't seem to exist, nor is it an alias.`;
const mod = require.cache[require.resolve(`../commands/${command}`)];
delete require.cache[require.resolve(`../commands/${command}.js`)];
for (let i = 0; i < mod.parent.children.length; i++) {
if (mod.parent.children[i] === mod) {
mod.parent.children.splice(i, 1);
break;
}
}
return false;
};

57
utils/imagedetect.js Normal file
View file

@ -0,0 +1,57 @@
const fetch = require("node-fetch");
const fileType = require("file-type");
// this checks if the file is, in fact, an image
const typeCheck = async (image) => {
// download the file to a buffer
const imageRequest = await fetch(image);
const imageBuffer = await imageRequest.buffer();
try {
// get the file type
const imageType = fileType(imageBuffer);
// check if the file is a jpeg, png, or webp
if (imageType && ["image/jpeg", "image/png", "image/webp"].includes(imageType.mime)) {
// if it is, then return the url with the file type
return {
type: imageType.ext,
url: image
};
} else {
// if not, then return a message
return "Not the correct file type";
}
} catch (error) {
if (error) console.error;
}
};
// this checks for the latest message containing an image and returns the url of the image
module.exports = async (cmdMessage) => {
// we start by getting the messages
const messages = await cmdMessage.channel.getMessages();
// iterate over each message
for (const message of messages) {
// check the attachments first
if (message.attachments.length !== 0) {
// get type of file
const type = await typeCheck(message.attachments[0].url);
// move to the next message if the file isn't an image
if (type === "Not the correct file type") continue;
// if the file is an image then return it
return type;
// if there's nothing in the attachments check the embeds next
} else if (message.embeds.length !== 0) {
// embeds can have 2 possible entries with images, we check the thumbnail first
if (message.embeds[0].thumbnail) {
const type = await typeCheck(message.embeds[0].thumbnail.url);
if (type === "Not the correct file type") continue;
return type;
// if there isn't a thumbnail check the image area
} else if (message.embeds[0].image) {
const type = await typeCheck(message.embeds[0].image.url);
if (type === "Not the correct file type") continue;
return type;
}
}
}
};

18
utils/logger.js Normal file
View file

@ -0,0 +1,18 @@
const moment = require("moment");
const winston = require("winston");
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "logs/error.log", level: "error" }),
new winston.transports.File({ filename: "logs/main.log" }),
],
format: winston.format.printf(log => `[${moment().format("YYYY-MM-DD HH:mm:ss")}]: [${log.level.toUpperCase()}] - ${log.message}`)
});
exports.log = (type = "info", content) => logger.log(type, content);
exports.error = (...args) => this.log("error", ...args);
exports.warn = (...args) => this.log("warn", ...args);
exports.debug = (...args) => this.log("debug", ...args);

12
utils/meme.sh Normal file
View file

@ -0,0 +1,12 @@
newfile=$(mktemp /tmp/XXXXXXXXXXXXXXXXXXXXXX.png)
convert - -resize 600x600 +profile "*" $newfile
SIZE=$(identify -format "%[fx:w]x%[fx:h]" $newfile)
convert $newfile \
-gravity north \
\( -size $SIZE -background none -font "./assets/ImpactMix.ttf" -pointsize 50 -stroke black -strokewidth 3 caption:"$1" \) -composite \
\( -size $SIZE -background none -font "./assets/ImpactMix.ttf" -pointsize 50 -fill white -stroke none caption:"$1" \) -composite \
-gravity south \
\( -size $SIZE -background none -font "./assets/ImpactMix.ttf" -pointsize 50 -stroke black -strokewidth 3 caption:"$2" \) -composite \
\( -size $SIZE -background none -font "./assets/ImpactMix.ttf" -pointsize 50 -fill white -stroke none caption:"$2" \) -composite\
-
rm $newfile

41
utils/misc.js Normal file
View file

@ -0,0 +1,41 @@
// random(array) to select a random entry in array
exports.random = (array) => {
return array[Math.floor(Math.random() * array.length)];
};
// clean(text) to clean message of any private info or mentions
exports.clean = async (text) => {
const config = require("../config.json");
if (text && text.constructor.name == "Promise")
text = await text;
if (typeof evaled !== "string")
text = require("util").inspect(text, { depth: 1 });
text = text
.replace(/`/g, `\`${String.fromCharCode(8203)}`)
.replace(/@/g, `@${String.fromCharCode(8203)}`)
.replace(config.token, "<redacted>")
.replace(config.mashapeKey, "<redacted>")
.replace(config.catToken, "<redacted>")
.replace(config.googleKey, "<redacted>")
.replace(config.cseID, "<redacted>")
.replace(config.dblToken, "<redacted>");
return text;
};
// regexEscape(string) to escape characters in a string for use in a regex
exports.regexEscape = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
};
// define defaults for prefixes and tags
exports.defaults = {
prefix: "&"
};
exports.tagDefaults = {
help: {
content: "https://essem.space/esmBot/commands.html?dev=true",
author: "198198681982205953"
}
};

View file

@ -0,0 +1,36 @@
// eris doesn't come with an awaitMessages method by default, so we make our own
const EventEmitter = require("events").EventEmitter;
class MessageCollector extends EventEmitter {
constructor(channel, filter, options = {}) {
super();
this.filter = filter;
this.channel = channel;
this.options = options;
this.ended = false;
this.collected = [];
this.bot = channel.guild ? channel.guild.shard.client : channel._client;
this.listener = message => this.verify(message);
this.bot.on("messageCreate", this.listener);
if (options.time) setTimeout(() => this.stop("time"), options.time);
}
verify(message) {
if (this.channel.id !== message.channel.id) return false;
if (this.filter(message)) {
this.collected.push(message);
this.emit("message", message);
if (this.collected.length >= this.options.maxMatches) this.stop("maxMatches");
return true;
}
return false;
}
stop(reason) {
if (this.ended) return;
this.ended = true;
this.bot.removeListener("messageCreate", this.listener);
this.emit("end", this.collected, reason);
}
}
module.exports = MessageCollector;

View file

@ -0,0 +1,36 @@
// eris doesn't come with an awaitReactions method by default, so we make our own
const EventEmitter = require("events").EventEmitter;
class ReactionCollector extends EventEmitter {
constructor(message, filter, options = {}) {
super();
this.filter = filter;
this.message = message;
this.options = options;
this.ended = false;
this.collected = [];
this.bot = message.channel.guild ? message.channel.guild.shard.client : message.channel._client;
this.listener = (message, emoji, userID) => this.verify(message, emoji, userID);
this.bot.on("messageReactionAdd", this.listener);
if (options.time) setTimeout(() => this.stop("time"), options.time);
}
verify(message, emoji, userID) {
if (this.message.id !== message.id) return false;
if (this.filter(message, emoji, userID)) {
this.collected.push({ message: message, emoji: emoji, userID: userID });
this.emit("reaction", message, emoji, userID);
if (this.collected.length >= this.options.maxMatches) this.stop("maxMatches");
return true;
}
return false;
}
stop(reason) {
if (this.ended) return;
this.ended = true;
this.bot.removeListener("messageReactionAdd", this.listener);
this.emit("end", this.collected, reason);
}
}
module.exports = ReactionCollector;

View file

@ -0,0 +1,49 @@
const ReactionCollector = require("./awaitreactions.js");
const MessageCollector = require("./awaitmessages.js");
const client = require("../client.js");
const paginationEmbed = async (message, pages, timeout = 120000) => {
let page = 0;
pages[page].embed.footer.text = `Page ${page + 1} of ${pages.length}`;
const currentPage = await message.channel.createMessage(pages[page]);
const emojiList = ["◀", "🔢", "▶", "🗑"];
for (const emoji of emojiList) {
await currentPage.addReaction(emoji);
}
const reactionCollector = new ReactionCollector(currentPage, (message, reaction, user) => emojiList.includes(reaction.name) && !client.users.get(user).bot, { time: timeout });
reactionCollector.on("reaction", (msg, reaction) => {
//reaction.users.remove(msg.author);
//const reactionAuthor = currentPage.getReactions();
switch (reaction.name) {
case "◀":
page = page > 0 ? --page : pages.length - 1;
pages[page].embed.footer.text = `Page ${page + 1} of ${pages.length}`;
currentPage.edit(pages[page]);
break;
case "🔢":
message.channel.createMessage(`${message.author.mention}, what page do you want to jump to?`).then(askMessage => {
const messageCollector = new MessageCollector(askMessage.channel, (response) => response.author.id === message.author.id && !isNaN(response.content) && Number(response.content) <= pages.length, { time: timeout, maxMatches: 1 });
return messageCollector.on("message", response => {
askMessage.delete();
page = Number(response.content) - 1;
pages[page].embed.footer.text = `Page ${page + 1} of ${pages.length}`;
currentPage.edit(pages[page]);
});
}).catch(error => { if (error) console.error; });
break;
case "▶":
page = page + 1 < pages.length ? ++page : 0;
pages[page].embed.footer.text = `Page ${page + 1} of ${pages.length}`;
currentPage.edit(pages[page]);
break;
case "🗑":
currentPage.delete();
return;
default:
break;
}
});
reactionCollector.on("end", () => currentPage.removeReactions());
return currentPage;
};
module.exports = paginationEmbed;

23
utils/soundplayer.js Normal file
View file

@ -0,0 +1,23 @@
const client = require("./client.js");
module.exports = async (sound, message) => {
if (message.member.voiceState.channelID) {
if (!message.channel.guild.members.get(client.user.id).permission.has("voiceConnect") || !message.channel.permissionsOf(client.user.id).has("voiceConnect")) return client.createMessage(message.channel.id, `${message.author.mention}, I can't join this voice channel!`);
const voiceChannel = message.channel.guild.channels.get(message.member.voiceState.channelID);
client.createMessage(message.channel.id, "🔊 Playing sound...");
const connection = await client.joinVoiceChannel(voiceChannel.id);
if (connection.playing) {
connection.stopPlaying();
}
connection.play(require("fs").createReadStream(sound));
connection.on("error", (error) => {
voiceChannel.leave();
require("./logger.js").error(error);
});
connection.once("end", () => {
voiceChannel.leave();
});
} else {
client.createMessage(message.channel.id, `${message.author.mention}, you need to be in a voice channel first!`);
}
};

16
utils/urlcheck.js Normal file
View file

@ -0,0 +1,16 @@
module.exports = (string) => {
var protocolAndDomainRE = /^(?:\w+:)?\/\/(\S+)$/;
var domainRE = /^[^\s.]+\.\S{2,}$/;
var match = string.match(protocolAndDomainRE);
if (!match) {
return false;
}
var everythingAfterProtocol = match[1];
if (!everythingAfterProtocol) {
return false;
}
if (domainRE.test(everythingAfterProtocol)) {
return true;
}
return false;
};

12
utils/wrap.js Normal file
View file

@ -0,0 +1,12 @@
module.exports = (str) => {
var regexString = ".{1,15}([\\s\u200B]+|$)|[^\\s\u200B]+?([\\s\u200B]+|$)";
var re = new RegExp(regexString, "g");
var lines = str.match(re) || [];
var result = lines.map((line) => {
if (line.slice(-1) === "\n") {
line = line.slice(0, line.length - 1);
}
return line;
}).join("\n");
return result;
};