Browse Source

Port guides and react to typeorm

ghorsington 4 năm trước cách đây
mục cha
commit
17395ca761
4 tập tin đã thay đổi với 146 bổ sung78 xóa
  1. 1 0
      .gitignore
  2. 119 78
      src/commands/guide.ts
  3. 3 0
      src/commands/react.ts
  4. 23 0
      src/util.ts

+ 1 - 0
.gitignore

@@ -39,3 +39,4 @@ imagestats.csv
 clarifai_keys.js
 .env
 db_old.json
+db_migrated.json

+ 119 - 78
src/commands/guide.ts

@@ -1,9 +1,8 @@
-import { db } from "../db";
-import { isAuthorised } from "../util";
+import { isAuthorised, isAuthorisedAsync } from "../util";
 import { ICommand } from "./command";
-import { CollectionChain } from "lodash";
 import { Message } from "discord.js";
-import { ObjectChain } from "lodash";
+import { getRepository, getConnection } from "typeorm";
+import { Guide, GuideType, GuideKeyword } from "../entity/Guide";
 
 const VALID_GUIDE_TYPES = new Set(["meme", "guide", "misc"]);
 
@@ -16,41 +15,55 @@ interface IGuide {
     content: string
 };
 
-function listGuides(msg: Message, guideType: string, message: string){
-    let guidesForType = db.get(guideType) as CollectionChain<IGuide>;
-    let guides = guidesForType
-            .reduce((p, c) => `${p}\n${c.displayName} -- ${c.name}`, "\n")
-            .value();
+async function matchGuide(keywords: string[]) {
+    let a = await getRepository(Guide).query(
+        `select guide.*
+         from guide
+         inner join (select gk.guideId, count(guideKeywordId) as gc
+                    from guide_keywords_guide_keyword as gk
+                    where
+                    gk.guideKeywordId in    (select id
+                                            from guide_keyword
+                                            where
+                                            guide_keyword.keyword in (${keywords.map(s => "?").join(",")}))
+                    group by gk.guideId
+                    order by gc desc) as gks
+        on gks.guideId = guide.id
+        limit 1`,
+        keywords
+    ) as Guide[];
+    if(a.length == 0)
+        return null;
+    return a[0];
+}
+
+async function listGuides(msg: Message, guideType: string, message: string) {
+    let repo = getRepository(Guide);
+
+    let allGuides = await repo.createQueryBuilder("guide")
+                            .select(["guide.displayName"])
+                            .leftJoinAndSelect("guide.keywords", "keyword")
+                            .where("guide.type = :type", { type: guideType })
+                            .getMany();
+
+    let guides = allGuides
+            .reduce((p, c) => `${p}\n${c.displayName} -- ${c.keywords.map(c => c.keyword).join(" ")}`, "\n");
     msg.channel.send(`${msg.author.toString()} ${message}\n\`\`\`${guides}\`\`\`\n\nTo display the guides, ping me with one or more keywords, like \`@NoctBot sybaris com\``);
 }
 
 export default {
-    onDirectMention: (actionsDone, msg, content) => {
+    onDirectMention: async (actionsDone, msg, content) => {
         if (actionsDone)
             return false;
     
         if(msg.attachments.size > 0 || content.length == 0)
             return false;
     
-        let parts = content.trim().split(" ");
-
-        let guides = db.get("guides") as CollectionChain<IGuide>;
-
-        let guide = guides
-            .clone()
-            .concat(db.get("memes").value(), db.get("miscs").value())
-            .map(g => Object.assign({
-                parts: g.name.toLowerCase().split(" ")
-            }, g))
-            .sortBy(g => g.parts.length)
-            .maxBy(k => db._.intersection(parts, k.parts).length)
-            .value();
-        
-        let hits =
-            guide !== undefined &&
-            db._.intersection(guide.name.toLowerCase().split(" "), parts).length > 0;
+        let parts = content.split(" ").map(s => s.trim()).filter(s => s.length != 0);
+
+        let guide = await matchGuide(parts);
         
-        if (hits) {
+        if (guide) {
             msg.channel.send(guide.content);
             return true;
         }
@@ -59,11 +72,11 @@ export default {
     commands: [
         {
             pattern: makePattern,
-            action: (msg, s, match) => {
-                if (!isAuthorised(msg.member)) return;
+            action: async (msg, s, match) => {
+                if (!await isAuthorisedAsync(msg.member)) return;
                 let type = match[1].toLowerCase();
                 let name = match[2].trim();
-                let keywords = match[3].trim().toLowerCase();
+                let keywords = match[3].toLowerCase().split(" ").map(s => s.trim()).filter(s => s.length != 0);
                 let contents = match[4].trim();
     
                 if(contents.length == 0){
@@ -73,33 +86,60 @@ export default {
                     return;
                 }
         
-                if(!VALID_GUIDE_TYPES.has(type)){
+                if(!Object.values(GuideType).includes(type)){
                     msg.channel.send(
                         `${msg.author.toString()} The type ${type} is not a valid guide type!`
                     );
                     return;
                 }
-        
-                let typeDB = `${type}s`;
-        
-                let guide = (db.get(typeDB) as ObjectChain<any>).find({
-                    name: keywords
-                }) as ObjectChain<any>;
-        
-                if (!guide.isUndefined().value()) {
-                    guide.assign({
+                
+                let repo = getRepository(GuideKeyword);
+                let guideRepo = getRepository(Guide);
+                
+                let existingKeywords = await repo.find({
+                    where: [
+                        ...keywords.map(k => ({ keyword: k }))
+                    ]
+                });
+                
+                let existingGuide = await matchGuide(keywords);
+
+                let addGuide = async () => {
+                    let newKeywords = new Set<string>();
+                    let knownKeywords = new Set(existingKeywords.map(e => e.keyword));
+                    for(let word of keywords) {
+                        if(!knownKeywords.has(word))
+                            newKeywords.add(word);
+                    }
+
+                    let addedKeywords = await repo.save([...newKeywords].map(k => repo.create({
+                        keyword: k
+                    })));
+
+                    await guideRepo.save(guideRepo.create({
+                        content: contents,
                         displayName: name,
-                        content: contents
-                    }).write();
-                } else {
-                    (db.get(typeDB) as CollectionChain<any>)
-                        .push({
-                            name: keywords,
+                        keywords: [...existingKeywords, ...addedKeywords],
+                        type: type as GuideType
+                    }));
+                };
+
+                if(existingGuide) {
+                    let guideKeywordsCount = await repo
+                        .createQueryBuilder("keywords")
+                        .leftJoinAndSelect("keywords.relatedGuides", "guide")
+                        .where("guide.id = :id", {id: existingGuide.id })
+                        .getCount();
+
+                    if(guideKeywordsCount == existingKeywords.length)
+                        await guideRepo.update({id: existingGuide.id}, {
                             displayName: name,
                             content: contents
-                        })
-                        .write();
-                }
+                        });
+                    else
+                        await addGuide();
+                } else
+                    await addGuide();
         
                 msg.channel.send(
                     `${msg.author.toString()} Added/updated "${name}" (keywords \`${keywords}\`)!`
@@ -108,43 +148,44 @@ export default {
         },
         {
             pattern: deletePattern,
-            action: (msg, s, match) => {
-                if (!isAuthorised(msg.member)) return;
+            action: async (msg, s, match) => {
+                if (!await isAuthorisedAsync(msg.member)) return;
                 let type = match[1];
-                let keywords = match[2].trim();
+                let keywords = match[2].toLowerCase().split(" ").map(s => s.trim()).filter(s => s.length != 0);
         
-                if(!VALID_GUIDE_TYPES.has(type)){
-                    msg.channel.send(
+                if(!Object.values(GuideType).includes(type)){
+                    await msg.channel.send(
                         `${msg.author.toString()} The type ${type} is not a valid guide type!`
                     );
                     return;
                 }
-        
-                let typeDB = `${type}s`;
-        
-                let val = (db.get(typeDB) as ObjectChain<any>).find({
-                    name: keywords
-                });
-        
-                if (val.isUndefined().value()) {
-                    msg.channel.send(`${msg.author.toString()} No ${type} "${keywords}"!`);
-                    return;
+
+                let dedupedKeywords = [...new Set(keywords)];
+                
+                let repo = getRepository(GuideKeyword);
+                let guideRepo = getRepository(Guide);
+                let existingGuide = await matchGuide(keywords);
+
+                if(existingGuide) {
+                    let guideKeywordsCount = await repo
+                        .createQueryBuilder("keywords")
+                        .leftJoinAndSelect("keywords.relatedGuides", "guide")
+                        .where("guide.id = :id", {id: existingGuide.id })
+                        .getCount();
+
+                    if(guideKeywordsCount == dedupedKeywords.length) {
+                        await guideRepo.delete({ id: existingGuide.id });
+                        await msg.channel.send(`${msg.author.toString()} Removed ${type} "${keywords}"!`);
+                        return;
+                    }
                 }
-        
-                (db.get(typeDB) as CollectionChain<any>)
-                    .remove({
-                        name: keywords
-                    })
-                    .write();
-        
-                msg.channel.send(
-                    `${msg.author.toString()} Removed ${type} "${keywords}"!`
-                );
+                
+                await msg.channel.send(`${msg.author.toString()} No such ${type} with keywords \`${keywords}\`! Did you forget to specify all keywords?`);
             }
         },
-        { pattern: "guides", action: msg => listGuides(msg, "guides", "Here are the guides I have:") },
-        { pattern: "memes", action: msg => listGuides(msg, "memes", "Here are some random memes I have:") },
-        { pattern: "misc", action: msg => listGuides(msg, "misc", "These are some misc stuff I can also do:") },
+        { pattern: "guides", action: async msg => await listGuides(msg, "guide", "Here are the guides I have:") },
+        { pattern: "memes", action: async msg => await listGuides(msg, "meme", "Here are some random memes I have:") },
+        { pattern: "misc", action: async msg => await listGuides(msg, "misc", "These are some misc stuff I can also do:") },
     ],
     documentation: {
         "make <guidetype> <NEWLINE>name: <name> <NEWLINE>keywords: <keywords> <NEWLINE>contents: <content>": {

+ 3 - 0
src/commands/react.ts

@@ -133,6 +133,9 @@ export default {
 
         let randomEmotes = await getRandomEmotes([...reactionEmoteTypes]);
 
+        if(randomEmotes.length == 0)
+            return false;
+
         for(let emote in randomEmotes)
             await msg.react(client.emojis.find(e => e.id == emote));
 

+ 23 - 0
src/util.ts

@@ -2,6 +2,8 @@ import { db } from "./db"
 import { CollectionChain } from "lodash";
 import { GuildMember } from "discord.js";
 import { DocumentationSet } from "./commands/command";
+import { getRepository } from "typeorm";
+import { KnownUser } from "./entity/KnownUser";
 
 const VALID_EXTENSIONS = new Set([
     "png",
@@ -40,4 +42,25 @@ export function isAuthorised(member : GuildMember) {
     if (roles.intersectionWith(member.roles.keyArray()).isEmpty().value())
         return false;
     return true;
+}
+
+export async function isAuthorisedAsync(member : GuildMember) {
+    let repo = getRepository(KnownUser);
+
+    let user = await repo.findOne({
+        where: { userID: member.id },
+        select: [ "canModerate" ]
+    });
+
+    if (user && user.canModerate)
+        return true;
+
+    let role = await repo.createQueryBuilder()
+                .select(["userId"])
+                .where("userId in (:...ids)", {ids: member.roles.keyArray()})
+                .andWhere("canModerate = 1")
+                .getOne();
+    if (role)
+        return true;
+    return false;
 }