123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- import { Message } from "discord.js";
- import { getRepository } from "typeorm";
- import { Guide, GuideType, GuideKeyword } from "@shared/db/entity/Guide";
- import { Event, BotEventData, Command, ICommandData, Plugin } from "src/model/plugin";
- @Plugin
- export class GuideCommands {
- async matchGuide(keywords: string[]): Promise<Guide | null> {
- const 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): Promise<void> {
- const repo = getRepository(Guide);
- const 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 (const 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);
- }
- }
- @Event("directMention")
- async displayGuide(data: BotEventData, msg: Message, lowerCaseContent: string): Promise<void> {
- if (data.actionsDone)
- return;
- if (msg.attachments.size > 0 || lowerCaseContent.length == 0)
- return;
- const parts = lowerCaseContent.split(" ").map(s => s.trim()).filter(s => s.length != 0);
- const guide = await this.matchGuide(parts);
- if (guide) {
- msg.channel.send(guide.content);
- data.actionsDone = true;
- }
- }
- @Command({
- type: "mention",
- 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 <GUIDE TYPE> <NEWLINE>name: <NAME> <NEWLINE> keywords: <KEYWORDS> <NEWLINE> contents: <CONTENTS>"
- }
- })
- async makeGuide({ message, contents }: ICommandData): Promise<void> {
- const match = contents as RegExpMatchArray;
- const type = match[1].toLowerCase();
- const name = match[2].trim();
- const keywords = match[3].toLowerCase().split(" ").map(s => s.trim()).filter(s => s.length != 0);
- const msgContents = match[4].trim();
- if (msgContents.length == 0) {
- message.reply("the guide must have some content!");
- return;
- }
-
- if (!(<string[]>Object.values(GuideType)).includes(type)) {
- message.reply(`the type ${type} is not a valid guide type!`);
- return;
- }
- const repo = getRepository(GuideKeyword);
- const guideRepo = getRepository(Guide);
- const existingKeywords = await repo.find({
- where: [
- ...keywords.map(k => ({ keyword: k }))
- ]
- });
- const existingGuide = await this.matchGuide(keywords);
- const addGuide = async () => {
- const newKeywords = new Set<string>();
- const knownKeywords = new Set(existingKeywords.map(e => e.keyword));
- for (const word of keywords) {
- if (!knownKeywords.has(word))
- newKeywords.add(word);
- }
- const addedKeywords = await repo.save([...newKeywords].map(k => repo.create({
- keyword: k
- })));
- await guideRepo.save(guideRepo.create({
- content: msgContents,
- displayName: name,
- keywords: [...existingKeywords, ...addedKeywords],
- type: type as GuideType
- }));
- };
- if (existingGuide) {
- const 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: msgContents
- });
- else
- await addGuide();
- } else
- await addGuide();
- message.reply(
- `Added/updated "${name}" (keywords \`${keywords.join(" ")}\`)!`
- );
- }
- @Command({
- type: "mention",
- pattern: /^delete (\w+)\s+(.+)$/i,
- auth: true,
- documentation: {
- example: "delete <guidetype> <keywords>",
- description: "Deletes a guide with the specified keywords"
- }
- })
- async deleteGuide({ message, contents }: ICommandData): Promise<void> {
- const match = contents as RegExpMatchArray;
- const type = match[1];
- const keywords = match[2].toLowerCase().split(" ").map(s => s.trim()).filter(s => s.length != 0);
- if (!(<string[]>Object.values(GuideType)).includes(type)) {
- await message.reply(
- `The type ${type} is not a valid guide type!`
- );
- return;
- }
- const dedupedKeywords = [...new Set(keywords)];
- const repo = getRepository(GuideKeyword);
- const guideRepo = getRepository(Guide);
- const existingGuide = await this.matchGuide(keywords);
- if (existingGuide) {
- const 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 message.reply(`removed ${type} "${keywords.join(" ")}"!`);
- return;
- }
- }
- await message.reply(`no such ${type} with keywords \`${keywords.join(" ")}\`! Did you forget to specify all keywords?`);
- }
- @Command({
- type: "mention",
- pattern: "guides",
- documentation: {
- description: "Lists all guides and keywords that trigger them.",
- example: "guides"
- }
- })
- async showGuides({ message }: ICommandData): Promise<void> {
- await this.listGuides(message, "guide", "Here are the guides I have:");
- }
- @Command({
- type: "mention",
- pattern: "memes",
- documentation: {
- description: "Lists all memes and keywords that trigger them.",
- example: "memes"
- }
- })
- async showMemes({ message }: ICommandData): Promise<void> {
- await this.listGuides(message, "meme", "Here are some random memes I have:");
- }
- @Command({
- type: "mention",
- pattern: "misc",
- documentation: {
- description: "Lists all additional keywords the bot reacts to.",
- example: "misc"
- }
- })
- async showMisc({ message }: ICommandData): Promise<void> {
- await this.listGuides(message, "misc", "These are some misc stuff I can also do:");
- }
- }
|