3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-15 05:41:51 +00:00

Reorganize/clean up TagsPlugin code

This commit is contained in:
Dragory 2020-09-15 01:37:32 +03:00
parent b758c9cfe0
commit fc8b78fb55
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
6 changed files with 171 additions and 122 deletions

View file

@ -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) {

View file

@ -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,

View 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,
};
}

View file

@ -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);

View file

@ -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));
}
} }

View file

@ -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);