main.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. const Discord = require("discord.js");
  2. const TOKEN = require("./token.js");
  3. const db = require("./db.js");
  4. const RSSParser = require("rss-parser");
  5. const interval = require("interval-promise");
  6. const TurndownService = require("turndown");
  7. const fs = require("fs");
  8. const isImage = require("is-image");
  9. const Base64 = require("js-base64").Base64;
  10. const turndown = new TurndownService();
  11. turndown.addRule("image", {
  12. filter: "img",
  13. replacement: () => ""
  14. });
  15. turndown.addRule("link", {
  16. filter: node => node.nodeName === "A" &&node.getAttribute("href"),
  17. replacement: (content, node) => node.getAttribute("href")
  18. });
  19. const statsFile = fs.openSync(__dirname + "/imagestats.csv", "a");
  20. const client = new Discord.Client();
  21. const parser = new RSSParser();
  22. const RSS_UPDATE_INTERVAL_MIN = 5;
  23. const REACT_PROBABILITY = 0.6;
  24. async function checkFeeds() {
  25. let feeds = db.get("rssFeeds").value();
  26. let outlets = db.get("feedOutputs").value();
  27. let oldNews = db.get("postedNewsGuids");
  28. for(let feedEntry of feeds) {
  29. let feed = await parser.parseURL(feedEntry.url);
  30. if(feed.items.length == 0)
  31. continue;
  32. let printableItems = feed.items.filter(i => i.isoDate > feedEntry.lastUpdate).sort((a, b) => a.isoDate.localeCompare(b.isoDate));
  33. if(printableItems.length > 0) {
  34. printableItems.forEach(item => {
  35. let itemID = Base64.encode(item.guid);
  36. if(oldNews.has(itemID).value())
  37. return;
  38. outlets.forEach(ch => {
  39. 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, "")}`);
  40. });
  41. oldNews.set(itemID, true).write();
  42. });
  43. let lastUpdateDate = printableItems[printableItems.length - 1].isoDate;
  44. console.log(`Setting last update marker on ${feedEntry.url} to ${lastUpdateDate}`);
  45. db.get("rssFeeds").find({ url: feedEntry.url}).assign({lastUpdate: lastUpdateDate}).write();
  46. }
  47. }
  48. }
  49. function isAuthorised(member) {
  50. if (db.get("editors.users").includes(member.id).value())
  51. return true;
  52. if (db.get("editors.roles").intersectionWith(member.roles.keyArray()).isEmpty().value())
  53. return false;
  54. return true;
  55. }
  56. const commands = {
  57. "make guide": msg => {
  58. if (!isAuthorised(msg.member)) return;
  59. let content = msg.content.substring(msg.content.indexOf("make guide") + "make guide ".length);
  60. let guideName = content.substring(0, content.indexOf("\n")).trim();
  61. let guideContent = content.substring(content.indexOf("\n")).trim();
  62. let guide = db.get("guides").find({ name: guideName });
  63. if (!guide.isUndefined().value()) {
  64. guide.assign({ content: guideContent }).write();
  65. } else {
  66. db.get("guides")
  67. .push({
  68. name: guideName,
  69. content: guideContent
  70. })
  71. .write();
  72. }
  73. msg.channel.send(
  74. `${msg.author.toString()} Added/updated "${guideName}"!`
  75. );
  76. },
  77. "delete guide": (msg, s) => {
  78. if (!isAuthorised(msg.member)) return;
  79. let guideName = s.substring("delete guide ".length).trim();
  80. let val = db.get("guides").find({ name: guideName });
  81. if (val.isUndefined().value()) {
  82. msg.channel.send(`${msg.author.toString()} No guide "${guideName}"!`);
  83. return;
  84. }
  85. db.get("guides")
  86. .remove({ name: guideName })
  87. .write();
  88. msg.channel.send(
  89. `${msg.author.toString()} Removed guide "${guideName}"!`
  90. );
  91. },
  92. "react to": (msg, s) => {
  93. if (!isAuthorised(msg.member)) return;
  94. const pattern = /^react to\s+"([^"]+)"\s+with\s+\<:[^:]+:([^\>]+)\>$/i;
  95. let contents = pattern.exec(s);
  96. if(contents != null) {
  97. let reactable = contents[1].trim().toLowerCase();
  98. let reactionEmoji = contents[2];
  99. if(!client.emojis.has(reactionEmoji)){
  100. msg.channel.send(`${msg.author.toString()} I cannot react with this emoji :(`);
  101. return;
  102. }
  103. db.get("messageReactions").set(reactable, reactionEmoji).write();
  104. msg.channel.send(`${msg.author.toString()} Added reaction!`);
  105. }
  106. },
  107. "remove reaction to": (msg, s) => {
  108. if (!isAuthorised(msg.member)) return;
  109. let content = s.substring("remove reaction to ".length).trim().toLowerCase();
  110. if(!db.get("messageReactions").has(content).value()) {
  111. msg.channel.send(`${msg.author.toString()} No such reaction available!`);
  112. return;
  113. }
  114. db.get("messageReactions").unset(content).write();
  115. msg.channel.send(`${msg.author.toString()} Removed reaction!`);
  116. },
  117. "reactions": msg => {
  118. let reactions = db.get("messageReactions").keys().value().reduce((p, c) => `${p}\n${c}`, "");
  119. msg.channel.send(`I'll react to the following messages:\n\`\`\`${reactions}\`\`\``);
  120. },
  121. "help": msg => {
  122. let guides = db
  123. .get("guides")
  124. .map(g => g.name)
  125. .reduce((p, c) => `${p}\n${c}`, "")
  126. .value();
  127. msg.channel.send(
  128. `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}\`\`\``
  129. );
  130. }
  131. };
  132. client.on("ready", () => {
  133. console.log("Ready!");
  134. client.user.setActivity("@NoctBot help", { type: "PLAYING" });
  135. interval(checkFeeds, RSS_UPDATE_INTERVAL_MIN * 60 * 1000);
  136. });
  137. client.on("message", m => {
  138. let imagesCount = m.attachments.filter(v => isImage(v.filename)).size;
  139. if(imagesCount > 0) {
  140. let now = new Date();
  141. fs.writeSync(statsFile, `${now.getUTCFullYear()}-${now.getUTCMonth()+1}-${now.getUTCDate()} ${now.getUTCHours()}:${now.getUTCMinutes()};${imagesCount};${m.channel.name}\n`);
  142. }
  143. if (m.author.id == client.user.id) return;
  144. let content = m.cleanContent.trim();
  145. let lowerContent = content.toLowerCase();
  146. if(db.get("messageReactions").has(lowerContent).value()) {
  147. m.react(client.emojis.get(db.get("messageReactions").get(lowerContent).value()));
  148. return;
  149. }
  150. if(m.mentions.users.size == 0)
  151. return;
  152. if (!db.get("reactableMentionedUsers").intersectionWith(m.mentions.users.map(u => u.id)).isEmpty().value()) {
  153. const emoteId = db
  154. .get("emotes")
  155. .get("angery")
  156. .randomElement()
  157. .value();
  158. m.react(client.emojis.find(e => e.id == emoteId));
  159. return;
  160. }
  161. if (m.mentions.users.first().id == client.user.id) {
  162. if(content.startsWith(`@${client.user.username}`)) {
  163. content = content
  164. .substring(`@${client.user.username} `.length);
  165. let lowerCaseContent = content.toLowerCase().trim();
  166. for (let c in commands) {
  167. if (lowerCaseContent.startsWith(c)) {
  168. commands[c](m, content);
  169. return;
  170. }
  171. }
  172. if (lowerCaseContent.length > 0) {
  173. let parts = lowerCaseContent.trim().split(" ");
  174. let guide = db
  175. .get("guides")
  176. .map(g => Object.assign({parts: g.name.toLowerCase().split(" ")}, g))
  177. .sortBy(g => g.parts.length)
  178. .maxBy(k => db._.intersection(parts, k.parts).length)
  179. .value();
  180. let hits =
  181. guide !== undefined &&
  182. db._.intersection(guide.name.toLowerCase().split(" "), parts).length > 0;
  183. if (hits) {
  184. m.channel.send(guide.content);
  185. return;
  186. }
  187. }
  188. }
  189. let emoteType = "angery";
  190. if(db.get("specialUsers").includes(m.author.id).value())
  191. emoteType = "hug";
  192. else if(db.get("bigUsers").includes(m.author.id).value())
  193. emoteType = "big";
  194. else if(db.get("dedUsers").includes(m.author.id).value())
  195. emoteType = "ded";
  196. const id = db
  197. .get("emotes")
  198. .get(emoteType)
  199. .randomElement()
  200. .value();
  201. m.channel.send(client.emojis.find(e => e.id == id).toString());
  202. } else if (m.content.includes("Noct")) {
  203. m.channel.send(
  204. client.emojis.find(e => e.name == "mukuNeighWaaaaaa").toString()
  205. );
  206. }
  207. });
  208. client.on("messageReactionAdd", (r, u) => {
  209. if(Math.random() <= REACT_PROBABILITY && !r.me)
  210. r.message.react(r.emoji);
  211. });
  212. client.login(TOKEN);