news_aggregator.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. const TurndownService = require("turndown");
  2. const RSSParser = require("rss-parser");
  3. const db = require("../db.js");
  4. const interval = require("interval-promise");
  5. const client = require("../client.js");
  6. const sha1 = require("sha1");
  7. const html = require("node-html-parser");
  8. const axios = require("axios");
  9. const path = require("path");
  10. const fs = require("fs");
  11. const Discord = require("discord.js");
  12. const UPDATE_INTERVAL = 5;
  13. const aggregators = [];
  14. const aggregateChannelID = db.get("aggregateChannel").value();
  15. // TODO: Run BBCode converter instead
  16. const turndown = new TurndownService();
  17. turndown.addRule("image", {
  18. filter: "img",
  19. replacement: () => ""
  20. });
  21. turndown.addRule("link", {
  22. filter: node => node.nodeName === "A" &&node.getAttribute("href"),
  23. replacement: (content, node) => node.getAttribute("href")
  24. });
  25. function markdownify(htmStr, link) {
  26. return turndown.turndown(htmStr)/*.replace(/( {2}\n|\n\n){2,}/gm, "\n").replace(link, "")*/;
  27. }
  28. async function checkFeeds() {
  29. console.log(`Aggregating feeds on ${new Date().toISOString()}`);
  30. let aggregatorJobs = [];
  31. for(let aggregator of aggregators) {
  32. if(aggregator.aggregate)
  33. aggregatorJobs.push(aggregator.aggregate());
  34. }
  35. let aggregatedItems = await Promise.all(aggregatorJobs);
  36. for(let itemSet of aggregatedItems) {
  37. for(let item of itemSet) {
  38. let itemObj = {
  39. id: item.id,
  40. link: item.link || "",
  41. title: item.title || "",
  42. author: item.author,
  43. contents: markdownify(item.contents, item.link),
  44. hash: null,
  45. cacheMessageId: null,
  46. postedMessageId: null,
  47. embedColor: item.embedColor || 0xffffffff
  48. };
  49. itemObj.hash = sha1(itemObj.contents);
  50. await addNewsItem(itemObj);
  51. }
  52. }
  53. }
  54. // TODO: Replace with proper forum implementation
  55. async function addNewsItem(item) {
  56. let aggrItems = db.get("aggregatedItemsCache");
  57. if(aggrItems.has(item.id).value()) {
  58. let postedItem = aggrItems.get(item.id).value();
  59. // No changes, skip
  60. if(postedItem.hash == item.hash)
  61. return;
  62. else
  63. await deleteCacheMessage(postedItem.cacheMessageId);
  64. }
  65. let ch = client.channels.get(aggregateChannelID);
  66. let msg = await ch.send(new Discord.RichEmbed({
  67. title: item.title,
  68. url: item.link,
  69. color: item.embedColor,
  70. timestamp: new Date(),
  71. description: `${item.contents.substring(0, Math.min(item.contents.length, 300))}...`,
  72. author: {
  73. name: item.author
  74. },
  75. footer: {
  76. text: "NoctBot News Aggregator"
  77. }
  78. }));
  79. aggrItems.set(item.id, {
  80. hash: item.hash,
  81. cacheMessageId: msg.id,
  82. postedMessageId: null
  83. }).write();
  84. }
  85. async function deleteCacheMessage(messageId) {
  86. let ch = client.channels.get(aggregateChannelID);
  87. let msg = await tryFetchMessage(ch, messageId);
  88. if(msg)
  89. await msg.delete();
  90. }
  91. async function tryFetchMessage(channel, messageId) {
  92. try {
  93. return await channel.fetchMessage(messageId);
  94. }catch(error){
  95. return null;
  96. }
  97. }
  98. function initAggregators() {
  99. let aggregatorsPath = path.join(path.dirname(module.filename), "aggregators");
  100. let files = fs.readdirSync(aggregatorsPath);
  101. for(let file of files) {
  102. let ext = path.extname(file);
  103. if(ext != ".js")
  104. continue;
  105. let obj = require(path.resolve(aggregatorsPath, file));
  106. if(obj)
  107. aggregators.push(obj);
  108. if(obj.init)
  109. obj.init();
  110. }
  111. }
  112. function onStart() {
  113. initAggregators();
  114. interval(checkFeeds, UPDATE_INTERVAL * 1000);
  115. };
  116. module.exports = {
  117. onStart: onStart
  118. };