123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- import TurndownService, { Options } from "turndown";
- import interval from "interval-promise";
- import { client, forumClient } from "../client";
- import sha1 from "sha1";
- import { ICommand } from "./command";
- import { TextChannel, Message, ReactionCollector, MessageReaction, Collector, User, Channel } from "discord.js";
- import { Dict } from "../util";
- import { getRepository, Not, IsNull } from "typeorm";
- import { PostedForumNewsItem } from "@db/entity/PostedForumsNewsItem";
- import { KnownChannel } from "@db/entity/KnownChannel";
- import { PostVerifyMessage } from "@db/entity/PostVerifyMessage";
- import { render } from "../bbcode-parser/bbcode-js";
- const PREVIEW_CHAR_LIMIT = 300;
- const NEWS_POST_VERIFY_CHANNEL = "newsPostVerify";
- let verifyChannelId: string = null;
- const reactionCollectors: Dict<ReactionCollector> = {};
- const verifyMessageIdToPost: Dict<PostedForumNewsItem> = {};
- const NEWS_FEED_CHANNEL = "newsFeed";
- let botUserId = 0;
- const turndown = new TurndownService();
- turndown.addRule("image", {
- filter: "img",
- replacement: () => ""
- });
- turndown.addRule("link", {
- filter: (node: HTMLElement, opts: Options) => node.nodeName === "A" && node.getAttribute("href") != null,
- replacement: (content: string, node: HTMLElement) => node.getAttribute("href")
- });
- const RSS_UPDATE_INTERVAL_MIN = 5;
- const NEWS_FORUM_ID = 49;
- function bbCodeToMarkdown(bbCode: string) {
- return turndown.turndown(render(bbCode)).replace(/( {2}\n|\n\n){2,}/gm, "\n");
- }
- async function checkFeeds() {
- console.log(`Checking feeds on ${new Date().toISOString()}`);
- let forumsNewsRepo = getRepository(PostedForumNewsItem);
- let postVerifyMessageRepo = getRepository(PostVerifyMessage);
- let forumThreads = await forumClient.getForumThreads(NEWS_FORUM_ID);
- for (let thread of [...forumThreads.threads, ...forumThreads.sticky]) {
- let firstPost = await forumClient.getPost(thread.first_post_id);
- let contents = bbCodeToMarkdown(firstPost.message);
- let itemObj = forumsNewsRepo.create({
- id: thread.thread_id.toString(),
- hash: sha1(firstPost.message),
- verifyMessage: postVerifyMessageRepo.create({
- author: thread.username,
- link: `https://custommaid3d2.com/index.php?threads/${thread.thread_id}/`,
- title: thread.title,
- text: `${contents.substr(0, Math.min(contents.length, PREVIEW_CHAR_LIMIT))}...`,
- isNew: true
- })
- });
- let postItem = await forumsNewsRepo.findOne({
- where: { id: itemObj.id },
- relations: ["verifyMessage"]
- });
- if (postItem) {
- if (process.env.IGNORE_CHANGED_NEWS === "TRUE") {
- await forumsNewsRepo.update({
- id: postItem.id
- }, {
- hash: itemObj.hash
- });
- continue;
- }
- // Add message ID to mark for edit
- if (postItem.hash != itemObj.hash) {
- let newHash = itemObj.hash;
- if (!postItem.verifyMessage)
- postItem.verifyMessage = itemObj.verifyMessage;
- itemObj = postItem;
- itemObj.verifyMessage.isNew = false;
- itemObj.hash = newHash;
- }
- else
- continue;
- }
- if (!shouldVerify() || firstPost.user_id == botUserId)
- await sendNews(itemObj);
- else
- await addVerifyMessage(itemObj);
- }
- }
- async function initPendingReactors() {
- let verifyChannel = client.channels.get(verifyChannelId);
- let repo = getRepository(PostedForumNewsItem);
- let verifyMessageRepo = getRepository(PostVerifyMessage);
- let pendingVerifyMessages = await repo.find({
- where: { verifyMessage: Not(IsNull()) },
- select: ["id"],
- relations: ["verifyMessage"]
- });
- for (let msg of pendingVerifyMessages) {
- let m = await tryFetchMessage(verifyChannel, msg.verifyMessage.messageId);
- if (!m) {
- await repo.update({ id: msg.id }, { verifyMessage: null });
- await verifyMessageRepo.delete(msg.verifyMessage);
- continue;
- }
- let collector = m.createReactionCollector(isVerifyReaction, { maxEmojis: 1 });
- collector.on("collect", collectReaction);
- reactionCollectors[m.id] = collector;
- verifyMessageIdToPost[m.id] = msg;
- }
- }
- async function addVerifyMessage(item: PostedForumNewsItem) {
- let verifyChannel = client.channels.get(verifyChannelId) as TextChannel;
- if(!verifyChannel) {
- console.log(`Skipping adding item ${item.id} because no verify channel is set up!`);
- return;
- }
- let verifyMessageRepo = getRepository(PostVerifyMessage);
- let forumsNewsRepo = getRepository(PostedForumNewsItem);
- if (item.verifyMessage.messageId) {
- let oldMessage = await tryFetchMessage(verifyChannel, item.verifyMessage.messageId);
- if (oldMessage)
- await oldMessage.delete();
- }
- let newMessage = await verifyChannel.send(toVerifyString(item.id, item.verifyMessage)) as Message;
- item.verifyMessage.messageId = newMessage.id;
- await newMessage.react("✅");
- await newMessage.react("❌");
- let collector = newMessage.createReactionCollector(isVerifyReaction, { maxEmojis: 1 });
- collector.on("collect", collectReaction)
- reactionCollectors[newMessage.id] = collector;
- verifyMessageIdToPost[newMessage.id] = item;
- item.verifyMessage = await verifyMessageRepo.save(item.verifyMessage);
- await forumsNewsRepo.save(item);
- }
- async function collectReaction(reaction: MessageReaction, collector: Collector<string, MessageReaction>) {
- let verifyMessageRepo = getRepository(PostVerifyMessage);
- let postRepo = getRepository(PostedForumNewsItem);
- let m = reaction.message;
- collector.stop();
- delete reactionCollectors[m.id];
- let post = verifyMessageIdToPost[m.id];
- await postRepo.update({ id: post.id }, { verifyMessage: null });
- await verifyMessageRepo.delete({ id: post.verifyMessage.id });
- await reaction.message.delete();
- if (reaction.emoji.name == "✅")
- sendNews(post);
- delete verifyMessageIdToPost[m.id];
- }
- async function sendNews(item: PostedForumNewsItem) {
- let channelRepo = getRepository(KnownChannel);
- let newsPostRepo = getRepository(PostedForumNewsItem);
- let outChannel = await channelRepo.findOne({
- where: { channelType: NEWS_FEED_CHANNEL }
- });
- let sentMessage = await postNewsItem(outChannel.channelId, item);
- item.postedMessageId = sentMessage.id;
- item.verifyMessage = null;
- await newsPostRepo.save(item);
- }
- function isVerifyReaction(reaction: MessageReaction, user: User) {
- return (reaction.emoji.name == "✅" || reaction.emoji.name == "❌") && !user.bot;
- }
- async function tryFetchMessage(channel: Channel, messageId: string) {
- try {
- if (!(channel instanceof TextChannel))
- return null;
- return await channel.fetchMessage(messageId);
- } catch (error) {
- return null;
- }
- }
- function shouldVerify() {
- return verifyChannelId != null;
- }
- async function postNewsItem(channel: string, item: PostedForumNewsItem): Promise<Message | null> {
- let newsMessage = toNewsString(item.verifyMessage);
- let ch = client.channels.get(channel);
- if (!(ch instanceof TextChannel))
- return null;
- if (item.postedMessageId) {
- let message = await tryFetchMessage(ch, item.postedMessageId);
- if (message)
- return await message.edit(newsMessage);
- else
- return await ch.send(newsMessage) as Message;
- }
- else
- return await ch.send(newsMessage) as Message;
- }
- function markdownify(htmStr: string, link: string) {
- return turndown.turndown(htmStr).replace(/( {2}\n|\n\n){2,}/gm, "\n").replace(link, "");
- }
- function toNewsString(item: PostVerifyMessage) {
- return `**${item.title}**
- Posted by ${item.author}
- ${item.link}
- ${item.text}`;
- }
- function toVerifyString(postId: string, item: PostVerifyMessage) {
- return `[${item.isNew ? "🆕 ADD" : "✏️ EDIT"}]
- Post ID: **${postId}**
-
- ${toNewsString(item)}
-
- React with ✅ (approve) or ❌ (deny).`;
- }
- export default {
- commands: [
- {
- pattern: /^edit (\d+)\s+((.*[\n\r]*)+)$/i,
- action: async (msg, s, match) => {
- if (msg.channel.id != verifyChannelId)
- return;
- let id = match[1];
- let newContents = match[2].trim();
- let repo = getRepository(PostedForumNewsItem);
- let verifyRepo = getRepository(PostVerifyMessage);
- let post = await repo.findOne({
- where: { id: id },
- relations: ["verifyMessage"]
- });
- if (!post || !post.verifyMessage) {
- msg.channel.send(`${msg.author.toString()} No unapproved news items with id ${id}!`);
- return;
- }
- let editMsg = await tryFetchMessage(client.channels.get(verifyChannelId), post.verifyMessage.messageId);
- if (!editMsg) {
- msg.channel.send(`${msg.author.toString()} No verify message found for ${id}! This is a bug: report to horse.`);
- return;
- }
- post.verifyMessage.text = newContents;
- await verifyRepo.save(post.verifyMessage);
- await editMsg.edit(toVerifyString(post.id, post.verifyMessage));
- await msg.delete();
- }
- }
- ],
- onStart: async () => {
- let repo = getRepository(KnownChannel);
- let verifyChannel = await repo.findOne({
- channelType: NEWS_POST_VERIFY_CHANNEL
- });
- if (!verifyChannel)
- return;
- verifyChannelId = verifyChannel.channelId;
- let user = await forumClient.getMe();
- botUserId = user.user_id;
- await initPendingReactors();
- interval(checkFeeds, RSS_UPDATE_INTERVAL_MIN * 60 * 1000);
- }
- } as ICommand;
|