Browse Source

Refactor reactions to use typeor; refactor async

ghorsington 5 years ago
parent
commit
9c17f64ffb
4 changed files with 125 additions and 76 deletions
  1. 5 5
      src/commands/command.ts
  2. 102 55
      src/commands/react.ts
  3. 2 2
      src/entity/MessageReaction.ts
  4. 16 14
      src/main.ts

+ 5 - 5
src/commands/command.ts

@@ -1,6 +1,6 @@
 import { Message } from "discord.js";
 
-export type BotEvent = (actionsDone: boolean, ...params: any[]) => boolean;
+export type BotEvent = (actionsDone: boolean, ...params: any[]) => boolean | Promise<boolean>;
 
 export interface IDocumentation {
     auth: boolean;
@@ -19,9 +19,9 @@ export interface IBotCommand {
 export interface ICommand {
     commands?: Array<IBotCommand>;
     documentation?: DocumentationSet;
-    onMessage?(actionsDone: boolean, m : Message, content: string) : boolean;
-    onIndirectMention?(actionsDone: boolean, m: Message) : boolean;
-    onDirectMention?(actionsDone: boolean, m: Message, content: string) : boolean;
-    postMessage?(actionsDone: boolean, m: Message) : boolean;
+    onMessage?(actionsDone: boolean, m : Message, content: string) : boolean | Promise<boolean>;
+    onIndirectMention?(actionsDone: boolean, m: Message) : boolean | Promise<boolean>;
+    onDirectMention?(actionsDone: boolean, m: Message, content: string) : boolean | Promise<boolean>;
+    postMessage?(actionsDone: boolean, m: Message) : boolean | Promise<boolean>;
     onStart?(): void;
 };

+ 102 - 55
src/commands/react.ts

@@ -1,49 +1,84 @@
-import { db, IRandomElementMixin } from "../db";
 import { isAuthorised } from "../util";
 import client from "../client";
 import { ICommand } from "./command";
-import { ObjectChain, CollectionChain } from "lodash";
+import { getRepository } from "typeorm";
+import { MessageReaction } from "../entity/MessageReaction";
+import { KnownUser } from "../entity/KnownUser";
+import { ReactionType, ReactionEmote } from "../entity/ReactionEmote";
 
 const pattern = /^react to\s+"([^"]+)"\s+with\s+\<:[^:]+:([^\>]+)\>$/i;
 
+async function getRandomEmotes(allowedTypes: ReactionType[]) {
+    let reactionEmotesRepo = getRepository(ReactionEmote);
+
+    return await reactionEmotesRepo.query(`
+        select reactionId 
+        from (  select type, reactionId
+                from reaction_emote
+                where type in (?)
+                order by type, random() )
+        group by type`, [ allowedTypes ]) as string[];
+}
+
 export default {
     commands: [
         {
             pattern: "react to",
-            action: (msg, s) => {
-                if (!isAuthorised(msg.member)) return;
+            action: async (msg, s) => {
+                if (!isAuthorised(msg.member))
+                    return;
                 let contents = pattern.exec(s);
+
                 if (contents != null) {
                     let reactable = contents[1].trim().toLowerCase();
                     let reactionEmoji = contents[2];
+
                     if (!client.emojis.has(reactionEmoji)) {
                         msg.channel.send(`${msg.author.toString()} I cannot react with this emoji :(`);
                         return;
                     }
-                    db.get("messageReactions").set(reactable, reactionEmoji);
-                    db.write();
+
+                    let repo = getRepository(MessageReaction);
+
+                    let message = repo.create({
+                        message: reactable,
+                        reactionEmoteId: reactionEmoji
+                    });
+                    await repo.save(message);
+
                     msg.channel.send(`${msg.author.toString()} Added reaction!`);
                 }
             }
         },
         {
             pattern: "remove reaction to",
-            action: (msg, s) => {
-                if (!isAuthorised(msg.member)) return;
+            action: async (msg, s) => {
+                if (!isAuthorised(msg.member))
+                    return;
+
                 let content = s.substring("remove reaction to ".length).trim().toLowerCase();
-                if (!db.get("messageReactions").has(content).value()) {
+                let repo = getRepository(MessageReaction);
+                let result = await repo.delete({ message: content });
+
+                if (result.affected == 0) {
                     msg.channel.send(`${msg.author.toString()} No such reaction available!`);
                     return;
                 }
-                db.get("messageReactions").unset(content).write();
                 msg.channel.send(`${msg.author.toString()} Removed reaction!`);
             }
         },
         {
             pattern: "reactions",
-            action: msg => {
-                let reactions = db.get("messageReactions").keys().value().reduce((p: string, c: string) => `${p}\n${c}`, "");
-                msg.channel.send(`I'll react to the following messages:\n\`\`\`${reactions}\`\`\``);
+            action: async msg => {
+
+                let reactionsRepo = getRepository(MessageReaction);
+                
+                let messages = await reactionsRepo.find({
+                    select: [ "message" ]
+                });
+
+                let reactions = messages.reduce((p, c) => `${p}\n${c.message}`, "");
+                msg.channel.send(`I'll react to the following messages:\n\`\`\`${reactions}\n\`\`\``);
             }
         }
     ],
@@ -61,67 +96,79 @@ export default {
             description: "Lists all known messages this bot can react to."
         }
     },
-    onMessage: (actionsDone, msg, content) => {
+    onMessage: async (actionsDone, msg, content) => {
         if (actionsDone)
             return false;
 
         let lowerContent = content.toLowerCase();
-        if (db.get("messageReactions").has(lowerContent).value()) {
-            msg.react(client.emojis.get((db.get("messageReactions") as ObjectChain<any>).get(lowerContent).value()));
+
+        let reactionRepo = getRepository(MessageReaction);
+        let usersRepo = getRepository(KnownUser);
+
+        let message = await reactionRepo.findOne({ message: lowerContent });
+
+        if(message) {
+            msg.react(client.emojis.get(message.reactionEmoteId));
             return true;
         }
 
         if (msg.mentions.users.size == 0)
             return false;
 
-        if (!(db.get("reactableMentionedUsers") as CollectionChain<any>).intersectionWith(msg.mentions.users.map(u => u.id)).isEmpty().value()) {
-            const emoteId = ((db
-                .get("emotes") as ObjectChain<any>)
-                .get("angery") as IRandomElementMixin)
-                .randomElement()
-                .value();
-            msg.react(client.emojis.find(e => e.id == emoteId));
-            return true;
+        let knownUsers = await usersRepo.find({
+            select: [ "mentionReactionType" ],
+            where: [...msg.mentions.users.map(u => ({ userId: u.id }))]
+        });
+
+        if(knownUsers.length == 0)
+            return false;
+
+        let reactionEmoteTypes = new Set<ReactionType>();
+        
+        for(let user of knownUsers) {
+            if(user.mentionReactionType == ReactionType.NONE)
+                continue;
+            reactionEmoteTypes.add(user.mentionReactionType);
         }
 
-        return false;
+        let randomEmotes = await getRandomEmotes([...reactionEmoteTypes]);
+
+        for(let emote in randomEmotes)
+            await msg.react(client.emojis.find(e => e.id == emote));
+
+        return true;
     },
-    onIndirectMention: (actionsDone, msg) => {
+    onIndirectMention: async (actionsDone, msg) => {
         if (actionsDone)
             return false;
-        let emoteType = "angery";
-        if ((db.get("specialUsers") as CollectionChain<any>).includes(msg.author.id).value())
-            emoteType = "hug";
-        else if ((db.get("bigUsers") as CollectionChain<any>).includes(msg.author.id).value())
-            emoteType = "big";
-        else if ((db.get("dedUsers") as CollectionChain<any>).includes(msg.author.id).value())
-            emoteType = "ded";
-
-        let id = ((db
-            .get("emotes") as ObjectChain<string>)
-            .get(emoteType) as IRandomElementMixin)
-            .randomElement()
-            .value();
-
-        let emote = client.emojis.find(e => e.id == id);
+        let emoteType = ReactionType.ANGERY;
+
+        let repo = getRepository(KnownUser);
+
+        let knownUser = await repo.findOne({ 
+            select: [ "mentionReactionType" ],
+            where: [{userID: msg.id}]
+        });
+
+        if(!knownUser || knownUser.mentionReactionType == ReactionType.NONE)
+            return false;
+
+        emoteType = knownUser.mentionReactionType;
+
+        let emotes = await getRandomEmotes([ emoteType ]);
+
+        if(emotes.length != 1)
+            return false;
+
+        let emote = client.emojis.find(e => e.id == emotes[0]);
 
         if (!emote) {
-            console.log(`WARNING: Emote ${id} no longer is valid. Deleting invalid emojis from the list...`);
-            ((db.get("emotes") as ObjectChain<any>)
-                .get(emoteType) as CollectionChain<any>)
-                .remove((id: string) => !client.emojis.has(id))
-                .write();
-
-            id = ((db
-                .get("emotes") as ObjectChain<any>)
-                .get(emoteType) as IRandomElementMixin)
-                .randomElement()
-                .value();
-            emote = client.emojis.find(e => e.id == id);
-        }
+            console.log(`WARNING: Emote ${emotes[0]} no longer is valid. Deleting invalid emojis from the list...`);
 
-        if (!emote)
+            let emotesRepo = getRepository(ReactionEmote);
+            emotesRepo.delete({ reactionId: emotes[0] });
             return false;
+        }
 
         msg.channel.send(emote.toString());
         return true;

+ 2 - 2
src/entity/MessageReaction.ts

@@ -1,9 +1,9 @@
-import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
+import {Entity, PrimaryColumn, Column} from "typeorm";
 
 @Entity()
 export class MessageReaction {
     
-    @PrimaryGeneratedColumn()
+    @PrimaryColumn()
     message: string;
 
     @Column()

+ 16 - 14
src/main.ts

@@ -10,21 +10,25 @@ import {createConnection} from "typeorm";
 dotenv.config();
 const REACT_PROBABILITY = 0.3;
 
-function trigger<T extends (actionDone: boolean, ...args: any[]) => boolean>(actions : Array<T>, ...params: any[]) {
+async function trigger(actions : BotEvent[], ...params: any[]) {
     let actionDone = false;
     for (let i = 0; i < actions.length; i++) {
         const action = actions[i];
-        actionDone = action(actionDone, ...params) || actionDone;
+        let actionResult = action(actionDone, ...params);
+        if(actionResult instanceof Promise)
+            actionDone = (await actionResult) || actionDone;
+        else
+            actionDone = actionResult || actionDone;
     }
     return actionDone;
 }
 
-let commands : Array<IBotCommand> = [];
-let msgActions : Array<BotEvent> = [];
-let indirectMentionActions : Array<BotEvent> = [];
+let commands : IBotCommand[] = [];
+let msgActions : BotEvent[] = [];
+let indirectMentionActions : BotEvent[] = [];
 let startActions : Array<() => void> = [];
-let directMessageActions : Array<BotEvent> = [];
-let postActions : Array<BotEvent> = [];
+let directMessageActions : BotEvent[] = [];
+let postActions : BotEvent[] = [];
 
 client.on("ready", () => {
     console.log("Starting up NoctBot!");
@@ -38,13 +42,13 @@ client.on("ready", () => {
     console.log("NoctBot is ready!");
 });
 
-client.on("message", m => {
+client.on("message", async m => {
     if (m.author.id == client.user.id) 
         return;
 
     let content = m.cleanContent.trim();
 
-    if (!shouldShowMaintenanceMessage(m.guild.id) && trigger(msgActions, m, content))
+    if (!shouldShowMaintenanceMessage(m.guild.id) && await trigger(msgActions, m, content))
         return;
 
     if (m.mentions.users.size > 0 && m.mentions.users.first().id == client.user.id) {
@@ -72,16 +76,16 @@ client.on("message", m => {
                 }
             }
 
-            if (trigger(directMessageActions, m, lowerCaseContent))
+            if (await trigger(directMessageActions, m, lowerCaseContent))
                 return;
         }
 
-        if (trigger(indirectMentionActions, m))
+        if (await trigger(indirectMentionActions, m))
             return;
     }
 
     if(!shouldShowMaintenanceMessage(m.guild.id))
-        trigger(postActions);
+        await trigger(postActions);
 });
 
 client.on("messageReactionAdd", (r, u) => {
@@ -110,8 +114,6 @@ async function main() {
         let obj = require(path.resolve(commandsPath, file)).default as ICommand;
         if (obj.commands)
             for (let command of obj.commands) {
-                // if (obj.commands.hasOwnProperty(command))
-                    // commands[command] = obj.commands[command];
                 commands.push(command);
             }