Reorganize/clean up TagsPlugin code
This commit is contained in:
parent
b758c9cfe0
commit
fc8b78fb55
6 changed files with 171 additions and 122 deletions
|
@ -1,9 +1,9 @@
|
||||||
import { tagsCmd } from "../types";
|
import { tagsCmd } from "../types";
|
||||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
import { renderTag } from "../util/renderTag";
|
|
||||||
import { MessageContent } from "eris";
|
import { MessageContent } from "eris";
|
||||||
import { TemplateParseError } from "../../../templateFormatter";
|
import { TemplateParseError } from "../../../templateFormatter";
|
||||||
import { sendErrorMessage } from "../../../pluginUtils";
|
import { sendErrorMessage } from "../../../pluginUtils";
|
||||||
|
import { renderTagBody } from "../util/renderTagBody";
|
||||||
|
|
||||||
export const TagEvalCmd = tagsCmd({
|
export const TagEvalCmd = tagsCmd({
|
||||||
trigger: "tag eval",
|
trigger: "tag eval",
|
||||||
|
@ -15,7 +15,7 @@ export const TagEvalCmd = tagsCmd({
|
||||||
|
|
||||||
async run({ message: msg, args, pluginData }) {
|
async run({ message: msg, args, pluginData }) {
|
||||||
try {
|
try {
|
||||||
const rendered = await renderTag(pluginData, args.body);
|
const rendered = await renderTagBody(pluginData, args.body);
|
||||||
msg.channel.createMessage(rendered);
|
msg.channel.createMessage(rendered);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TemplateParseError) {
|
if (e instanceof TemplateParseError) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { GuildLogs } from "src/data/GuildLogs";
|
||||||
|
|
||||||
export const Tag = t.union([t.string, tEmbed]);
|
export const Tag = t.union([t.string, tEmbed]);
|
||||||
|
|
||||||
const TagCategory = t.type({
|
export const TagCategory = t.type({
|
||||||
prefix: tNullable(t.string),
|
prefix: tNullable(t.string),
|
||||||
delete_with_command: tNullable(t.boolean),
|
delete_with_command: tNullable(t.boolean),
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ const TagCategory = t.type({
|
||||||
|
|
||||||
can_use: tNullable(t.boolean),
|
can_use: tNullable(t.boolean),
|
||||||
});
|
});
|
||||||
|
export type TTagCategory = t.TypeOf<typeof TagCategory>;
|
||||||
|
|
||||||
export const ConfigSchema = t.type({
|
export const ConfigSchema = t.type({
|
||||||
prefix: t.string,
|
prefix: t.string,
|
||||||
|
|
94
backend/src/plugins/Tags/util/matchAndRenderTagFromString.ts
Normal file
94
backend/src/plugins/Tags/util/matchAndRenderTagFromString.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager";
|
||||||
|
import { PluginData } from "knub";
|
||||||
|
import { TagsPluginType, TTagCategory } from "../types";
|
||||||
|
import { renderTagFromString } from "./renderTagFromString";
|
||||||
|
import { convertDelayStringToMS } from "../../../utils";
|
||||||
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
|
import { Member, MessageContent } from "eris";
|
||||||
|
|
||||||
|
interface Result {
|
||||||
|
renderedContent: MessageContent;
|
||||||
|
tagName: string;
|
||||||
|
categoryName: string | null;
|
||||||
|
category: TTagCategory | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function matchAndRenderTagFromString(
|
||||||
|
pluginData: PluginData<TagsPluginType>,
|
||||||
|
str: string,
|
||||||
|
member: Member,
|
||||||
|
extraMatchParams: ExtendedMatchParams = {},
|
||||||
|
): Promise<Result | null> {
|
||||||
|
const config = pluginData.config.getMatchingConfig({
|
||||||
|
...extraMatchParams,
|
||||||
|
member,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hard-coded tags in categories
|
||||||
|
for (const [name, category] of Object.entries(config.categories)) {
|
||||||
|
const canUse = category.can_use != null ? category.can_use : config.can_use;
|
||||||
|
if (canUse !== true) continue;
|
||||||
|
|
||||||
|
const prefix = category.prefix != null ? category.prefix : config.prefix;
|
||||||
|
if (prefix !== "" && !str.startsWith(prefix)) continue;
|
||||||
|
|
||||||
|
const withoutPrefix = str.slice(prefix.length);
|
||||||
|
|
||||||
|
for (const [tagName, tagBody] of Object.entries(category.tags)) {
|
||||||
|
const regex = new RegExp(`^${escapeStringRegexp(tagName)}(?:\\s|$)`);
|
||||||
|
if (regex.test(withoutPrefix)) {
|
||||||
|
const renderedContent = await renderTagFromString(
|
||||||
|
pluginData,
|
||||||
|
str,
|
||||||
|
prefix,
|
||||||
|
tagName,
|
||||||
|
category.tags[tagName],
|
||||||
|
member,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
renderedContent,
|
||||||
|
tagName,
|
||||||
|
categoryName: name,
|
||||||
|
category,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic tags
|
||||||
|
if (config.can_use !== true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dynamicTagPrefix = config.prefix;
|
||||||
|
if (!str.startsWith(dynamicTagPrefix)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dynamicTagNameMatch = str.slice(dynamicTagPrefix.length).match(/^\S+/);
|
||||||
|
if (dynamicTagNameMatch === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dynamicTagName = dynamicTagNameMatch[0];
|
||||||
|
const dynamicTag = await pluginData.state.tags.find(dynamicTagName);
|
||||||
|
if (!dynamicTag) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderedDynamicTagContent = await renderTagFromString(
|
||||||
|
pluginData,
|
||||||
|
str,
|
||||||
|
dynamicTagPrefix,
|
||||||
|
dynamicTagName,
|
||||||
|
dynamicTag.body,
|
||||||
|
member,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
renderedContent: renderedDynamicTagContent,
|
||||||
|
tagName: dynamicTagName,
|
||||||
|
categoryName: null,
|
||||||
|
category: null,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
import { TagsPluginType } from "../types";
|
import { TagsPluginType } from "../types";
|
||||||
import { SavedMessage } from "src/data/entities/SavedMessage";
|
import { SavedMessage } from "src/data/entities/SavedMessage";
|
||||||
import { PluginData } from "knub";
|
import { PluginData } from "knub";
|
||||||
import { resolveMember, convertDelayStringToMS, tStrictMessageContent } from "src/utils";
|
import { convertDelayStringToMS, resolveMember, tStrictMessageContent } from "src/utils";
|
||||||
import escapeStringRegexp from "escape-string-regexp";
|
|
||||||
import { validate } from "src/validatorUtils";
|
import { validate } from "src/validatorUtils";
|
||||||
import { LogType } from "src/data/LogType";
|
import { LogType } from "src/data/LogType";
|
||||||
import { TextChannel } from "eris";
|
import { TextChannel } from "eris";
|
||||||
import { renderSafeTagFromMessage } from "./renderSafeTagFromMessage";
|
import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString";
|
||||||
|
|
||||||
export async function onMessageCreate(pluginData: PluginData<TagsPluginType>, msg: SavedMessage) {
|
export async function onMessageCreate(pluginData: PluginData<TagsPluginType>, msg: SavedMessage) {
|
||||||
if (msg.is_bot) return;
|
if (msg.is_bot) return;
|
||||||
|
@ -21,104 +20,58 @@ export async function onMessageCreate(pluginData: PluginData<TagsPluginType>, ms
|
||||||
channelId: msg.channel_id,
|
channelId: msg.channel_id,
|
||||||
categoryId: channel.parentID,
|
categoryId: channel.parentID,
|
||||||
});
|
});
|
||||||
let deleteWithCommand = false;
|
|
||||||
|
|
||||||
// Find potential matching tag, looping through categories first and checking dynamic tags last
|
const tagResult = await matchAndRenderTagFromString(pluginData, msg.data.content, member, {
|
||||||
let renderedTag = null;
|
channelId: msg.channel_id,
|
||||||
let matchedTagName;
|
categoryId: channel.parentID,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tagResult) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for cooldowns
|
||||||
const cooldowns = [];
|
const cooldowns = [];
|
||||||
|
|
||||||
for (const [name, category] of Object.entries(config.categories)) {
|
if (tagResult.category) {
|
||||||
const canUse = category.can_use != null ? category.can_use : config.can_use;
|
// Category-specific cooldowns
|
||||||
if (canUse !== true) continue;
|
if (tagResult.category.user_tag_cooldown) {
|
||||||
|
const delay = convertDelayStringToMS(String(tagResult.category.user_tag_cooldown), "s");
|
||||||
const prefix = category.prefix != null ? category.prefix : config.prefix;
|
cooldowns.push([`tags-category-${tagResult.categoryName}-user-${msg.user_id}-tag-${tagResult.tagName}`, delay]);
|
||||||
if (prefix !== "" && !msg.data.content.startsWith(prefix)) continue;
|
}
|
||||||
|
if (tagResult.category.global_tag_cooldown) {
|
||||||
const withoutPrefix = msg.data.content.slice(prefix.length);
|
const delay = convertDelayStringToMS(String(tagResult.category.global_tag_cooldown), "s");
|
||||||
|
cooldowns.push([`tags-category-${tagResult.categoryName}-tag-${tagResult.tagName}`, delay]);
|
||||||
for (const [tagName, tagBody] of Object.entries(category.tags)) {
|
}
|
||||||
const regex = new RegExp(`^${escapeStringRegexp(tagName)}(?:\\s|$)`);
|
if (tagResult.category.user_category_cooldown) {
|
||||||
if (regex.test(withoutPrefix)) {
|
const delay = convertDelayStringToMS(String(tagResult.category.user_category_cooldown), "s");
|
||||||
renderedTag = await renderSafeTagFromMessage(
|
cooldowns.push([`tags-category-${tagResult.categoryName}-user--${msg.user_id}`, delay]);
|
||||||
pluginData,
|
}
|
||||||
msg.data.content,
|
if (tagResult.category.global_category_cooldown) {
|
||||||
prefix,
|
const delay = convertDelayStringToMS(String(tagResult.category.global_category_cooldown), "s");
|
||||||
tagName,
|
cooldowns.push([`tags-category-${tagResult.categoryName}`, delay]);
|
||||||
category.tags[tagName],
|
}
|
||||||
member,
|
} else {
|
||||||
);
|
// Dynamic tag cooldowns
|
||||||
if (renderedTag) {
|
if (config.user_tag_cooldown) {
|
||||||
matchedTagName = tagName;
|
const delay = convertDelayStringToMS(String(config.user_tag_cooldown), "s");
|
||||||
break;
|
cooldowns.push([`tags-user-${msg.user_id}-tag-${tagResult.tagName}`, delay]);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renderedTag) {
|
if (config.global_tag_cooldown) {
|
||||||
if (category.user_tag_cooldown) {
|
const delay = convertDelayStringToMS(String(config.global_tag_cooldown), "s");
|
||||||
const delay = convertDelayStringToMS(String(category.user_tag_cooldown), "s");
|
cooldowns.push([`tags-tag-${tagResult.tagName}`, delay]);
|
||||||
cooldowns.push([`tags-category-${name}-user-${msg.user_id}-tag-${matchedTagName}`, delay]);
|
|
||||||
}
|
|
||||||
if (category.global_tag_cooldown) {
|
|
||||||
const delay = convertDelayStringToMS(String(category.global_tag_cooldown), "s");
|
|
||||||
cooldowns.push([`tags-category-${name}-tag-${matchedTagName}`, delay]);
|
|
||||||
}
|
|
||||||
if (category.user_category_cooldown) {
|
|
||||||
const delay = convertDelayStringToMS(String(category.user_category_cooldown), "s");
|
|
||||||
cooldowns.push([`tags-category-${name}-user--${msg.user_id}`, delay]);
|
|
||||||
}
|
|
||||||
if (category.global_category_cooldown) {
|
|
||||||
const delay = convertDelayStringToMS(String(category.global_category_cooldown), "s");
|
|
||||||
cooldowns.push([`tags-category-${name}`, delay]);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteWithCommand =
|
|
||||||
category.delete_with_command != null ? category.delete_with_command : config.delete_with_command;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Matching tag was not found from the config, try a dynamic tag
|
if (config.user_cooldown) {
|
||||||
if (!renderedTag) {
|
const delay = convertDelayStringToMS(String(config.user_cooldown), "s");
|
||||||
if (config.can_use !== true) return;
|
cooldowns.push([`tags-user-${tagResult.tagName}`, delay]);
|
||||||
|
}
|
||||||
|
|
||||||
const prefix = config.prefix;
|
if (config.global_cooldown) {
|
||||||
if (!msg.data.content.startsWith(prefix)) return;
|
const delay = convertDelayStringToMS(String(config.global_cooldown), "s");
|
||||||
|
cooldowns.push([`tags`, delay]);
|
||||||
const tagNameMatch = msg.data.content.slice(prefix.length).match(/^\S+/);
|
}
|
||||||
if (tagNameMatch === null) return;
|
|
||||||
|
|
||||||
const tagName = tagNameMatch[0];
|
|
||||||
const tag = await pluginData.state.tags.find(tagName);
|
|
||||||
if (!tag) return;
|
|
||||||
|
|
||||||
matchedTagName = tagName;
|
|
||||||
|
|
||||||
renderedTag = await renderSafeTagFromMessage(pluginData, msg.data.content, prefix, tagName, tag.body, member);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!renderedTag) return;
|
|
||||||
|
|
||||||
if (config.user_tag_cooldown) {
|
|
||||||
const delay = convertDelayStringToMS(String(config.user_tag_cooldown), "s");
|
|
||||||
cooldowns.push([`tags-user-${msg.user_id}-tag-${matchedTagName}`, delay]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.global_tag_cooldown) {
|
|
||||||
const delay = convertDelayStringToMS(String(config.global_tag_cooldown), "s");
|
|
||||||
cooldowns.push([`tags-tag-${matchedTagName}`, delay]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.user_cooldown) {
|
|
||||||
const delay = convertDelayStringToMS(String(config.user_cooldown), "s");
|
|
||||||
cooldowns.push([`tags-user-${matchedTagName}`, delay]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.global_cooldown) {
|
|
||||||
const delay = convertDelayStringToMS(String(config.global_cooldown), "s");
|
|
||||||
cooldowns.push([`tags`, delay]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOnCooldown = cooldowns.some(cd => pluginData.cooldowns.isOnCooldown(cd[0]));
|
const isOnCooldown = cooldowns.some(cd => pluginData.cooldowns.isOnCooldown(cd[0]));
|
||||||
|
@ -128,26 +81,25 @@ export async function onMessageCreate(pluginData: PluginData<TagsPluginType>, ms
|
||||||
pluginData.cooldowns.setCooldown(cd[0], cd[1]);
|
pluginData.cooldowns.setCooldown(cd[0], cd[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteWithCommand = config.delete_with_command;
|
const validationError = await validate(tStrictMessageContent, tagResult.renderedContent);
|
||||||
|
|
||||||
const validationError = await validate(tStrictMessageContent, renderedTag);
|
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
pluginData.state.logs.log(LogType.BOT_ALERT, {
|
pluginData.state.logs.log(LogType.BOT_ALERT, {
|
||||||
body: `Rendering tag ${matchedTagName} resulted in an invalid message: ${validationError.message}`,
|
body: `Rendering tag ${tagResult.tagName} resulted in an invalid message: ${validationError.message}`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof renderedTag === "string" && renderedTag.trim() === "") {
|
if (typeof tagResult.renderedContent === "string" && tagResult.renderedContent.trim() === "") {
|
||||||
pluginData.state.logs.log(LogType.BOT_ALERT, {
|
pluginData.state.logs.log(LogType.BOT_ALERT, {
|
||||||
body: `Tag \`${matchedTagName}\` resulted in an empty message, so it couldn't be sent`,
|
body: `Tag \`${tagResult.tagName}\` resulted in an empty message, so it couldn't be sent`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseMsg = await channel.createMessage(renderedTag);
|
const responseMsg = await channel.createMessage(tagResult.renderedContent);
|
||||||
|
|
||||||
// Save the command-response message pair once the message is in our database
|
// Save the command-response message pair once the message is in our database
|
||||||
|
const deleteWithCommand = tagResult.category?.delete_with_command ?? config.delete_with_command;
|
||||||
if (deleteWithCommand) {
|
if (deleteWithCommand) {
|
||||||
pluginData.state.savedMessages.onceMessageAvailable(responseMsg.id, async () => {
|
pluginData.state.savedMessages.onceMessageAvailable(responseMsg.id, async () => {
|
||||||
await pluginData.state.tags.addResponse(msg.id, responseMsg.id);
|
await pluginData.state.tags.addResponse(msg.id, responseMsg.id);
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
import { renderTemplate } from "src/templateFormatter";
|
import { renderTemplate } from "../../../templateFormatter";
|
||||||
import { PluginData, plugin } from "knub";
|
import { PluginData, plugin } from "knub";
|
||||||
import { TagsPluginType } from "../types";
|
import { Tag, TagsPluginType } from "../types";
|
||||||
|
import { renderRecursively, StrictMessageContent } from "../../../utils";
|
||||||
|
import * as t from "io-ts";
|
||||||
|
|
||||||
export async function renderTag(pluginData: PluginData<TagsPluginType>, body, args = [], extraData = {}) {
|
export async function renderTagBody(
|
||||||
|
pluginData: PluginData<TagsPluginType>,
|
||||||
|
body: t.TypeOf<typeof Tag>,
|
||||||
|
args = [],
|
||||||
|
extraData = {},
|
||||||
|
): Promise<StrictMessageContent> {
|
||||||
const dynamicVars = {};
|
const dynamicVars = {};
|
||||||
const maxTagFnCalls = 25;
|
const maxTagFnCalls = 25;
|
||||||
let tagFnCalls = 0;
|
let tagFnCalls = 0;
|
||||||
|
@ -22,6 +29,7 @@ export async function renderTag(pluginData: PluginData<TagsPluginType>, body, ar
|
||||||
if (tagFnCalls++ > maxTagFnCalls) return "\\_recursion\\_";
|
if (tagFnCalls++ > maxTagFnCalls) return "\\_recursion\\_";
|
||||||
if (typeof name !== "string") return "";
|
if (typeof name !== "string") return "";
|
||||||
if (name === "") return "";
|
if (name === "") return "";
|
||||||
|
|
||||||
// TODO: Incorporate tag categories here
|
// TODO: Incorporate tag categories here
|
||||||
const subTag = await pluginData.state.tags.find(name);
|
const subTag = await pluginData.state.tags.find(name);
|
||||||
if (!subTag) return "";
|
if (!subTag) return "";
|
||||||
|
@ -29,5 +37,11 @@ export async function renderTag(pluginData: PluginData<TagsPluginType>, body, ar
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return renderTemplate(body, data);
|
if (typeof body === "string") {
|
||||||
|
// Plain text tag
|
||||||
|
return { content: await renderTemplate(body, data) };
|
||||||
|
} else {
|
||||||
|
// Embed
|
||||||
|
return renderRecursively(body, str => renderTemplate(str, data));
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,12 +5,12 @@ import { renderRecursively, StrictMessageContent, stripObjectToScalars } from "s
|
||||||
import { parseArguments } from "knub-command-manager";
|
import { parseArguments } from "knub-command-manager";
|
||||||
import { TemplateParseError } from "src/templateFormatter";
|
import { TemplateParseError } from "src/templateFormatter";
|
||||||
import { PluginData } from "knub";
|
import { PluginData } from "knub";
|
||||||
import { renderTag } from "./renderTag";
|
|
||||||
import { logger } from "src/logger";
|
import { logger } from "src/logger";
|
||||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||||
import { LogType } from "../../../data/LogType";
|
import { LogType } from "../../../data/LogType";
|
||||||
|
import { renderTagBody } from "./renderTagBody";
|
||||||
|
|
||||||
export async function renderSafeTagFromMessage(
|
export async function renderTagFromString(
|
||||||
pluginData: PluginData<TagsPluginType>,
|
pluginData: PluginData<TagsPluginType>,
|
||||||
str: string,
|
str: string,
|
||||||
prefix: string,
|
prefix: string,
|
||||||
|
@ -21,21 +21,9 @@ export async function renderSafeTagFromMessage(
|
||||||
const variableStr = str.slice(prefix.length + tagName.length).trim();
|
const variableStr = str.slice(prefix.length + tagName.length).trim();
|
||||||
const tagArgs = parseArguments(variableStr).map(v => v.value);
|
const tagArgs = parseArguments(variableStr).map(v => v.value);
|
||||||
|
|
||||||
const renderTagString = async _str => {
|
|
||||||
let rendered = await renderTag(pluginData, _str, tagArgs, {
|
|
||||||
member: stripObjectToScalars(member, ["user"]),
|
|
||||||
user: stripObjectToScalars(member.user),
|
|
||||||
});
|
|
||||||
rendered = rendered.trim();
|
|
||||||
|
|
||||||
return rendered;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Format the string
|
// Format the string
|
||||||
try {
|
try {
|
||||||
return typeof tagBody === "string"
|
return renderTagBody(pluginData, tagBody, tagArgs);
|
||||||
? { content: await renderTagString(tagBody) }
|
|
||||||
: await renderRecursively(tagBody, renderTagString);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TemplateParseError) {
|
if (e instanceof TemplateParseError) {
|
||||||
const logs = pluginData.getPlugin(LogsPlugin);
|
const logs = pluginData.getPlugin(LogsPlugin);
|
Loading…
Add table
Reference in a new issue