rss_checker.js 6.3 KB

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