Browse Source

Switch to strict mode

ghorsington 3 years ago
parent
commit
2a809fd9ac

+ 3 - 0
bot/package.json

@@ -35,6 +35,7 @@
       "@types/request-promise-native": "^1.0.17",
       "@types/sha1": "^1.1.2",
       "@types/turndown": "^5.0.0",
+      "@types/ws": "^7.2.4",
       "@types/xml2js": "^0.4.5",
       "axios": "^0.19.2",
       "cheerio": "^1.0.0-rc.3",
@@ -68,6 +69,8 @@
    },
    "devDependencies": {
       "@types/node": "^14.0.5",
+      "@typescript-eslint/eslint-plugin": "^3.0.2",
+      "@typescript-eslint/parser": "^3.0.2",
       "eslint": "^7.1.0",
       "nodemon": "^2.0.4",
       "ts-node": "8.10.2"

+ 4 - 4
bot/src/bbcode-parser/bbcode-js.ts

@@ -70,7 +70,7 @@ export var extractQuotedText = function (value: string, parts?: string[]) {
                 while (parts && parts.length) {
                     nextPart = parts.shift();
                     value += " " + nextPart;
-                    if (nextPart[nextPart.length - 1] === quote) {
+                    if (nextPart![nextPart!.length - 1] === quote) {
                         break;
                     }
                 }
@@ -82,7 +82,7 @@ export var extractQuotedText = function (value: string, parts?: string[]) {
     return [value, parts];
 };
 
-export var parseParams = function (tagName: string, params: string) {
+export var parseParams = function (tagName: string, params?: string) {
     let paramMap: Dict<string> = {};
 
     if (!params) {
@@ -94,7 +94,7 @@ export var parseParams = function (tagName: string, params: string) {
     let parts = params.split(/\s+/);
 
     while (parts.length) {
-        let part = parts.shift();
+        let part = parts.shift() ?? "";
         // check if the param itself is a valid url
         if (!URL_PATTERN.exec(part)) {
             let index = part.indexOf('=');
@@ -119,7 +119,7 @@ export var parseParams = function (tagName: string, params: string) {
 
 const BBCODE_PATTERN = [{ e: '\\[(\\w+)(?:[= ]([^\\]]+))?]((?:.|[\r\n])*?)\\[/\\1]', func: tagReplace }];
 
-function tagReplace(options: BBCodeConfig, fullMatch: string, tag: string, params: string, value: string) {
+function tagReplace(options: BBCodeConfig, fullMatch: string, tag: string, params: string | undefined, value: string) {
     let val: string;
     tag = tag.toLowerCase();
     let paramsObj = parseParams(tag, params || undefined);

+ 11 - 3
bot/src/client.ts

@@ -1,7 +1,15 @@
-import { Client } from "discord.js";
+import { Client, ClientUser } from "discord.js";
 import { XenforoClient } from "./xenforo";
 
 export const FORUMS_DOMAIN = "https://custommaid3d2.com";
-export const client = new Client();
-export const forumClient = new XenforoClient(`${FORUMS_DOMAIN}/api`, process.env.FORUM_API_KEY);
 
+export class BotClient {
+    public bot = new Client();
+    public forum = new XenforoClient(`${FORUMS_DOMAIN}/api`, process.env.FORUM_API_KEY ?? "");
+
+    get botUser(): ClientUser {
+        return this.bot.user!;
+    }
+}
+
+export const client = new BotClient();

+ 2 - 2
bot/src/commands/aggregators/kiss_diary.ts

@@ -44,7 +44,7 @@ async function aggregate() {
         for(let {table, content} of items.get() as {table: CheerioElement, content: CheerioElement}[]) {
             let a = cheerio(table).find("a");
             let link = a.attr("href");
-            let matches = urlPattern.exec(link);
+            let matches = link ? urlPattern.exec(link) : false;
             if(!matches)
                 continue;
             
@@ -68,7 +68,7 @@ async function aggregate() {
                 link: diaryLink,
                 title: title,
                 author: "KISS BLOG",
-                contents: contentCh.html(),
+                contents: contentCh.html() ?? "",
                 embedColor: 0xf4c100,
                 needsTranslation: true
             });

+ 19 - 14
bot/src/commands/facemorph.ts

@@ -35,9 +35,11 @@ export class Facemorph {
     morphFaces = async (faces: cv.Rect[], data: Buffer) => {
         let padoru = Math.random() <= this.getPadoruChance();
         let jimpImage = await Jimp.read(data);
+        let emoteGuild = client.bot.guilds.resolve(EMOTE_GUILD);
+        if (!emoteGuild)
+            return jimpImage;
         let emojiKeys = process.env.FOOLS != "TRUE" ? [
-            ...client.guilds
-                .resolve(EMOTE_GUILD)
+            ...emoteGuild
                 .emojis.cache.filter(e => !e.animated && e.name.startsWith("PADORU") == padoru)
                 .keys()
         ]: 
@@ -54,7 +56,7 @@ export class Facemorph {
             let dx = rect.x + rect.width / 2;
             let dy = rect.y + rect.height / 2;
             let emojiKey = emojiKeys[Math.floor(Math.random() * emojiKeys.length)];
-            let emoji = client.emojis.resolve(emojiKey);
+            let emoji = client.bot.emojis.resolve(emojiKey)!;
             let emojiImage = await Jimp.read(emoji.url);
             let ew = emojiImage.getWidth();
             let eh = emojiImage.getHeight();
@@ -111,8 +113,11 @@ export class Facemorph {
             const titles = ["They are horse", "Neigh!", "Insert carrots into them!", "They will become horse!", "They will serve Geoffrey!", "tfw no carrots"];
             text = titles[Math.floor(Math.random() * titles.length)];
         }
-        else
-            text = `${(await this.getRandomCaption(FaceCaptionType.PREFIX)).message} ${(await this.getRandomCaption(FaceCaptionType.POSTFIX)).message}`;
+        else {
+            let prefixMessage = (await this.getRandomCaption(FaceCaptionType.PREFIX))?.message ?? "Feed them";
+            let postfixMessage = (await this.getRandomCaption(FaceCaptionType.POSTFIX)) ?? "carrots";
+            text = `${prefixMessage} ${postfixMessage}`;
+        }
         let h = Jimp.measureTextHeight(font, text, targetSize - CAPTION_OFFSET * 2);
         let finalImage = await Jimp.create(targetSize, targetSize + h + CAPTION_OFFSET * 2, padoru ? "#FD2027" : "#FFFFFF");
 
@@ -188,10 +193,10 @@ export class Facemorph {
             }
         }
 
-        let jimpImage;
+        let jimpImage: Jimp;
         if (processor)
-            jimpImage = await processor(faces, data);
-        else {
+                jimpImage = await processor(faces, data);
+            else {
             if (Math.random() <= CAPTION_PROBABILITY)
                 jimpImage = await this.captionFace(faces, data);
             else
@@ -202,7 +207,7 @@ export class Facemorph {
         let buffer = await jimpImage.getBufferAsync(Jimp.MIME_JPEG);
         let messageContents =
             successMessage ||
-            `I noticed a face in the image. I think this looks better ${client.emojis.resolve("505076258753740810").toString()}`;
+            `I noticed a face in the image. I think this looks better ${client.bot.emojis.resolve("505076258753740810")?.toString() ?? ":)"}`;
 
         message.channel.send(messageContents, {
             files: [buffer]
@@ -210,16 +215,16 @@ export class Facemorph {
     }
 
     processLastImage(msg: Message, processor: ImageProcessor) {
-        let lastImagedMessage = msg.channel.messages.cache.filter(m => !m.author.bot && m.attachments.find(v => isValidImage(v.name) !== undefined) != undefined).last();
+        let lastImagedMessage = msg.channel.messages.cache.filter(m => !m.author.bot && m.attachments.find(v => isValidImage(v.name)) != undefined).last();
 
         if (!lastImagedMessage) {
             msg.channel.send(`${msg.author.toString()} Sorry, I couldn't find any recent messages with images.`);
             return;
         }
 
-        let image = lastImagedMessage.attachments.find(v => isValidImage(v.name));
+        let image = lastImagedMessage.attachments.find(v => isValidImage(v.name))!;
 
-        let replyEmoji = client.emojis.resolve("505076258753740810");
+        let replyEmoji = client.bot.emojis.resolve("505076258753740810");
         let emojiText = replyEmoji ? replyEmoji.toString() : "Jiiii~";
 
         this.processFaceSwap(
@@ -235,7 +240,7 @@ export class Facemorph {
     async morphRandomImage(actionsDone: boolean, msg: Message, contests: string) {
         if (actionsDone) return false;
 
-        if (msg.mentions.users.size > 0 && msg.mentions.users.first().id == client.user.id)
+        if (msg.mentions.users.size > 0 && msg.mentions.users.first()?.id == client.botUser.id)
             return false;
 
         let imageAttachment = msg.attachments.find(v => isValidImage(v.name));
@@ -282,7 +287,7 @@ export class Facemorph {
         else
             processor = this.morphFaces;
 
-        let replyEmoji = client.emojis.resolve("505076258753740810");
+        let replyEmoji = client.bot.emojis.resolve("505076258753740810");
         let emojiText = replyEmoji ? replyEmoji.toString() : "Jiiii~";
 
         this.processFaceSwap(

+ 1 - 1
bot/src/commands/file_only_channel_checker.ts

@@ -27,7 +27,7 @@ export class FileOnlyChannelChecker {
 
         msg.delete();
 
-        let ch = msg.guild.channels.resolve(entry.warningMessageChannelId);
+        let ch = msg.guild?.channels.resolve(entry.warningMessageChannelId);
         
         if(ch instanceof TextChannel)
             ch.send(`> ${msg.content.replace(/\n/g, "\n> ")}\n\n${msg.author.toString()} Channel ${msg.channel.toString()} is only meant for sharing links and files! If you want to post text-only messages, do so in ${ch.toString()}!`);

+ 46 - 22
bot/src/commands/forums_news_checker.ts

@@ -1,6 +1,6 @@
 import TurndownService, { Options } from "turndown";
 import interval from "interval-promise";
-import { client, forumClient, FORUMS_DOMAIN } from "../client";
+import { client, FORUMS_DOMAIN } from "../client";
 import sha1 from "sha1";
 import { TextChannel, Message, ReactionCollector, MessageReaction, Collector, User, Channel } from "discord.js";
 import { Dict } from "../util";
@@ -22,7 +22,7 @@ export class ForumsNewsChecker {
 
     verifyMessageIdToPost: Dict<string> = {}
     reactionCollectors: Dict<ReactionCollector> = {};
-    verifyChannelId: string = null;
+    verifyChannelId?: string;
     botUserId = 0;
     turndown = new TurndownService();
 
@@ -33,7 +33,7 @@ export class ForumsNewsChecker {
         });
         this.turndown.addRule("link", {
             filter: (node: HTMLElement, opts: Options) => node.nodeName === "A" && node.getAttribute("href") != null,
-            replacement: (content: string, node: HTMLElement) => node.getAttribute("href")
+            replacement: (content: string, node: Node) => (node instanceof HTMLElement ? node.getAttribute("href") : null) ?? ""
         });
     }
 
@@ -50,10 +50,10 @@ export class ForumsNewsChecker {
             let forumsNewsRepo = getRepository(PostedForumNewsItem);
             let postVerifyMessageRepo = getRepository(PostVerifyMessage);
 
-            let forumThreads = await forumClient.getForumThreads(NEWS_FORUM_ID);
+            let forumThreads = await client.forum.getForumThreads(NEWS_FORUM_ID);
 
             for (let thread of [...forumThreads.threads, ...forumThreads.sticky]) {
-                let firstPost = await forumClient.getPost(thread.first_post_id);
+                let firstPost = await client.forum.getPost(thread.first_post_id);
 
                 let contents = this.bbCodeToMarkdown(firstPost.message);
                 let itemObj = forumsNewsRepo.create({
@@ -91,14 +91,15 @@ export class ForumsNewsChecker {
                             postItem.verifyMessage = itemObj.verifyMessage;
 
                         itemObj = postItem;
-                        itemObj.verifyMessage.isNew = false;
+                        if (itemObj.verifyMessage)
+                            itemObj.verifyMessage.isNew = false;
                         itemObj.hash = newHash;
                     }
                     else
                         continue;
                 }
 
-                if (!this.shouldVerify() || firstPost.user_id == this.botUserId)
+                if (!this.verifyChannelId || firstPost.user_id == this.botUserId)
                     await this.sendNews(itemObj);
                 else
                     await this.addVerifyMessage(itemObj);
@@ -109,7 +110,13 @@ export class ForumsNewsChecker {
     }
 
     async initPendingReactors() {
-        let verifyChannel = client.channels.resolve(this.verifyChannelId);
+        if (!this.verifyChannelId)
+            return;
+
+        let verifyChannel = client.bot.channels.resolve(this.verifyChannelId);
+
+        if (!verifyChannel)
+            return;
 
         let repo = getRepository(PostedForumNewsItem);
         let verifyMessageRepo = getRepository(PostVerifyMessage);
@@ -121,11 +128,11 @@ export class ForumsNewsChecker {
         });
 
         for (let msg of pendingVerifyMessages) {
-            let m = await this.tryFetchMessage(verifyChannel, msg.verifyMessage.messageId);
+            let m = await this.tryFetchMessage(verifyChannel, msg.verifyMessage!.messageId);
 
             if (!m) {
-                await repo.update({ id: msg.id }, { verifyMessage: null });
-                await verifyMessageRepo.delete(msg.verifyMessage);
+                await repo.update({ id: msg.id }, { verifyMessage: undefined });
+                await verifyMessageRepo.delete(msg.verifyMessage!);
                 continue;
             }
 
@@ -137,7 +144,12 @@ export class ForumsNewsChecker {
     }
 
     async addVerifyMessage(item: PostedForumNewsItem) {
-        let verifyChannel = client.channels.resolve(this.verifyChannelId) as TextChannel;
+        if (!this.verifyChannelId)
+            return;
+        if (!item.verifyMessage)
+            throw new Error("No verify message! This shouldn't happen!");
+
+        let verifyChannel = client.bot.channels.resolve(this.verifyChannelId) as TextChannel;
 
         if (!verifyChannel) {
             console.log(`Skipping adding item ${item.id} because no verify channel is set up!`);
@@ -182,8 +194,12 @@ export class ForumsNewsChecker {
             relations: ["verifyMessage"]
         });
 
-        await postRepo.update({ id: post.id }, { verifyMessage: null });
-        await verifyMessageRepo.delete({ id: post.verifyMessage.id });
+        if (!post) 
+            throw new Error("Post not found!");
+
+        await postRepo.update({ id: post.id }, { verifyMessage: undefined });
+        if (post.verifyMessage)
+            await verifyMessageRepo.delete({ id: post.verifyMessage.id });
         await reaction.message.delete();
 
         if (reaction.emoji.name == "✅")
@@ -200,10 +216,13 @@ export class ForumsNewsChecker {
             where: { channelType: NEWS_FEED_CHANNEL }
         });
 
+        if (!outChannel)
+            return;
+
         let sentMessage = await this.postNewsItem(outChannel.channelId, item);
 
-        item.postedMessageId = sentMessage.id;
-        item.verifyMessage = null;
+        item.postedMessageId = sentMessage?.id;
+        item.verifyMessage = undefined;
 
         await newsPostRepo.save(item);
     }
@@ -212,7 +231,9 @@ export class ForumsNewsChecker {
         return (reaction.emoji.name == "✅" || reaction.emoji.name == "❌") && !user.bot;
     }
 
-    async tryFetchMessage(channel: Channel, messageId: string) {
+    async tryFetchMessage(channel?: Channel, messageId?: string) {
+        if(!channel || !messageId)
+            return null;
         try {
             if (!(channel instanceof TextChannel))
                 return null;
@@ -222,13 +243,16 @@ export class ForumsNewsChecker {
         }
     }
 
-    shouldVerify() {
-        return this.verifyChannelId != null;
+    get shouldVerify() {
+        return this.verifyChannelId != undefined;
     }
 
     async postNewsItem(channel: string, item: PostedForumNewsItem): Promise<Message | null> {
+        if (!item.verifyMessage)
+            throw new Error("No message to send!");
+
         let newsMessage = this.toNewsString(item.verifyMessage);
-        let ch = client.channels.resolve(channel);
+        let ch = client.bot.channels.resolve(channel);
 
         if (!(ch instanceof TextChannel))
             return null;
@@ -284,7 +308,7 @@ React with ✅ (approve) or ❌ (deny).`;
             return;
         }
 
-        let editMsg = await this.tryFetchMessage(client.channels.resolve(this.verifyChannelId), post.verifyMessage.messageId);
+        let editMsg = await this.tryFetchMessage(client.bot.channels.resolve(this.verifyChannelId) ?? undefined, post.verifyMessage.messageId);
 
         if (!editMsg) {
             msg.channel.send(`${msg.author.toString()} No verify message found for ${id}! This is a bug: report to horse.`);
@@ -310,7 +334,7 @@ React with ✅ (approve) or ❌ (deny).`;
 
         this.verifyChannelId = verifyChannel.channelId;
 
-        let user = await forumClient.getMe();
+        let user = await client.forum.getMe();
         this.botUserId = user.user_id;
 
         await this.initPendingReactors();

+ 47 - 27
bot/src/commands/news_aggregator.ts

@@ -1,6 +1,6 @@
 import TurndownService, { Options } from "turndown";
 import interval from "interval-promise";
-import { client, forumClient, FORUMS_DOMAIN } from "../client";
+import { client, FORUMS_DOMAIN, BotClient } from "../client";
 import sha1 from "sha1";
 import * as path from "path";
 import * as fs from "fs";
@@ -25,7 +25,7 @@ const FORUMS_NEWS_ID = 49;
 export class NewsAggregator {
     tlClient = new TranslationServiceClient();
     aggregators: IAggregator[] = [];
-    aggregateChannelID: string = null;
+    aggregateChannelID?: string;
     bbCodeParser = new HTML2BBCode();
     turndown = new TurndownService();
     reactionCollectors: Dict<ReactionCollector> = {};
@@ -38,7 +38,7 @@ export class NewsAggregator {
         });
         this.turndown.addRule("link", {
             filter: (node: HTMLElement, opts: Options) => node.nodeName === "A" && node.getAttribute("href") != null,
-            replacement: (content: string, node: HTMLElement) => node.getAttribute("href")
+            replacement: (content: string, node: Node) => (node instanceof HTMLElement ? node.getAttribute("href") : null) ?? ""
         });
     }
 
@@ -56,8 +56,8 @@ export class NewsAggregator {
             for (let item of itemSet) {
                 let itemObj = {
                     ...item,
-                    cacheMessageId: null,
-                    postedMessageId: null
+                    cacheMessageId: undefined,
+                    postedMessageId: undefined
                 } as NewsPostItem;
 
                 itemObj.hash = sha1(itemObj.contents);
@@ -75,9 +75,12 @@ export class NewsAggregator {
     }
 
     async addNewsItem(item: NewsPostItem) {
+        if (!this.aggregateChannelID)
+            return;
+
         let repo = getRepository(AggroNewsItem);
 
-        let ch = client.channels.resolve(this.aggregateChannelID);
+        let ch = client.bot.channels.resolve(this.aggregateChannelID);
 
         if (!(ch instanceof TextChannel))
             return;
@@ -89,7 +92,7 @@ export class NewsAggregator {
 
         if (newsItem) {
             if (process.env.IGNORE_CHANGED_NEWS === "TRUE") {
-                newsItem.hash = item.hash;
+                newsItem.hash = item.hash ?? "";
                 await repo.save(newsItem);
                 return;
             }
@@ -97,7 +100,7 @@ export class NewsAggregator {
             // No changes, skip
             if (newsItem.hash == item.hash)
                 return;
-            else
+            else if(newsItem.editMessageId)
                 await this.deleteCacheMessage(newsItem.editMessageId);
             isNew = false;
         } else {
@@ -108,7 +111,7 @@ export class NewsAggregator {
             });
         }
 
-        if (item.needsTranslation)
+        if (item.needsTranslation && process.env.GOOGLE_APP_ID)
             try {
                 let request = {
                     parent: this.tlClient.locationPath(process.env.GOOGLE_APP_ID, "global"),
@@ -119,9 +122,12 @@ export class NewsAggregator {
                 };
 
                 let [res] = await this.tlClient.translateText(request);
-
-                item.title = res.translations[0].translatedText
-                item.contents = res.translations[1].translatedText;
+                let [translatedTitle, translatedContents] = res.translations ?? [undefined, undefined];
+                
+                if (translatedTitle?.translatedText && translatedContents?.translatedText) {
+                    item.title = translatedTitle.translatedText;
+                    item.contents = translatedContents.translatedText;
+                }
             } catch (err) {
                 console.log(`Failed to translate because ${err}`);
             }
@@ -129,10 +135,10 @@ export class NewsAggregator {
         item.contents = this.bbCodeParser.feed(item.contents).toString();
 
         if (!newsItem.forumsEditPostId) {
-            let createResponse = await forumClient.createThread(FORUMS_STAGING_ID, item.title, item.contents);
+            let createResponse = await client.forum.createThread(FORUMS_STAGING_ID, item.title, item.contents);
             newsItem.forumsEditPostId = createResponse.thread.thread_id;
-        } else {
-            await forumClient.postReply(newsItem.forumsNewsPostId, item.contents);
+        } else if(newsItem.forumsNewsPostId){
+            await client.forum.postReply(newsItem.forumsNewsPostId, item.contents);
         }
 
 
@@ -164,7 +170,7 @@ export class NewsAggregator {
     }
 
     isVerifyReaction(reaction: MessageReaction, user: User) {
-        return (reaction.emoji.name == "✅" || reaction.emoji.name == "❌") && !user.bot && user.id != client.user.id;
+        return (reaction.emoji.name == "✅" || reaction.emoji.name == "❌") && !user.bot && user.id != client.botUser.id;
     }
 
     collectReaction = async (reaction: MessageReaction, collector: Collector<string, MessageReaction>) => {
@@ -175,32 +181,41 @@ export class NewsAggregator {
         delete this.reactionCollectors[m.id];
         let post = this.verifyMessageIdToPost[m.id];
 
+        if(!post.forumsEditPostId) {
+            throw new Error("No forum edit post found!");
+        }
+
         if (reaction.emoji.name == "✅") {
-            let res = await forumClient.getThread(post.forumsEditPostId);
-            let forumPost = await forumClient.getPost(res.thread.first_post_id);
+            let res = await client.forum.getThread(post.forumsEditPostId);
+            let forumPost = await client.forum.getPost(res.thread.first_post_id);
 
             if (!post.forumsNewsPostId) {
-                let newThread = await forumClient.createThread(FORUMS_NEWS_ID, res.thread.title, forumPost.message);
+                let newThread = await client.forum.createThread(FORUMS_NEWS_ID, res.thread.title, forumPost.message);
                 post.forumsNewsPostId = newThread.thread.thread_id;
             } else {
-                let curThread = await forumClient.editThread(post.forumsNewsPostId, {
+                let curThread = await client.forum.editThread(post.forumsNewsPostId, {
                     title: res.thread.title
                 });
 
-                await forumClient.editPost(curThread.thread.first_post_id, {
+                await client.forum.editPost(curThread.thread.first_post_id, {
                     message: forumPost.message
                 });
             }
         }
 
-        await forumClient.deleteThread(post.forumsEditPostId);
-        await repo.update({ newsId: post.newsId, feedName: post.feedName }, { editMessageId: null, forumsEditPostId: null, forumsNewsPostId: post.forumsNewsPostId });
+        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) {
-        let ch = client.channels.resolve(this.aggregateChannelID);
+        if(!this.aggregateChannelID)
+            return;
+        let ch = client.bot.channels.resolve(this.aggregateChannelID);
         if (!(ch instanceof TextChannel))
             return;
 
@@ -244,7 +259,12 @@ export class NewsAggregator {
     }
 
     async initPendingReactors() {
-        let verifyChannel = client.channels.resolve(this.aggregateChannelID);
+        if(!this.aggregateChannelID)
+            return;
+
+        let verifyChannel = client.bot.channels.resolve(this.aggregateChannelID);
+        if(!verifyChannel)
+            throw new Error("Couldn't find verify channel!");
 
         let repo = getRepository(AggroNewsItem);
 
@@ -253,10 +273,10 @@ export class NewsAggregator {
         });
 
         for (let msg of pendingVerifyMessages) {
-            let m = await this.tryFetchMessage(verifyChannel, msg.editMessageId);
+            let m = await this.tryFetchMessage(verifyChannel, msg.editMessageId!);
 
             if (!m) {
-                await repo.update({ feedName: msg.feedName, newsId: msg.newsId }, { editMessageId: null });
+                await repo.update({ feedName: msg.feedName, newsId: msg.newsId }, { editMessageId: undefined });
                 continue;
             }
 

+ 1 - 1
bot/src/commands/random_react.ts

@@ -20,7 +20,7 @@ export class RandomReact {
         if(!reactInfo)
             return false;
 
-        let emote = client.emojis.resolve(reactInfo.reactionEmoteId);
+        let emote = client.bot.emojis.resolve(reactInfo.reactionEmoteId);
 
         if(!emote)
             return false;

+ 3 - 0
bot/src/commands/rcg.ts

@@ -16,6 +16,9 @@ export class Rcg {
     
         let regexResult = rcgRe.exec(result);
 
+        if(!regexResult)
+            return;
+
         msg.channel.send(`${msg.author.toString()} I find this very funny:`, {
             files: [ regexResult[1].trim() ]
         });

+ 10 - 5
bot/src/commands/react.ts

@@ -38,7 +38,7 @@ export class ReactCommands {
             let reactable = contents[1].trim().toLowerCase();
             let reactionEmoji = contents[2];
 
-            if (!client.emojis.cache.has(reactionEmoji)) {
+            if (!client.bot.emojis.cache.has(reactionEmoji)) {
                 msg.channel.send(`${msg.author.toString()} I cannot react with this emoji :(`);
                 return;
             }
@@ -96,7 +96,9 @@ export class ReactCommands {
         let message = await reactionRepo.findOne({ message: lowerContent });
 
         if (message) {
-            msg.react(client.emojis.resolve(message.reactionEmoteId));
+            let emoji = client.bot.emojis.resolve(message.reactionEmoteId);
+            if (emoji)
+                msg.react(emoji);
             return true;
         }
 
@@ -127,8 +129,11 @@ export class ReactCommands {
         if (randomEmotes.length == 0)
             return false;
 
-        for (let emote of randomEmotes)
-            await msg.react(client.emojis.cache.find(e => e.id == emote.reactionId));
+        for (let emote of randomEmotes) {
+            let emoji = client.bot.emojis.resolve(emote.reactionId);
+            if(emoji)
+                await msg.react(emoji);
+        }
 
         return true;
     }
@@ -159,7 +164,7 @@ export class ReactCommands {
         if (emotes.length != 1)
             return false;
 
-        let emote = client.emojis.cache.find(e => e.id == emotes[0].reactionId);
+        let emote = client.bot.emojis.resolve(emotes[0].reactionId);
 
         if (!emote) {
             console.log(`WARNING: Emote ${emotes[0]} no longer is valid. Deleting invalid emojis from the list...`);

+ 12 - 12
bot/src/main.ts

@@ -43,12 +43,12 @@ async function trigger(type: mCmd.ActionType, ...params: any[]) {
 
 let commandSets: mCmd.ICommand[] = [];
 let botCommands: mCmd.IBotCommand[] = [];
-let botEvents: { [event in mCmd.ActionType]?: mCmd.BotAction[] } = getNumberEnums(mCmd.ActionType).reduce((p, c) => { p[c] = []; return p; }, {} as any);
+let botEvents: { [event in mCmd.ActionType]: mCmd.BotAction[] } = getNumberEnums(mCmd.ActionType).reduce((p, c) => { p[c] = []; return p; }, {} as any);
 let startActions: Array<() => void | Promise<void>> = [];
 
-client.on("ready", async () => {
+client.bot.on('ready', async () => {
     console.log("Starting up NoctBot!");
-    client.user.setActivity(process.env.NODE_ENV == "dev" ? "Maintenance" : "@NoctBot help", {
+    client.botUser.setActivity(process.env.NODE_ENV == "dev" ? "Maintenance" : "@NoctBot help", {
         type: "PLAYING"
     });
     for (let i = 0; i < startActions.length; i++) {
@@ -60,8 +60,8 @@ client.on("ready", async () => {
     console.log("NoctBot is ready!");
 });
 
-client.on("message", async m => {
-    if (m.author.id == client.user.id)
+client.bot.on("message", async m => {
+    if (m.author.id == client.botUser.id)
         return;
 
     if(process.env.FOOLS == "TRUE" && (m.channel.id == "297109482905796608" || m.channel.id == "429295461099110402") && Math.random() <= 0.01) {
@@ -75,10 +75,10 @@ client.on("message", async m => {
     if (await trigger(mCmd.ActionType.MESSAGE, m, content))
         return;
 
-    if (m.mentions.users.size > 0 && m.mentions.users.has(client.user.id)) {
+    if (m.mentions.users.size > 0 && m.mentions.users.has(client.botUser.id)) {
 
-        if (m.content.trim().startsWith(`<@${client.user.id}>`) || m.content.trim().startsWith(`<@!${client.user.id}>`)) {
-            content = content.substring(`@${client.user.username}`.length).trim();
+        if (m.content.trim().startsWith(client.botUser.id) || m.content.trim().startsWith(client.botUser.discriminator)) {
+            content = content.substring(`@${client.botUser.username}`.length).trim();
 
             let lowerCaseContent = content.toLowerCase();
             for (let c of botCommands) {
@@ -106,7 +106,7 @@ client.on("message", async m => {
     await trigger(mCmd.ActionType.POST_MESSAGE);
 });
 
-client.on("messageReactionAdd", (r, u) => {
+client.bot.on("messageReactionAdd", (r, u) => {
     if (Math.random() <= REACT_PROBABILITY && !u.bot) {
         console.log(`Reacting to message ${r.message.id} because user ${u.tag} reacted to it`);
         r.message.react(r.emoji);
@@ -142,8 +142,8 @@ function loadCommand(mod: any) {
 export function getDocumentation() {
     return botCommands.filter(m => m.documentation !== undefined).map(m => ({
         name: m.pattern.toString(),
-        doc: m.documentation.description,
-        example: m.documentation.example,
+        doc: m.documentation?.description,
+        example: m.documentation?.example,
         auth: m.auth || false
     }));
 }
@@ -165,7 +165,7 @@ async function main() {
         loadCommand(require(path.resolve(commandsPath, file)));
     }
 
-    client.login(process.env.BOT_TOKEN);
+    client.bot.login(process.env.BOT_TOKEN);
 }
 
 main();

+ 4 - 21
bot/src/util.ts

@@ -13,7 +13,9 @@ export function isDevelopment() {
     return process.env.NODE_ENV == "dev";
 }
 
-export function isValidImage(fileName: string) {
+export function isValidImage(fileName?: string) {
+    if (!fileName)
+        return false;
     let extPosition = fileName.lastIndexOf(".");
     if (extPosition < 0)
         return false;
@@ -21,7 +23,7 @@ export function isValidImage(fileName: string) {
     return VALID_EXTENSIONS.has(ext);
 }
 
-export async function isAuthorisedAsync(member: GuildMember) {
+export async function isAuthorisedAsync(member: GuildMember | null | undefined) {
     if (!member)
         return false;
 
@@ -62,25 +64,6 @@ export function compareNumbers<T>(prop: (o: T) => number) {
 
 export type Dict<TVal> = { [key: string]: TVal };
 
-export class NeighBuilder {
-    private data: any[];
-
-    constructor() { }
-
-    append(...data: any[]) {
-        this.data.concat(data);
-    }
-
-    appendLine(...data: any[]) {
-        this.append(data);
-        this.data.push("\n");
-    }
-
-    toString() {
-        return this.data.reduce((prev, cur) => cur + `${prev}`, "");
-    }
-}
-
 export function getNumberEnums(e: any): number[] {
     return Object.keys(e).filter(k => typeof e[k as any] === "number").map(k => e[k as any]);
 }

+ 2 - 1
bot/tsconfig.json

@@ -15,7 +15,8 @@
         "baseUrl": ".",
         "paths": {
             "@shared/*": ["../shared/src/*"]
-        }
+        },
+        "strict": true
     },
     "references": [
         {