|
@@ -1,9 +1,8 @@
|
|
-import { db } from "../db";
|
|
|
|
-import { isAuthorised } from "../util";
|
|
|
|
|
|
+import { isAuthorised, isAuthorisedAsync } from "../util";
|
|
import { ICommand } from "./command";
|
|
import { ICommand } from "./command";
|
|
-import { CollectionChain } from "lodash";
|
|
|
|
import { Message } from "discord.js";
|
|
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"]);
|
|
const VALID_GUIDE_TYPES = new Set(["meme", "guide", "misc"]);
|
|
|
|
|
|
@@ -16,41 +15,55 @@ interface IGuide {
|
|
content: string
|
|
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\``);
|
|
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 {
|
|
export default {
|
|
- onDirectMention: (actionsDone, msg, content) => {
|
|
|
|
|
|
+ onDirectMention: async (actionsDone, msg, content) => {
|
|
if (actionsDone)
|
|
if (actionsDone)
|
|
return false;
|
|
return false;
|
|
|
|
|
|
if(msg.attachments.size > 0 || content.length == 0)
|
|
if(msg.attachments.size > 0 || content.length == 0)
|
|
return false;
|
|
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);
|
|
msg.channel.send(guide.content);
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
@@ -59,11 +72,11 @@ export default {
|
|
commands: [
|
|
commands: [
|
|
{
|
|
{
|
|
pattern: makePattern,
|
|
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 type = match[1].toLowerCase();
|
|
let name = match[2].trim();
|
|
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();
|
|
let contents = match[4].trim();
|
|
|
|
|
|
if(contents.length == 0){
|
|
if(contents.length == 0){
|
|
@@ -73,33 +86,60 @@ export default {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- if(!VALID_GUIDE_TYPES.has(type)){
|
|
|
|
|
|
+ if(!Object.values(GuideType).includes(type)){
|
|
msg.channel.send(
|
|
msg.channel.send(
|
|
`${msg.author.toString()} The type ${type} is not a valid guide type!`
|
|
`${msg.author.toString()} The type ${type} is not a valid guide type!`
|
|
);
|
|
);
|
|
return;
|
|
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,
|
|
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,
|
|
displayName: name,
|
|
content: contents
|
|
content: contents
|
|
- })
|
|
|
|
- .write();
|
|
|
|
- }
|
|
|
|
|
|
+ });
|
|
|
|
+ else
|
|
|
|
+ await addGuide();
|
|
|
|
+ } else
|
|
|
|
+ await addGuide();
|
|
|
|
|
|
msg.channel.send(
|
|
msg.channel.send(
|
|
`${msg.author.toString()} Added/updated "${name}" (keywords \`${keywords}\`)!`
|
|
`${msg.author.toString()} Added/updated "${name}" (keywords \`${keywords}\`)!`
|
|
@@ -108,43 +148,44 @@ export default {
|
|
},
|
|
},
|
|
{
|
|
{
|
|
pattern: deletePattern,
|
|
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 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!`
|
|
`${msg.author.toString()} The type ${type} is not a valid guide type!`
|
|
);
|
|
);
|
|
return;
|
|
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: {
|
|
documentation: {
|
|
"make <guidetype> <NEWLINE>name: <name> <NEWLINE>keywords: <keywords> <NEWLINE>contents: <content>": {
|
|
"make <guidetype> <NEWLINE>name: <name> <NEWLINE>keywords: <keywords> <NEWLINE>contents: <content>": {
|