|
@@ -1,36 +1,28 @@
|
|
|
import TurndownService from "turndown";
|
|
|
import interval from "interval-promise";
|
|
|
-import { client, FORUMS_DOMAIN } from "../client";
|
|
|
+import { client } from "../client";
|
|
|
import sha1 from "sha1";
|
|
|
import * as path from "path";
|
|
|
import * as fs from "fs";
|
|
|
import { HTML2BBCode } from "html2bbcode";
|
|
|
-import { Dict } from "../util";
|
|
|
import { IAggregator, NewsPostItem } from "./aggregators/aggregator";
|
|
|
-import { TextChannel, Message, Channel, ReactionCollector, MessageReaction, User, MessageEmbed } from "discord.js";
|
|
|
-import { getRepository, IsNull, Not } from "typeorm";
|
|
|
+import { TextChannel, Message, MessageEmbed } from "discord.js";
|
|
|
+import { getRepository, } from "typeorm";
|
|
|
import { KnownChannel } from "@shared/db/entity/KnownChannel";
|
|
|
import { AggroNewsItem } from "@shared/db/entity/AggroNewsItem";
|
|
|
-import { v3beta1 } from "@google-cloud/translate";
|
|
|
import { logger } from "src/logging";
|
|
|
import { Plugin } from "src/model/plugin";
|
|
|
-const { TranslationServiceClient } = v3beta1;
|
|
|
+import { assertOk, tryDo } from "src/util";
|
|
|
|
|
|
const UPDATE_INTERVAL = process.env.NODE_ENV == "dev" ? 60 : 5;
|
|
|
-const MAX_PREVIEW_LENGTH = 300;
|
|
|
const AGGREGATOR_MANAGER_CHANNEL = "aggregatorManager";
|
|
|
-const FORUMS_STAGING_ID = 54;
|
|
|
-const FORUMS_NEWS_ID = 49;
|
|
|
|
|
|
@Plugin
|
|
|
export class NewsAggregator {
|
|
|
- tlClient = new TranslationServiceClient();
|
|
|
aggregators: IAggregator[] = [];
|
|
|
aggregateChannelID?: string;
|
|
|
bbCodeParser = new HTML2BBCode();
|
|
|
turndown = new TurndownService();
|
|
|
- reactionCollectors: Dict<ReactionCollector> = {};
|
|
|
- verifyMessageIdToPost: Dict<AggroNewsItem> = {};
|
|
|
|
|
|
constructor() {
|
|
|
this.turndown.addRule("image", {
|
|
@@ -68,13 +60,6 @@ export class NewsAggregator {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- clipText(text: string): string {
|
|
|
- if (text.length <= MAX_PREVIEW_LENGTH)
|
|
|
- return text;
|
|
|
-
|
|
|
- return `${text.substring(0, MAX_PREVIEW_LENGTH)}...`;
|
|
|
- }
|
|
|
-
|
|
|
async addNewsItem(item: NewsPostItem): Promise<void> {
|
|
|
if (!this.aggregateChannelID)
|
|
|
return;
|
|
@@ -112,108 +97,24 @@ export class NewsAggregator {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- if (item.needsTranslation && process.env.GOOGLE_APP_ID)
|
|
|
- try {
|
|
|
- const request = {
|
|
|
- parent: this.tlClient.locationPath(process.env.GOOGLE_APP_ID, "global"),
|
|
|
- contents: [item.title, item.contents],
|
|
|
- mimeType: "text/html",
|
|
|
- sourceLanguageCode: "ja",
|
|
|
- targetLanguageCode: "en"
|
|
|
- };
|
|
|
-
|
|
|
- const [res] = await this.tlClient.translateText(request);
|
|
|
- const [translatedTitle, translatedContents] = res.translations ?? [undefined, undefined];
|
|
|
-
|
|
|
- if (translatedTitle?.translatedText && translatedContents?.translatedText) {
|
|
|
- item.title = translatedTitle.translatedText;
|
|
|
- item.contents = translatedContents.translatedText;
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- logger.error("Failed to translate news with Google", err);
|
|
|
- }
|
|
|
-
|
|
|
- item.contents = this.bbCodeParser.feed(item.contents).toString();
|
|
|
-
|
|
|
- if (!newsItem.forumsEditPostId) {
|
|
|
- const createResponse = await client.forum.createThread(FORUMS_STAGING_ID, item.title, item.contents);
|
|
|
- newsItem.forumsEditPostId = createResponse.thread.thread_id;
|
|
|
- } else if(newsItem.forumsNewsPostId){
|
|
|
- await client.forum.postReply(newsItem.forumsNewsPostId, item.contents);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- const msg = await ch.send(new MessageEmbed({
|
|
|
- title: item.title,
|
|
|
+ const msg = await assertOk(ch.send(new MessageEmbed({
|
|
|
+ title: `${(isNew ? "**[NEW]**" : "**[EDIT]**")} ${item.title}`,
|
|
|
url: item.link,
|
|
|
color: item.embedColor,
|
|
|
timestamp: new Date(),
|
|
|
- description: `${(isNew ? "**[NEW]**" : "**[EDIT]**")}\n[**Edit on forums**](${FORUMS_DOMAIN}/index.php?threads/.${newsItem.forumsEditPostId}/)`,
|
|
|
author: {
|
|
|
name: item.author
|
|
|
},
|
|
|
footer: {
|
|
|
text: "NoctBot News Aggregator"
|
|
|
}
|
|
|
- })) as Message;
|
|
|
+ }))) as Message;
|
|
|
|
|
|
newsItem.editMessageId = msg.id;
|
|
|
|
|
|
- await msg.react("✅");
|
|
|
- await msg.react("❌");
|
|
|
-
|
|
|
- const collector = msg.createReactionCollector(this.isVerifyReaction, { maxEmojis: 1 });
|
|
|
- collector.on("collect", this.collectorFor(collector));
|
|
|
- this.reactionCollectors[msg.id] = collector;
|
|
|
- this.verifyMessageIdToPost[msg.id] = newsItem;
|
|
|
-
|
|
|
await repo.save(newsItem);
|
|
|
}
|
|
|
|
|
|
- isVerifyReaction(reaction: MessageReaction, user: User): boolean {
|
|
|
- return (reaction.emoji.name == "✅" || reaction.emoji.name == "❌") && !user.bot && user.id != client.botUser.id;
|
|
|
- }
|
|
|
-
|
|
|
- collectorFor = (collector: ReactionCollector) =>
|
|
|
- async (reaction: MessageReaction): Promise<void> => {
|
|
|
- const repo = getRepository(AggroNewsItem);
|
|
|
-
|
|
|
- const m = reaction.message;
|
|
|
- collector.stop();
|
|
|
- delete this.reactionCollectors[m.id];
|
|
|
- const post = this.verifyMessageIdToPost[m.id];
|
|
|
-
|
|
|
- if(!post.forumsEditPostId) {
|
|
|
- throw new Error("No forum edit post found!");
|
|
|
- }
|
|
|
-
|
|
|
- if (reaction.emoji.name == "✅") {
|
|
|
- const res = await client.forum.getThread(post.forumsEditPostId);
|
|
|
- const forumPost = await client.forum.getPost(res.thread.first_post_id);
|
|
|
-
|
|
|
- if (!post.forumsNewsPostId) {
|
|
|
- const newThread = await client.forum.createThread(FORUMS_NEWS_ID, res.thread.title, forumPost.message);
|
|
|
- post.forumsNewsPostId = newThread.thread.thread_id;
|
|
|
- } else {
|
|
|
- const curThread = await client.forum.editThread(post.forumsNewsPostId, {
|
|
|
- title: res.thread.title
|
|
|
- });
|
|
|
-
|
|
|
- await client.forum.editPost(curThread.thread.first_post_id, {
|
|
|
- message: forumPost.message
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- await client.forum.deleteThread(post.forumsEditPostId);
|
|
|
- await repo.update({ newsId: post.newsId, feedName: post.feedName }, {
|
|
|
- editMessageId: undefined,
|
|
|
- forumsEditPostId: undefined,
|
|
|
- forumsNewsPostId: post.forumsNewsPostId });
|
|
|
- await reaction.message.delete();
|
|
|
- delete this.verifyMessageIdToPost[m.id];
|
|
|
- };
|
|
|
-
|
|
|
async deleteCacheMessage(messageId: string): Promise<void> {
|
|
|
if(!this.aggregateChannelID)
|
|
|
return;
|
|
@@ -221,20 +122,15 @@ export class NewsAggregator {
|
|
|
if (!(ch instanceof TextChannel))
|
|
|
return;
|
|
|
|
|
|
- const msg = await this.tryFetchMessage(ch, messageId);
|
|
|
+ const msg = await tryDo(ch.messages.fetch(messageId));
|
|
|
|
|
|
- if (msg)
|
|
|
- await msg.delete();
|
|
|
- }
|
|
|
-
|
|
|
- async tryFetchMessage(channel: Channel, messageId: string): Promise<Message | null> {
|
|
|
- try {
|
|
|
- if (!(channel instanceof TextChannel))
|
|
|
- return null;
|
|
|
- return await channel.messages.fetch(messageId);
|
|
|
- } catch (error) {
|
|
|
- return null;
|
|
|
- }
|
|
|
+ if (msg.ok) {
|
|
|
+ const deleteResult = await tryDo(msg.result.delete());
|
|
|
+ if (!deleteResult.ok)
|
|
|
+ logger.error("NewsAggregator: failed to delete message %s: %s", messageId, deleteResult.error);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ logger.error("NewsAggregator: failed to fetch messate %s: %s", this.aggregateChannelID, msg.error);
|
|
|
}
|
|
|
|
|
|
initAggregators(): void {
|
|
@@ -261,35 +157,6 @@ export class NewsAggregator {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async initPendingReactors(): Promise<void> {
|
|
|
- if(!this.aggregateChannelID)
|
|
|
- return;
|
|
|
-
|
|
|
- const verifyChannel = client.bot.channels.resolve(this.aggregateChannelID);
|
|
|
- if(!verifyChannel)
|
|
|
- throw new Error("Couldn't find verify channel!");
|
|
|
-
|
|
|
- const repo = getRepository(AggroNewsItem);
|
|
|
-
|
|
|
- const pendingVerifyMessages = await repo.find({
|
|
|
- where: { editMessageId: Not(IsNull()) }
|
|
|
- });
|
|
|
-
|
|
|
- for (const msg of pendingVerifyMessages) {
|
|
|
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
|
- const m = await this.tryFetchMessage(verifyChannel, msg.editMessageId!);
|
|
|
-
|
|
|
- if (!m) {
|
|
|
- await repo.update({ feedName: msg.feedName, newsId: msg.newsId }, { editMessageId: undefined });
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- const collector = m.createReactionCollector(this.isVerifyReaction, { maxEmojis: 1 });
|
|
|
- collector.on("collect", this.collectorFor(collector));
|
|
|
- this.reactionCollectors[m.id] = collector;
|
|
|
- this.verifyMessageIdToPost[m.id] = msg;
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
async start(): Promise<void> {
|
|
|
const repo = getRepository(KnownChannel);
|
|
@@ -303,7 +170,6 @@ export class NewsAggregator {
|
|
|
|
|
|
this.aggregateChannelID = ch.channelId;
|
|
|
|
|
|
- await this.initPendingReactors();
|
|
|
this.initAggregators();
|
|
|
interval(this.checkFeeds, UPDATE_INTERVAL * 60 * 1000);
|
|
|
}
|