commit
2893ddc21c
12 changed files with 521 additions and 0 deletions
76
backend/src/plugins/Tags/TagsPlugin.ts
Normal file
76
backend/src/plugins/Tags/TagsPlugin.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { zeppelinPlugin } from "../ZeppelinPluginBlueprint";
|
||||||
|
import { ConfigSchema, TagsPluginType } from "./types";
|
||||||
|
import { PluginOptions } from "knub";
|
||||||
|
import { GuildArchives } from "src/data/GuildArchives";
|
||||||
|
import { GuildTags } from "src/data/GuildTags";
|
||||||
|
import { GuildSavedMessages } from "src/data/GuildSavedMessages";
|
||||||
|
import { GuildLogs } from "src/data/GuildLogs";
|
||||||
|
import { onMessageCreate } from "./util/onMessageCreate";
|
||||||
|
import { onMessageDelete } from "./util/onMessageDelete";
|
||||||
|
import { TagCreateCmd } from "./commands/TagCreateCmd";
|
||||||
|
import { TagDeleteCmd } from "./commands/TagDeleteCmd";
|
||||||
|
import { TagEvalCmd } from "./commands/TagEvalCmd";
|
||||||
|
import { TagListCmd } from "./commands/TagListCmd";
|
||||||
|
import { TagSourceCmd } from "./commands/TagSourceCmd";
|
||||||
|
|
||||||
|
const defaultOptions: PluginOptions<TagsPluginType> = {
|
||||||
|
config: {
|
||||||
|
prefix: "!!",
|
||||||
|
delete_with_command: true,
|
||||||
|
|
||||||
|
user_tag_cooldown: null,
|
||||||
|
global_tag_cooldown: null,
|
||||||
|
user_cooldown: null,
|
||||||
|
global_cooldown: null,
|
||||||
|
|
||||||
|
categories: {},
|
||||||
|
|
||||||
|
can_create: false,
|
||||||
|
can_use: false,
|
||||||
|
can_list: false,
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
level: ">=50",
|
||||||
|
config: {
|
||||||
|
can_use: true,
|
||||||
|
can_create: true,
|
||||||
|
can_list: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TagsPlugin = zeppelinPlugin<TagsPluginType>()("tags", {
|
||||||
|
configSchema: ConfigSchema,
|
||||||
|
defaultOptions,
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
commands: [
|
||||||
|
TagEvalCmd,
|
||||||
|
TagCreateCmd,
|
||||||
|
TagDeleteCmd,
|
||||||
|
TagListCmd,
|
||||||
|
TagSourceCmd,
|
||||||
|
],
|
||||||
|
|
||||||
|
onLoad(pluginData) {
|
||||||
|
const { state, guild } = pluginData;
|
||||||
|
|
||||||
|
state.archives = GuildArchives.getGuildInstance(guild.id);
|
||||||
|
state.tags = GuildTags.getGuildInstance(guild.id);
|
||||||
|
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
|
||||||
|
state.logs = new GuildLogs(guild.id);
|
||||||
|
|
||||||
|
state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg);
|
||||||
|
state.savedMessages.events.on("create", state.onMessageCreateFn);
|
||||||
|
|
||||||
|
state.onMessageDeleteFn = msg => onMessageDelete(pluginData, msg);
|
||||||
|
state.savedMessages.events.on("delete", state.onMessageDeleteFn);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnload(pluginData) {
|
||||||
|
pluginData.state.savedMessages.events.off("create", pluginData.state.onMessageCreateFn);
|
||||||
|
pluginData.state.savedMessages.events.off("delete", pluginData.state.onMessageDeleteFn);
|
||||||
|
},
|
||||||
|
});
|
32
backend/src/plugins/Tags/commands/TagCreateCmd.ts
Normal file
32
backend/src/plugins/Tags/commands/TagCreateCmd.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { tagsCmd } from "../types";
|
||||||
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
|
import { parseTemplate, TemplateParseError } from "src/templateFormatter";
|
||||||
|
import { sendErrorMessage, sendSuccessMessage } from "src/pluginUtils";
|
||||||
|
|
||||||
|
export const TagCreateCmd = tagsCmd({
|
||||||
|
trigger: "tag",
|
||||||
|
permission: "can_create",
|
||||||
|
|
||||||
|
signature: {
|
||||||
|
tag: ct.string(),
|
||||||
|
body: ct.string({ catchAll: true }),
|
||||||
|
},
|
||||||
|
|
||||||
|
async run({ message: msg, args, pluginData }) {
|
||||||
|
try {
|
||||||
|
parseTemplate(args.body);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TemplateParseError) {
|
||||||
|
sendErrorMessage(pluginData, msg.channel, `Invalid tag syntax: ${e.message}`);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await pluginData.state.tags.createOrUpdate(args.tag, args.body, msg.author.id);
|
||||||
|
|
||||||
|
const prefix = pluginData.config.get().prefix;
|
||||||
|
sendSuccessMessage(pluginData, msg.channel, `Tag set! Use it with: \`${prefix}${args.tag}\``);
|
||||||
|
},
|
||||||
|
});
|
23
backend/src/plugins/Tags/commands/TagDeleteCmd.ts
Normal file
23
backend/src/plugins/Tags/commands/TagDeleteCmd.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { tagsCmd } from "../types";
|
||||||
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
|
import { sendErrorMessage, sendSuccessMessage } from "src/pluginUtils";
|
||||||
|
|
||||||
|
export const TagDeleteCmd = tagsCmd({
|
||||||
|
trigger: "tag delete",
|
||||||
|
permission: "can_create",
|
||||||
|
|
||||||
|
signature: {
|
||||||
|
tag: ct.string(),
|
||||||
|
},
|
||||||
|
|
||||||
|
async run({ message: msg, args, pluginData }) {
|
||||||
|
const tag = await pluginData.state.tags.find(args.tag);
|
||||||
|
if (!tag) {
|
||||||
|
sendErrorMessage(pluginData, msg.channel, "No tag with that name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await pluginData.state.tags.delete(args.tag);
|
||||||
|
sendSuccessMessage(pluginData, msg.channel, "Tag deleted!");
|
||||||
|
},
|
||||||
|
});
|
17
backend/src/plugins/Tags/commands/TagEvalCmd.ts
Normal file
17
backend/src/plugins/Tags/commands/TagEvalCmd.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { tagsCmd } from "../types";
|
||||||
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
|
import { renderTag } from "../util/renderTag";
|
||||||
|
|
||||||
|
export const TagEvalCmd = tagsCmd({
|
||||||
|
trigger: "tag eval",
|
||||||
|
permission: "can_create",
|
||||||
|
|
||||||
|
signature: {
|
||||||
|
body: ct.string({ catchAll: true }),
|
||||||
|
},
|
||||||
|
|
||||||
|
async run({ message: msg, args, pluginData }) {
|
||||||
|
const rendered = await renderTag(pluginData, args.body);
|
||||||
|
msg.channel.createMessage(rendered);
|
||||||
|
},
|
||||||
|
});
|
18
backend/src/plugins/Tags/commands/TagListCmd.ts
Normal file
18
backend/src/plugins/Tags/commands/TagListCmd.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { tagsCmd } from "../types";
|
||||||
|
|
||||||
|
export const TagListCmd = tagsCmd({
|
||||||
|
trigger: ["tag list", "tags", "taglist"],
|
||||||
|
permission: "can_list",
|
||||||
|
|
||||||
|
async run({ message: msg, pluginData }) {
|
||||||
|
const tags = await pluginData.state.tags.all();
|
||||||
|
if (tags.length === 0) {
|
||||||
|
msg.channel.createMessage(`No tags created yet! Use \`tag create\` command to create one.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = pluginData.config.getForMessage(msg).prefix;
|
||||||
|
const tagNames = tags.map(tag => tag.tag).sort();
|
||||||
|
msg.channel.createMessage(`Available tags (use with ${prefix}tag): \`\`\`${tagNames.join(", ")}\`\`\``);
|
||||||
|
},
|
||||||
|
});
|
40
backend/src/plugins/Tags/commands/TagSourceCmd.ts
Normal file
40
backend/src/plugins/Tags/commands/TagSourceCmd.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { tagsCmd } from "../types";
|
||||||
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
|
import { sendErrorMessage, getBaseUrl, sendSuccessMessage } from "src/pluginUtils";
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
|
||||||
|
export const TagSourceCmd = tagsCmd({
|
||||||
|
trigger: "tag",
|
||||||
|
permission: "can_create",
|
||||||
|
|
||||||
|
signature: {
|
||||||
|
tag: ct.string(),
|
||||||
|
|
||||||
|
delete: ct.bool({ option: true, shortcut: "d", isSwitch: true }),
|
||||||
|
},
|
||||||
|
|
||||||
|
async run({ message: msg, args, pluginData }) {
|
||||||
|
if (args.delete) {
|
||||||
|
const actualTag = await pluginData.state.tags.find(args.tag);
|
||||||
|
if (!actualTag) {
|
||||||
|
sendErrorMessage(pluginData, msg.channel, "No tag with that name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await pluginData.state.tags.delete(args.tag);
|
||||||
|
sendSuccessMessage(pluginData, msg.channel, "Tag deleted!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = await pluginData.state.tags.find(args.tag);
|
||||||
|
if (!tag) {
|
||||||
|
sendErrorMessage(pluginData, msg.channel, "No tag with that name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const archiveId = await pluginData.state.archives.create(tag.body, moment().add(10, "minutes"));
|
||||||
|
const url = pluginData.state.archives.getUrl(getBaseUrl(pluginData), archiveId);
|
||||||
|
|
||||||
|
msg.channel.createMessage(`Tag source:\n${url}`);
|
||||||
|
},
|
||||||
|
});
|
58
backend/src/plugins/Tags/types.ts
Normal file
58
backend/src/plugins/Tags/types.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import * as t from "io-ts";
|
||||||
|
import { BasePluginType, command, eventListener } from "knub";
|
||||||
|
import { tNullable, tEmbed } from "src/utils";
|
||||||
|
import { GuildArchives } from "src/data/GuildArchives";
|
||||||
|
import { GuildTags } from "src/data/GuildTags";
|
||||||
|
import { GuildSavedMessages } from "src/data/GuildSavedMessages";
|
||||||
|
import { GuildLogs } from "src/data/GuildLogs";
|
||||||
|
|
||||||
|
export const Tag = t.union([t.string, tEmbed]);
|
||||||
|
|
||||||
|
const TagCategory = t.type({
|
||||||
|
prefix: tNullable(t.string),
|
||||||
|
delete_with_command: tNullable(t.boolean),
|
||||||
|
|
||||||
|
user_tag_cooldown: tNullable(t.union([t.string, t.number])), // Per user, per tag
|
||||||
|
user_category_cooldown: tNullable(t.union([t.string, t.number])), // Per user, per tag category
|
||||||
|
global_tag_cooldown: tNullable(t.union([t.string, t.number])), // Any user, per tag
|
||||||
|
global_category_cooldown: tNullable(t.union([t.string, t.number])), // Any user, per category
|
||||||
|
|
||||||
|
tags: t.record(t.string, Tag),
|
||||||
|
|
||||||
|
can_use: tNullable(t.boolean),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ConfigSchema = t.type({
|
||||||
|
prefix: t.string,
|
||||||
|
delete_with_command: t.boolean,
|
||||||
|
|
||||||
|
user_tag_cooldown: tNullable(t.union([t.string, t.number])), // Per user, per tag
|
||||||
|
global_tag_cooldown: tNullable(t.union([t.string, t.number])), // Any user, per tag
|
||||||
|
user_cooldown: tNullable(t.union([t.string, t.number])), // Per user
|
||||||
|
global_cooldown: tNullable(t.union([t.string, t.number])), // Any tag use
|
||||||
|
|
||||||
|
categories: t.record(t.string, TagCategory),
|
||||||
|
|
||||||
|
can_create: t.boolean,
|
||||||
|
can_use: t.boolean,
|
||||||
|
can_list: t.boolean,
|
||||||
|
});
|
||||||
|
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||||
|
|
||||||
|
export interface TagsPluginType extends BasePluginType {
|
||||||
|
config: TConfigSchema;
|
||||||
|
state: {
|
||||||
|
archives: GuildArchives;
|
||||||
|
tags: GuildTags;
|
||||||
|
savedMessages: GuildSavedMessages;
|
||||||
|
logs: GuildLogs;
|
||||||
|
|
||||||
|
onMessageCreateFn;
|
||||||
|
onMessageDeleteFn;
|
||||||
|
|
||||||
|
tagFunctions: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tagsCmd = command<TagsPluginType>();
|
||||||
|
export const tagsEvent = eventListener<TagsPluginType>();
|
145
backend/src/plugins/Tags/util/onMessageCreate.ts
Normal file
145
backend/src/plugins/Tags/util/onMessageCreate.ts
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
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 { validate } from "src/validatorUtils";
|
||||||
|
import { LogType } from "src/data/LogType";
|
||||||
|
import { TextChannel } from "eris";
|
||||||
|
import { renderSafeTagFromMessage } from "./renderSafeTagFromMessage";
|
||||||
|
|
||||||
|
export async function onMessageCreate(pluginData: PluginData<TagsPluginType>, msg: SavedMessage) {
|
||||||
|
if (msg.is_bot) return;
|
||||||
|
if (!msg.data.content) return;
|
||||||
|
|
||||||
|
const member = await resolveMember(pluginData.client, pluginData.guild, msg.user_id);
|
||||||
|
if (!member) return;
|
||||||
|
|
||||||
|
const config = pluginData.config.getMatchingConfig({ member, channelId: msg.channel_id });
|
||||||
|
let deleteWithCommand = false;
|
||||||
|
|
||||||
|
// Find potential matching tag, looping through categories first and checking dynamic tags last
|
||||||
|
let renderedTag = null;
|
||||||
|
let matchedTagName;
|
||||||
|
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 (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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matching tag was not found from the config, try a dynamic tag
|
||||||
|
if (!renderedTag) {
|
||||||
|
if (config.can_use !== true) return;
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOnCooldown = cooldowns.some(cd => pluginData.cooldowns.isOnCooldown(cd[0]));
|
||||||
|
if (isOnCooldown) return;
|
||||||
|
|
||||||
|
for (const cd of cooldowns) {
|
||||||
|
pluginData.cooldowns.setCooldown(cd[0], cd[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteWithCommand = config.delete_with_command;
|
||||||
|
|
||||||
|
const validationError = await validate(tStrictMessageContent, renderedTag);
|
||||||
|
if (validationError) {
|
||||||
|
pluginData.state.logs.log(LogType.BOT_ALERT, {
|
||||||
|
body: `Rendering tag ${matchedTagName} resulted in an invalid message: ${validationError.message}`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = pluginData.guild.channels.get(msg.channel_id) as TextChannel;
|
||||||
|
const responseMsg = await channel.createMessage(renderedTag);
|
||||||
|
|
||||||
|
// Save the command-response message pair once the message is in our database
|
||||||
|
if (deleteWithCommand) {
|
||||||
|
pluginData.state.savedMessages.onceMessageAvailable(responseMsg.id, async () => {
|
||||||
|
await pluginData.state.tags.addResponse(msg.id, responseMsg.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
32
backend/src/plugins/Tags/util/onMessageDelete.ts
Normal file
32
backend/src/plugins/Tags/util/onMessageDelete.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { PluginData } from "knub";
|
||||||
|
import { TagsPluginType } from "../types";
|
||||||
|
import { SavedMessage } from "src/data/entities/SavedMessage";
|
||||||
|
import { TextChannel } from "eris";
|
||||||
|
|
||||||
|
export async function onMessageDelete(pluginData: PluginData<TagsPluginType>, msg: SavedMessage) {
|
||||||
|
// Command message was deleted -> delete the response as well
|
||||||
|
const commandMsgResponse = await pluginData.state.tags.findResponseByCommandMessageId(msg.id);
|
||||||
|
if (commandMsgResponse) {
|
||||||
|
const channel = pluginData.guild.channels.get(msg.channel_id) as TextChannel;
|
||||||
|
if (!channel) return;
|
||||||
|
|
||||||
|
const responseMsg = await pluginData.state.savedMessages.find(commandMsgResponse.response_message_id);
|
||||||
|
if (!responseMsg || responseMsg.deleted_at != null) return;
|
||||||
|
|
||||||
|
await channel.deleteMessage(commandMsgResponse.response_message_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response was deleted -> delete the command message as well
|
||||||
|
const responseMsgResponse = await pluginData.state.tags.findResponseByResponseMessageId(msg.id);
|
||||||
|
if (responseMsgResponse) {
|
||||||
|
const channel = pluginData.guild.channels.get(msg.channel_id) as TextChannel;
|
||||||
|
if (!channel) return;
|
||||||
|
|
||||||
|
const commandMsg = await pluginData.state.savedMessages.find(responseMsgResponse.command_message_id);
|
||||||
|
if (!commandMsg || commandMsg.deleted_at != null) return;
|
||||||
|
|
||||||
|
await channel.deleteMessage(responseMsgResponse.command_message_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
45
backend/src/plugins/Tags/util/renderSafeTagFromMessage.ts
Normal file
45
backend/src/plugins/Tags/util/renderSafeTagFromMessage.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { Tag, TagsPluginType } from "../types";
|
||||||
|
import { Member } from "eris";
|
||||||
|
import * as t from "io-ts";
|
||||||
|
import { StrictMessageContent, stripObjectToScalars, renderRecursively } from "src/utils";
|
||||||
|
import { parseArguments } from "knub-command-manager";
|
||||||
|
import { TemplateParseError } from "src/templateFormatter";
|
||||||
|
import { PluginData } from "knub";
|
||||||
|
import { renderTag } from "./renderTag";
|
||||||
|
import { logger } from "src/logger";
|
||||||
|
|
||||||
|
export async function renderSafeTagFromMessage(
|
||||||
|
pluginData: PluginData<TagsPluginType>,
|
||||||
|
str: string,
|
||||||
|
prefix: string,
|
||||||
|
tagName: string,
|
||||||
|
tagBody: t.TypeOf<typeof Tag>,
|
||||||
|
member: Member,
|
||||||
|
): Promise<StrictMessageContent | null> {
|
||||||
|
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);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TemplateParseError) {
|
||||||
|
logger.warn(`Invalid tag format!\nError: ${e.message}\nFormat: ${tagBody}`);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
backend/src/plugins/Tags/util/renderTag.ts
Normal file
33
backend/src/plugins/Tags/util/renderTag.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { renderTemplate } from "src/templateFormatter";
|
||||||
|
import { PluginData, plugin } from "knub";
|
||||||
|
import { TagsPluginType } from "../types";
|
||||||
|
|
||||||
|
export async function renderTag(pluginData: PluginData<TagsPluginType>, body, args = [], extraData = {}) {
|
||||||
|
const dynamicVars = {};
|
||||||
|
const maxTagFnCalls = 25;
|
||||||
|
let tagFnCalls = 0;
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
args,
|
||||||
|
...extraData,
|
||||||
|
...pluginData.state.tagFunctions,
|
||||||
|
set(name, val) {
|
||||||
|
if (typeof name !== "string") return;
|
||||||
|
dynamicVars[name] = val;
|
||||||
|
},
|
||||||
|
get(name) {
|
||||||
|
return dynamicVars[name] == null ? "" : dynamicVars[name];
|
||||||
|
},
|
||||||
|
tag: async (name, ...subTagArgs) => {
|
||||||
|
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 "";
|
||||||
|
return renderTemplate(subTag.body, { ...data, args: subTagArgs });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return renderTemplate(body, data);
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import { PingableRolesPlugin } from "./PingableRoles/PingableRolesPlugin";
|
||||||
import { GuildConfigReloaderPlugin } from "./GuildConfigReloader/GuildConfigReloaderPlugin";
|
import { GuildConfigReloaderPlugin } from "./GuildConfigReloader/GuildConfigReloaderPlugin";
|
||||||
import { CasesPlugin } from "./Cases/CasesPlugin";
|
import { CasesPlugin } from "./Cases/CasesPlugin";
|
||||||
import { MutesPlugin } from "./Mutes/MutesPlugin";
|
import { MutesPlugin } from "./Mutes/MutesPlugin";
|
||||||
|
import { TagsPlugin } from "./Tags/TagsPlugin";
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export const guildPlugins: Array<ZeppelinPluginBlueprint<any>> = [
|
export const guildPlugins: Array<ZeppelinPluginBlueprint<any>> = [
|
||||||
|
@ -22,6 +23,7 @@ export const guildPlugins: Array<ZeppelinPluginBlueprint<any>> = [
|
||||||
MessageSaverPlugin,
|
MessageSaverPlugin,
|
||||||
NameHistoryPlugin,
|
NameHistoryPlugin,
|
||||||
RemindersPlugin,
|
RemindersPlugin,
|
||||||
|
TagsPlugin,
|
||||||
UsernameSaverPlugin,
|
UsernameSaverPlugin,
|
||||||
UtilityPlugin,
|
UtilityPlugin,
|
||||||
WelcomeMessagePlugin,
|
WelcomeMessagePlugin,
|
||||||
|
|
Loading…
Add table
Reference in a new issue