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 isImage = require("is-image"); 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("./imagestats.csv", "a"); const client = new Discord.Client(); const parser = new RSSParser(); const RSS_UPDATE_INTERVAL_MIN = 5; const REACT_PROBABILITY = 0.6; async function checkFeeds() { 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 => { if(oldNews.has(item.guid).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(item.guid, 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 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; } 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}\`\`\`` ); } }; client.on("ready", () => { console.log("Ready!"); client.user.setActivity("@NoctBot help", { type: "PLAYING" }); interval(checkFeeds, RSS_UPDATE_INTERVAL_MIN * 60 * 1000); }); client.on("message", m => { let imagesCount = m.attachments.filter(v => isImage(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}\n`); } 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) 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.first().id == client.user.id) { if(content.startsWith(`@${client.user.username}`)) { content = content .substring(`@${client.user.username} `.length); let lowerCaseContent = content.toLowerCase().trim(); for (let c in commands) { if (lowerCaseContent.startsWith(c)) { commands[c](m, content); return; } } 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; } } } 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() ); } }); client.on("messageReactionAdd", (r, u) => { if(Math.random() <= REACT_PROBABILITY && !r.me) r.message.react(r.emoji); }); client.login(TOKEN);