news_aggregator.js 3.8 KB

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