|
@@ -1,278 +1,53 @@
|
|
|
-const Discord = require("discord.js");
|
|
|
const TOKEN = require("./token.js");
|
|
|
-const db = require("./db.js");
|
|
|
-const RSSParser = require("rss-parser");
|
|
|
-const interval = require("interval-promise");
|
|
|
-const TurndownService = require("turndown");
|
|
|
const fs = require("fs");
|
|
|
-const Jimp = require("jimp");
|
|
|
-const Clarifai = require("clarifai");
|
|
|
-const ClarifaiTOKEN = require("./clarifai_keys.js");
|
|
|
-
|
|
|
-const EMOTE_GUILD = "505333548694241281";
|
|
|
-
|
|
|
-const VALID_EXTENSIONS = new Set([
|
|
|
- "png",
|
|
|
- "jpg",
|
|
|
- "jpeg",
|
|
|
- "tiff",
|
|
|
- "tif",
|
|
|
- "bmp",
|
|
|
- "webp"
|
|
|
-]);
|
|
|
-
|
|
|
-const clarifaiApp = new Clarifai.App({
|
|
|
- apiKey: ClarifaiTOKEN
|
|
|
-});
|
|
|
-
|
|
|
-const turndown = new TurndownService();
|
|
|
-turndown.addRule("image", {
|
|
|
- filter: "img",
|
|
|
- replacement: () => ""
|
|
|
-});
|
|
|
-turndown.addRule("link", {
|
|
|
- filter: node => node.nodeName === "A" &&node.getAttribute("href"),
|
|
|
- replacement: (content, node) => node.getAttribute("href")
|
|
|
-});
|
|
|
-
|
|
|
-const statsFile = fs.openSync(__dirname + "/imagestats.csv", "a");
|
|
|
-
|
|
|
-const client = new Discord.Client();
|
|
|
-const parser = new RSSParser();
|
|
|
-const RSS_UPDATE_INTERVAL_MIN = 5;
|
|
|
+const path = require("path");
|
|
|
+const client = require("./client.js");
|
|
|
|
|
|
const REACT_PROBABILITY = 0.6;
|
|
|
|
|
|
-function isValidImage(fileName) {
|
|
|
- let extPosition = fileName.lastIndexOf(".");
|
|
|
- if(extPosition < 0)
|
|
|
- return false;
|
|
|
- let ext = fileName.substring(extPosition + 1).toLowerCase();
|
|
|
- return VALID_EXTENSIONS.has(ext);
|
|
|
-}
|
|
|
-
|
|
|
-function getThreadId(url) {
|
|
|
- let result = url.substring(url.lastIndexOf(".") + 1);
|
|
|
- if(result.endsWith("/"))
|
|
|
- result = result.substring(0, result.length - 1);
|
|
|
- return result;
|
|
|
-}
|
|
|
-
|
|
|
-async function checkFeeds() {
|
|
|
- console.log(`Checking feeds on ${new Date().toISOString()}`);
|
|
|
- let feeds = db.get("rssFeeds").value();
|
|
|
- let outlets = db.get("feedOutputs").value();
|
|
|
- let oldNews = db.get("postedNewsGuids");
|
|
|
- for(let feedEntry of feeds) {
|
|
|
- let feed = await parser.parseURL(feedEntry.url);
|
|
|
- if(feed.items.length == 0)
|
|
|
- continue;
|
|
|
- let printableItems = feed.items.filter(i => i.isoDate > feedEntry.lastUpdate).sort((a, b) => a.isoDate.localeCompare(b.isoDate));
|
|
|
- if(printableItems.length > 0) {
|
|
|
- printableItems.forEach(item => {
|
|
|
- let itemID = getThreadId(item.guid);
|
|
|
- if(oldNews.has(itemID).value())
|
|
|
- return;
|
|
|
- outlets.forEach(ch => {
|
|
|
- client.channels.get(ch).send(`**${item.title}**\nPosted by ${item.creator}\n${item.link}\n\n${turndown.turndown(item[feedEntry.contentElement]).replace(/( {2}\n|\n\n){2,}/gm, "\n").replace(item.link, "")}`);
|
|
|
- });
|
|
|
- oldNews.set(itemID, true).write();
|
|
|
- });
|
|
|
- let lastUpdateDate = printableItems[printableItems.length - 1].isoDate;
|
|
|
- console.log(`Setting last update marker on ${feedEntry.url} to ${lastUpdateDate}`);
|
|
|
- db.get("rssFeeds").find({ url: feedEntry.url}).assign({lastUpdate: lastUpdateDate}).write();
|
|
|
- }
|
|
|
+function trigger(actions, ...params) {
|
|
|
+ let actionDone = false;
|
|
|
+ for (let i = 0; i < actions.length; i++) {
|
|
|
+ const action = actions[i];
|
|
|
+ actionDone |= action(...params, actionDone);
|
|
|
}
|
|
|
+ return actionDone;
|
|
|
}
|
|
|
|
|
|
-function isAuthorised(member) {
|
|
|
- if (db.get("editors.users").includes(member.id).value())
|
|
|
- return true;
|
|
|
- if (db.get("editors.roles").intersectionWith(member.roles.keyArray()).isEmpty().value())
|
|
|
- return false;
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-async function processFaceSwap(message, attachmentUrl) {
|
|
|
- let result = await clarifaiApp.models.predict(Clarifai.FACE_DETECT_MODEL, attachmentUrl);
|
|
|
-
|
|
|
- if(result.outputs[0].data.regions === undefined || result.outputs[0].data.regions.length == 0)
|
|
|
- return;
|
|
|
-
|
|
|
- let image = await Jimp.read(attachmentUrl);
|
|
|
- let w = image.getWidth();
|
|
|
- let h = image.getHeight();
|
|
|
-
|
|
|
- let emojiKeys = [...client.guilds.get(EMOTE_GUILD).emojis.filter(e => !e.animated).keys()];
|
|
|
-
|
|
|
- for(let region of result.outputs[0].data.regions) {
|
|
|
- let bb = region.region_info.bounding_box;
|
|
|
- let bw = (bb.right_col - bb.left_col) * w;
|
|
|
- let bh = (bb.bottom_row - bb.top_row) * h;
|
|
|
-
|
|
|
- let dx = (bb.right_col + bb.left_col) * w / 2;
|
|
|
- let dy = (bb.bottom_row + bb.top_row) * h / 2;
|
|
|
-
|
|
|
- let emojiKey = emojiKeys[Math.floor(Math.random() * emojiKeys.length)];
|
|
|
- let emoji = client.emojis.get(emojiKey);
|
|
|
-
|
|
|
- let emojiImage = await Jimp.read(emoji.url);
|
|
|
- let ew = emojiImage.getWidth();
|
|
|
- let eh = emojiImage.getHeight();
|
|
|
-
|
|
|
- const CONSTANT_SCALE = 1.5;
|
|
|
- let scaleFactor = Math.max(bw, bh) / Math.min(ew, eh) * CONSTANT_SCALE;
|
|
|
- ew *= scaleFactor;
|
|
|
- eh *= scaleFactor;
|
|
|
-
|
|
|
- emojiImage = emojiImage.scale(scaleFactor);
|
|
|
- image = image.composite(emojiImage, dx - ew / 2, dy - eh / 2);
|
|
|
- }
|
|
|
-
|
|
|
- image.quality(90);
|
|
|
- let buffer = await image.getBufferAsync(Jimp.MIME_JPEG);
|
|
|
+let commands = {};
|
|
|
+let msgActions = [];
|
|
|
+let indirectMentionActions = [];
|
|
|
+let startActions = [];
|
|
|
+let directMessageActions = [];
|
|
|
+let postActions = [];
|
|
|
|
|
|
- message.channel.send(`I noticed a face in the image. I think this looks better ${client.emojis.get("505076258753740810").toString()}`, {
|
|
|
- files: [ buffer ]
|
|
|
+client.on("ready", () => {
|
|
|
+ console.log("Starting up NoctBot!");
|
|
|
+ client.user.setActivity("@NoctBot help", {
|
|
|
+ type: "PLAYING"
|
|
|
});
|
|
|
-}
|
|
|
-
|
|
|
-function faceMorph(message) {
|
|
|
- let probValue = db.get("faceEditChannels").get(message.channel.id);
|
|
|
- if(probValue.isUndefined().value() || probValue.isNull().value())
|
|
|
- return;
|
|
|
-
|
|
|
- if(Math.random() > probValue.value())
|
|
|
- return;
|
|
|
-
|
|
|
- let imageAttachment = message.attachments.find(v => isValidImage(v.filename));
|
|
|
- processFaceSwap(message, imageAttachment.url).catch(err => console.log(`Failed to run faceapp because ${err}`));
|
|
|
-}
|
|
|
-
|
|
|
-const commands = {
|
|
|
- "make guide": msg => {
|
|
|
- if (!isAuthorised(msg.member)) return;
|
|
|
- let content = msg.content.substring(msg.content.indexOf("make guide") + "make guide ".length);
|
|
|
- let guideName = content.substring(0, content.indexOf("\n")).trim();
|
|
|
- let guideContent = content.substring(content.indexOf("\n")).trim();
|
|
|
-
|
|
|
- let guide = db.get("guides").find({ name: guideName });
|
|
|
-
|
|
|
- if (!guide.isUndefined().value()) {
|
|
|
- guide.assign({ content: guideContent }).write();
|
|
|
- } else {
|
|
|
- db.get("guides")
|
|
|
- .push({
|
|
|
- name: guideName,
|
|
|
- content: guideContent
|
|
|
- })
|
|
|
- .write();
|
|
|
- }
|
|
|
-
|
|
|
- msg.channel.send(
|
|
|
- `${msg.author.toString()} Added/updated "${guideName}"!`
|
|
|
- );
|
|
|
- },
|
|
|
- "delete guide": (msg, s) => {
|
|
|
- if (!isAuthorised(msg.member)) return;
|
|
|
- let guideName = s.substring("delete guide ".length).trim();
|
|
|
- let val = db.get("guides").find({ name: guideName });
|
|
|
-
|
|
|
- if (val.isUndefined().value()) {
|
|
|
- msg.channel.send(`${msg.author.toString()} No guide "${guideName}"!`);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- db.get("guides")
|
|
|
- .remove({ name: guideName })
|
|
|
- .write();
|
|
|
- msg.channel.send(
|
|
|
- `${msg.author.toString()} Removed guide "${guideName}"!`
|
|
|
- );
|
|
|
- },
|
|
|
- "react to": (msg, s) => {
|
|
|
- if (!isAuthorised(msg.member)) return;
|
|
|
- const pattern = /^react to\s+"([^"]+)"\s+with\s+\<:[^:]+:([^\>]+)\>$/i;
|
|
|
- let contents = pattern.exec(s);
|
|
|
- if(contents != null) {
|
|
|
- let reactable = contents[1].trim().toLowerCase();
|
|
|
- let reactionEmoji = contents[2];
|
|
|
- if(!client.emojis.has(reactionEmoji)){
|
|
|
- msg.channel.send(`${msg.author.toString()} I cannot react with this emoji :(`);
|
|
|
- return;
|
|
|
- }
|
|
|
- db.get("messageReactions").set(reactable, reactionEmoji).write();
|
|
|
- msg.channel.send(`${msg.author.toString()} Added reaction!`);
|
|
|
- }
|
|
|
- },
|
|
|
- "remove reaction to": (msg, s) => {
|
|
|
- if (!isAuthorised(msg.member)) return;
|
|
|
- let content = s.substring("remove reaction to ".length).trim().toLowerCase();
|
|
|
- if(!db.get("messageReactions").has(content).value()) {
|
|
|
- msg.channel.send(`${msg.author.toString()} No such reaction available!`);
|
|
|
- return;
|
|
|
- }
|
|
|
- db.get("messageReactions").unset(content).write();
|
|
|
- msg.channel.send(`${msg.author.toString()} Removed reaction!`);
|
|
|
- },
|
|
|
- "reactions": msg => {
|
|
|
- let reactions = db.get("messageReactions").keys().value().reduce((p, c) => `${p}\n${c}`, "");
|
|
|
- msg.channel.send(`I'll react to the following messages:\n\`\`\`${reactions}\`\`\``);
|
|
|
- },
|
|
|
- "help": msg => {
|
|
|
- let guides = db
|
|
|
- .get("guides")
|
|
|
- .map(g => g.name)
|
|
|
- .reduce((p, c) => `${p}\n${c}`, "")
|
|
|
- .value();
|
|
|
- msg.channel.send(
|
|
|
- `Hello! I am NoctBot! I have answers for the most common C(O)M-related questions.\nJust ping me with one of the following keywords:\n\`\`\`${guides}\`\`\``
|
|
|
- );
|
|
|
+ for (let i = 0; i < startActions.length; i++) {
|
|
|
+ const action = startActions[i];
|
|
|
+ action();
|
|
|
}
|
|
|
-};
|
|
|
-
|
|
|
-client.on("ready", () => {
|
|
|
- console.log("Ready!");
|
|
|
- client.user.setActivity("@NoctBot help", { type: "PLAYING" });
|
|
|
- interval(checkFeeds, RSS_UPDATE_INTERVAL_MIN * 60 * 1000);
|
|
|
+ console.log("NoctBot is ready!");
|
|
|
});
|
|
|
|
|
|
client.on("message", m => {
|
|
|
- if (m.author.id == client.user.id) return;
|
|
|
-
|
|
|
- let imagesCount = m.attachments.filter(v => isValidImage(v.filename)).size;
|
|
|
-
|
|
|
- if(imagesCount > 0) {
|
|
|
- let now = new Date();
|
|
|
- fs.writeSync(statsFile, `${now.getUTCFullYear()}-${now.getUTCMonth()+1}-${now.getUTCDate()} ${now.getUTCHours()}:${now.getUTCMinutes()};${imagesCount};${m.channel.name}\n`);
|
|
|
- faceMorph(m);
|
|
|
- }
|
|
|
+ if (m.author.id == client.user.id)
|
|
|
+ return;
|
|
|
|
|
|
let content = m.cleanContent.trim();
|
|
|
- let lowerContent = content.toLowerCase();
|
|
|
- if(db.get("messageReactions").has(lowerContent).value()) {
|
|
|
- m.react(client.emojis.get(db.get("messageReactions").get(lowerContent).value()));
|
|
|
- return;
|
|
|
- }
|
|
|
|
|
|
- if(m.mentions.users.size == 0)
|
|
|
+ if (trigger(msgActions, m, content))
|
|
|
return;
|
|
|
|
|
|
- if (!db.get("reactableMentionedUsers").intersectionWith(m.mentions.users.map(u => u.id)).isEmpty().value()) {
|
|
|
- const emoteId = db
|
|
|
- .get("emotes")
|
|
|
- .get("angery")
|
|
|
- .randomElement()
|
|
|
- .value();
|
|
|
- m.react(client.emojis.find(e => e.id == emoteId));
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (m.mentions.users.size > 0 && m.mentions.users.first().id == client.user.id) {
|
|
|
+
|
|
|
+ if (content.startsWith(`@${client.user.username}`)) {
|
|
|
+ content = content.substring(`@${client.user.username}`.length).trim();
|
|
|
|
|
|
- if (m.mentions.users.first().id == client.user.id) {
|
|
|
- if(content.startsWith(`@${client.user.username}`)) {
|
|
|
- content = content
|
|
|
- .substring(`@${client.user.username} `.length);
|
|
|
- let lowerCaseContent = content.toLowerCase().trim();
|
|
|
+ let lowerCaseContent = content.toLowerCase();
|
|
|
for (let c in commands) {
|
|
|
if (lowerCaseContent.startsWith(c)) {
|
|
|
commands[c](m, content);
|
|
@@ -280,48 +55,56 @@ client.on("message", m => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (lowerCaseContent.length > 0) {
|
|
|
- let parts = lowerCaseContent.trim().split(" ");
|
|
|
- let guide = db
|
|
|
- .get("guides")
|
|
|
- .map(g => Object.assign({parts: g.name.toLowerCase().split(" ")}, g))
|
|
|
- .sortBy(g => g.parts.length)
|
|
|
- .maxBy(k => db._.intersection(parts, k.parts).length)
|
|
|
- .value();
|
|
|
- let hits =
|
|
|
- guide !== undefined &&
|
|
|
- db._.intersection(guide.name.toLowerCase().split(" "), parts).length > 0;
|
|
|
- if (hits) {
|
|
|
- m.channel.send(guide.content);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
+ if (lowerCaseContent.length > 0 && trigger(directMessageActions, m, lowerCaseContent))
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- let emoteType = "angery";
|
|
|
- if(db.get("specialUsers").includes(m.author.id).value())
|
|
|
- emoteType = "hug";
|
|
|
- else if(db.get("bigUsers").includes(m.author.id).value())
|
|
|
- emoteType = "big";
|
|
|
- else if(db.get("dedUsers").includes(m.author.id).value())
|
|
|
- emoteType = "ded";
|
|
|
-
|
|
|
- const id = db
|
|
|
- .get("emotes")
|
|
|
- .get(emoteType)
|
|
|
- .randomElement()
|
|
|
- .value();
|
|
|
- m.channel.send(client.emojis.find(e => e.id == id).toString());
|
|
|
- } else if (m.content.includes("Noct")) {
|
|
|
- m.channel.send(
|
|
|
- client.emojis.find(e => e.name == "mukuNeighWaaaaaa").toString()
|
|
|
- );
|
|
|
+ if (trigger(indirectMentionActions, m))
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ trigger(postActions);
|
|
|
});
|
|
|
|
|
|
client.on("messageReactionAdd", (r, u) => {
|
|
|
- if(Math.random() <= REACT_PROBABILITY && !r.me)
|
|
|
+ if (Math.random() <= REACT_PROBABILITY && !r.me)
|
|
|
r.message.react(r.emoji);
|
|
|
});
|
|
|
|
|
|
-client.login(TOKEN);
|
|
|
+function main() {
|
|
|
+ let commandsPath = path.resolve(path.dirname(module.filename), "commands");
|
|
|
+ let files = fs.readdirSync(commandsPath);
|
|
|
+
|
|
|
+ for (let i = 0; i < files.length; i++) {
|
|
|
+ const file = files[i];
|
|
|
+ let ext = path.extname(file);
|
|
|
+ if (ext != ".js")
|
|
|
+ continue;
|
|
|
+
|
|
|
+ let obj = require(path.resolve(commandsPath, file));
|
|
|
+ if (obj.commands) {
|
|
|
+ for (let command in obj.commands) {
|
|
|
+ if (obj.commands.hasOwnProperty(command))
|
|
|
+ commands[command] = obj.commands[command];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (obj.onMessage)
|
|
|
+ msgActions.push(obj.onMessage);
|
|
|
+
|
|
|
+ if (obj.onIndirectMention)
|
|
|
+ indirectMentionActions.push(obj.onIndirectMention);
|
|
|
+
|
|
|
+ if (obj.onDirectMention)
|
|
|
+ directMessageActions.push(obj.onDirectMention);
|
|
|
+
|
|
|
+ if (obj.postMessage)
|
|
|
+ postActions.push(obj.postMessage);
|
|
|
+
|
|
|
+ if (obj.onStart)
|
|
|
+ startActions.push(obj.onStart);
|
|
|
+ }
|
|
|
+
|
|
|
+ client.login(TOKEN);
|
|
|
+}
|
|
|
+
|
|
|
+main();
|