|  | @@ -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>": {
 |