3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-14 21:31:50 +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 { commandTypeHelpers as ct } from "../../../commandTypes";
import { renderTag } from "../util/renderTag";
import { MessageContent } from "eris";
import { TemplateParseError } from "../../../templateFormatter";
import { sendErrorMessage } from "../../../pluginUtils";
import { renderTagBody } from "../util/renderTagBody";
export const TagEvalCmd = tagsCmd({
trigger: "tag eval",
@ -15,7 +15,7 @@ export const TagEvalCmd = tagsCmd({
async run({ message: msg, args, pluginData }) {
try {
const rendered = await renderTag(pluginData, args.body);
const rendered = await renderTagBody(pluginData, args.body);
msg.channel.createMessage(rendered);
} catch (e) {
if (e instanceof TemplateParseError) {

View file

@ -8,7 +8,7 @@ import { GuildLogs } from "src/data/GuildLogs";
export const Tag = t.union([t.string, tEmbed]);
const TagCategory = t.type({
export const TagCategory = t.type({
prefix: tNullable(t.string),
delete_with_command: tNullable(t.boolean),
@ -21,6 +21,7 @@ const TagCategory = t.type({
can_use: tNullable(t.boolean),
});
export type TTagCategory = t.TypeOf<typeof TagCategory>;
export const ConfigSchema = t.type({
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 { SavedMessage } from "src/data/entities/SavedMessage";
import { PluginData } from "knub";
import { resolveMember, convertDelayStringToMS, tStrictMessageContent } from "src/utils";
import escapeStringRegexp from "escape-string-regexp";
import { convertDelayStringToMS, resolveMember, tStrictMessageContent } from "src/utils";
import { validate } from "src/validatorUtils";
import { LogType } from "src/data/LogType";
import { TextChannel } from "eris";
import { renderSafeTagFromMessage } from "./renderSafeTagFromMessage";
import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString";
export async function onMessageCreate(pluginData: PluginData<TagsPluginType>, msg: SavedMessage) {
if (msg.is_bot) return;
@ -21,104 +20,58 @@ export async function onMessageCreate(pluginData: PluginData<TagsPluginType>, ms
channelId: msg.channel_id,
categoryId: channel.parentID,
});
let deleteWithCommand = false;
// Find potential matching tag, looping through categories first and checking dynamic tags last
let renderedTag = null;
let matchedTagName;
const tagResult = await matchAndRenderTagFromString(pluginData, msg.data.content, member, {
channelId: msg.channel_id,
categoryId: channel.parentID,
});
if (!tagResult) {
return;
}
// Check for cooldowns
const cooldowns = [];
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 !== "" && !msg.data.content.startsWith(prefix)) continue;
const withoutPrefix = msg.data.content.slice(prefix.length);
for (const [tagName, tagBody] of Object.entries(category.tags)) {
const regex = new RegExp(`^${escapeStringRegexp(tagName)}(?:\\s|$)`);
if (regex.test(withoutPrefix)) {
renderedTag = await renderSafeTagFromMessage(
pluginData,
msg.data.content,
prefix,
tagName,
category.tags[tagName],
member,
);
if (renderedTag) {
matchedTagName = tagName;
break;
}
}
if (tagResult.category) {
// Category-specific cooldowns
if (tagResult.category.user_tag_cooldown) {
const delay = convertDelayStringToMS(String(tagResult.category.user_tag_cooldown), "s");
cooldowns.push([`tags-category-${tagResult.categoryName}-user-${msg.user_id}-tag-${tagResult.tagName}`, delay]);
}
if (tagResult.category.global_tag_cooldown) {
const delay = convertDelayStringToMS(String(tagResult.category.global_tag_cooldown), "s");
cooldowns.push([`tags-category-${tagResult.categoryName}-tag-${tagResult.tagName}`, delay]);
}
if (tagResult.category.user_category_cooldown) {
const delay = convertDelayStringToMS(String(tagResult.category.user_category_cooldown), "s");
cooldowns.push([`tags-category-${tagResult.categoryName}-user--${msg.user_id}`, delay]);
}
if (tagResult.category.global_category_cooldown) {
const delay = convertDelayStringToMS(String(tagResult.category.global_category_cooldown), "s");
cooldowns.push([`tags-category-${tagResult.categoryName}`, delay]);
}
} else {
// Dynamic tag cooldowns
if (config.user_tag_cooldown) {
const delay = convertDelayStringToMS(String(config.user_tag_cooldown), "s");
cooldowns.push([`tags-user-${msg.user_id}-tag-${tagResult.tagName}`, delay]);
}
if (renderedTag) {
if (category.user_tag_cooldown) {
const delay = convertDelayStringToMS(String(category.user_tag_cooldown), "s");
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;
if (config.global_tag_cooldown) {
const delay = convertDelayStringToMS(String(config.global_tag_cooldown), "s");
cooldowns.push([`tags-tag-${tagResult.tagName}`, delay]);
}
}
// Matching tag was not found from the config, try a dynamic tag
if (!renderedTag) {
if (config.can_use !== true) return;
if (config.user_cooldown) {
const delay = convertDelayStringToMS(String(config.user_cooldown), "s");
cooldowns.push([`tags-user-${tagResult.tagName}`, delay]);
}
const prefix = config.prefix;
if (!msg.data.content.startsWith(prefix)) return;
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]);
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]));
@ -128,26 +81,25 @@ export async function onMessageCreate(pluginData: PluginData<TagsPluginType>, ms
pluginData.cooldowns.setCooldown(cd[0], cd[1]);
}
deleteWithCommand = config.delete_with_command;
const validationError = await validate(tStrictMessageContent, renderedTag);
const validationError = await validate(tStrictMessageContent, tagResult.renderedContent);
if (validationError) {
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;
}
if (typeof renderedTag === "string" && renderedTag.trim() === "") {
if (typeof tagResult.renderedContent === "string" && tagResult.renderedContent.trim() === "") {
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;
}
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
const deleteWithCommand = tagResult.category?.delete_with_command ?? config.delete_with_command;
if (deleteWithCommand) {
pluginData.state.savedMessages.onceMessageAvailable(responseMsg.id, async () => {
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 { 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 maxTagFnCalls = 25;
let tagFnCalls = 0;
@ -22,6 +29,7 @@ export async function renderTag(pluginData: PluginData<TagsPluginType>, body, ar
if (tagFnCalls++ > maxTagFnCalls) return "\\_recursion\\_";
if (typeof name !== "string") return "";
if (name === "") return "";
// TODO: Incorporate tag categories here
const subTag = await pluginData.state.tags.find(name);
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 { TemplateParseError } from "src/templateFormatter";
import { PluginData } from "knub";
import { renderTag } from "./renderTag";
import { logger } from "src/logger";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { LogType } from "../../../data/LogType";
import { renderTagBody } from "./renderTagBody";
export async function renderSafeTagFromMessage(
export async function renderTagFromString(
pluginData: PluginData<TagsPluginType>,
str: string,
prefix: string,
@ -21,21 +21,9 @@ export async function renderSafeTagFromMessage(
const variableStr = str.slice(prefix.length + tagName.length).trim();
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
try {
return typeof tagBody === "string"
? { content: await renderTagString(tagBody) }
: await renderRecursively(tagBody, renderTagString);
return renderTagBody(pluginData, tagBody, tagArgs);
} catch (e) {
if (e instanceof TemplateParseError) {
const logs = pluginData.getPlugin(LogsPlugin);