import { isAuthorisedAsync } from "../util"; import { Message } from "discord.js"; import { getRepository } from "typeorm"; import { Guide, GuideType, GuideKeyword } from "@shared/db/entity/Guide"; import { CommandSet, Action, ActionType, Command } from "src/model/command"; @CommandSet export class GuideCommands { async 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((v, i) => `$${i + 1}`).join(",")})) group by gk."guideId") as gks on gks."guideId" = guide.id order by gc desc limit 1`, keywords ) as Guide[]; if (a.length == 0) return null; return a[0]; } async 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(); const MAX_GUIDES_PER_MSG = 30; let guides = `${msg.author.toString()} ${message}\n\`\`\``; let guideNum = 0; for (let guide of allGuides) { guides += `${guide.displayName} -- ${guide.keywords.map(k => k.keyword).join(" ")}\n`; guideNum++; if (guideNum == MAX_GUIDES_PER_MSG) { guides += "```"; await msg.channel.send(guides); guides = "```\n"; guideNum = 0; } } if (guideNum != 0) { guides += "```\n\nTo display the guides, ping me with one or more keywords, like `@NoctBot sybaris com`"; await msg.channel.send(guides); } } @Action(ActionType.DIRECT_MENTION) async displayGuide(actionsDone: boolean, msg: Message, content: string) { if (actionsDone) return false; if (msg.attachments.size > 0 || content.length == 0) return false; let parts = content.split(" ").map(s => s.trim()).filter(s => s.length != 0); let guide = await this.matchGuide(parts); if (guide) { msg.channel.send(guide.content); return true; } return false; } @Command({ pattern: /^make (\w+)\s+name:(.+)\s*keywords:(.+)\s*contents:((.*[\n\r]*)+)$/i, auth: true, documentation: { description: "Creates a new guide of the specified type, the specified keywords and content.", example: "make name: keywords: contents: " } }) async makeGuide(msg: Message, content: string, match: RegExpMatchArray) { if (!await isAuthorisedAsync(msg.member)) return; let type = match[1].toLowerCase(); let name = match[2].trim(); let keywords = match[3].toLowerCase().split(" ").map(s => s.trim()).filter(s => s.length != 0); let contents = match[4].trim(); if (contents.length == 0) { msg.channel.send( `${msg.author.toString()} The guide must have some content!` ); return; } if (!(Object.values(GuideType)).includes(type)) { msg.channel.send( `${msg.author.toString()} The type ${type} is not a valid guide type!` ); return; } let repo = getRepository(GuideKeyword); let guideRepo = getRepository(Guide); let existingKeywords = await repo.find({ where: [ ...keywords.map(k => ({ keyword: k })) ] }); let existingGuide = await this.matchGuide(keywords); let addGuide = async () => { let newKeywords = new Set(); 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, 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 }); else await addGuide(); } else await addGuide(); msg.channel.send( `${msg.author.toString()} Added/updated "${name}" (keywords \`${keywords.join(" ")}\`)!` ); } @Command({ pattern: /^delete (\w+)\s+(.+)$/i, auth: true, documentation: { example: "delete ", description: "Deletes a guide with the specified keywords" } }) async deleteGuide(msg: Message, content: string, match: RegExpMatchArray) { if (!await isAuthorisedAsync(msg.member)) return; let type = match[1]; let keywords = match[2].toLowerCase().split(" ").map(s => s.trim()).filter(s => s.length != 0); if (!(Object.values(GuideType)).includes(type)) { await msg.channel.send( `${msg.author.toString()} The type ${type} is not a valid guide type!` ); return; } let dedupedKeywords = [...new Set(keywords)]; let repo = getRepository(GuideKeyword); let guideRepo = getRepository(Guide); let existingGuide = await this.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.join(" ")}"!`); return; } } await msg.channel.send(`${msg.author.toString()} No such ${type} with keywords \`${keywords.join(" ")}\`! Did you forget to specify all keywords?`); } @Command({ pattern: "guides", documentation: { description: "Lists all guides and keywords that trigger them.", example: "guides" } }) async showGuides(msg: Message) { await this.listGuides(msg, "guide", "Here are the guides I have:"); } @Command({ pattern: "memes", documentation: {description: "Lists all memes and keywords that trigger them.", example: "memes"} }) async showMemes(msg: Message) { await this.listGuides(msg, "meme", "Here are some random memes I have:") } @Command({ pattern: "misc", documentation: {description: "Lists all additional keywords the bot reacts to.", example: "misc"} }) async showMisc(msg: Message) { await this.listGuides(msg, "misc", "These are some misc stuff I can also do:") } };