rss_checker.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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 verifyChannelID = db.get("newsPostVerifyChannel").value();
  8. const reactionCollectors = {};
  9. const verifyMessageIdToPostId = {};
  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 parser = new RSSParser();
  20. const RSS_UPDATE_INTERVAL_MIN = 5;
  21. function getThreadId(url) {
  22. let result = url.substring(url.lastIndexOf(".") + 1);
  23. if(result.endsWith("/"))
  24. result = result.substring(0, result.length - 1);
  25. return result;
  26. }
  27. async function checkFeeds() {
  28. console.log(`Checking feeds on ${new Date().toISOString()}`);
  29. let feeds = db.get("rssFeeds").value();
  30. let oldNews = db.get("postedNewsGuids");
  31. for(let feedEntry of feeds) {
  32. let feed = await parser.parseURL(feedEntry.url);
  33. if(feed.items.length == 0)
  34. continue;
  35. let printableItems = feed.items.filter(i => i.isoDate > feedEntry.lastUpdate).sort((a, b) => a.isoDate.localeCompare(b.isoDate));
  36. if(printableItems.length > 0) {
  37. printableItems.forEach(item => {
  38. let itemID = getThreadId(item.guid);
  39. let contents = item[feedEntry.contentElement];
  40. let itemObj = {
  41. id: itemID,
  42. title: item.title,
  43. link: item.link,
  44. creator: item.creator,
  45. contents: contents,
  46. hash: sha1(contents),
  47. messageId: null,
  48. verifyMessageId: null
  49. };
  50. if(oldNews.has(itemObj.id).value()){
  51. let data = oldNews.get(itemObj.id).value();
  52. // Old type, don't care
  53. if(data === true)
  54. return;
  55. // Add message ID to mark for edit
  56. if(data.hash != itemObj.hash)
  57. itemObj.messageId = data.messageId;
  58. else
  59. return;
  60. }
  61. if(!shouldVerify())
  62. sendNews(itemObj);
  63. else
  64. addVerifyMessage(itemObj);
  65. });
  66. let lastUpdateDate = printableItems[printableItems.length - 1].isoDate;
  67. console.log(`Setting last update marker on ${feedEntry.url} to ${lastUpdateDate}`);
  68. db.get("rssFeeds").find({ url: feedEntry.url}).assign({lastUpdate: lastUpdateDate}).write();
  69. }
  70. }
  71. }
  72. function initPendingReactors() {
  73. let verifyChannel = client.channels.get(verifyChannelID);
  74. db.get("newsCache").forOwn(async i => {
  75. let m = await verifyChannel.fetchMessage(i.verifyMessageId);
  76. let collector = m.createReactionCollector(isVerifyReaction, { maxEmojis: 1 });
  77. collector.on("collect", collectReaction)
  78. reactionCollectors[m.id] = collector;
  79. verifyMessageIdToPostId[m.id] = i.id;
  80. }).value();
  81. }
  82. async function addVerifyMessage(item) {
  83. let verifyChannel = client.channels.get(verifyChannelID);
  84. let cache = db.get("newsCache");
  85. let postedNews = db.get("postedNewsGuids");
  86. let process = "🆕 ADD";
  87. if(postedNews.has(item.id).value())
  88. process = "✏️ EDIT";
  89. if(cache.has(item.id).value()) {
  90. let oldItem = cache.get(item.id).value();
  91. let oldMessage = await verifyChannel.fetchMessage(oldItem.verifyMessageId);
  92. if(oldMessage)
  93. await oldMessage.delete();
  94. }
  95. let newMessage = await verifyChannel.send(`[${process}]
  96. Post ID: **${item.id}**
  97. ${newsToString(item)}
  98. React with ✅ (approve) or ❌ (deny).`
  99. );
  100. await newMessage.react("✅");
  101. await newMessage.react("❌");
  102. var collector = newMessage.createReactionCollector(isVerifyReaction, { maxEmojis: 1 });
  103. collector.on("collect", collectReaction)
  104. reactionCollectors[newMessage.id] = collector;
  105. verifyMessageIdToPostId[newMessage.id] = item.id;
  106. item.verifyMessageId = newMessage.id;
  107. cache.set(item.id, item).write();
  108. }
  109. function collectReaction(reaction, collector) {
  110. let cache = db.get("newsCache");
  111. let oldNews = db.get("postedNewsGuids");
  112. let m = reaction.message;
  113. collector.stop();
  114. delete reactionCollectors[m.id];
  115. let postId = verifyMessageIdToPostId[m.id];
  116. let item = cache.get(postId).value();
  117. cache.unset(postId).write();
  118. m.delete();
  119. if(reaction.emoji.name == "✅")
  120. sendNews(item);
  121. else
  122. oldNews.set(item.id, {
  123. hash: item.hash,
  124. messageId: null
  125. }).write();
  126. }
  127. async function sendNews(item) {
  128. let outChannel = db.get("feedOutputChannel").value();
  129. let oldNews = db.get("postedNewsGuids");
  130. let sentMessage = await postNewsItem(outChannel, item);
  131. oldNews.set(item.id, {
  132. hash: item.hash,
  133. messageId: sentMessage.id
  134. }).write();
  135. }
  136. function isVerifyReaction(reaction, user) {
  137. return (reaction.emoji.name == "✅" || reaction.emoji.name == "❌") && !user.bot;
  138. }
  139. function shouldVerify() {
  140. return verifyChannelID != "";
  141. }
  142. async function postNewsItem(channel, item) {
  143. let newsText = newsToString(item);
  144. let ch = client.channels.get(channel);
  145. if(item.messageId) {
  146. let message = await ch.fetchMessage(item.messageId);
  147. if(message)
  148. return await message.edit(newsText);
  149. else
  150. return await ch.send(newsText);
  151. }
  152. else
  153. return await ch.send(newsText);
  154. }
  155. function newsToString(item) {
  156. return `**${item.title}**
  157. Posted by ${item.creator}
  158. ${item.link}
  159. ${turndown.turndown(item.contents).replace(/( {2}\n|\n\n){2,}/gm, "\n").replace(item.link, "")}`;
  160. }
  161. const onStart = () => {
  162. initPendingReactors();
  163. interval(checkFeeds, RSS_UPDATE_INTERVAL_MIN * 60 * 1000);
  164. };
  165. module.exports = {
  166. onStart: onStart
  167. };