3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-05-25 10:25:01 +00:00

feat: add tag aliases support

This commit is contained in:
Rstar284 2022-04-22 15:52:39 +04:00
parent 280543df4e
commit e171037d5e
No known key found for this signature in database
GPG key ID: FB4E41B2606FAE1A
10 changed files with 162 additions and 30 deletions

View file

@ -0,0 +1,66 @@
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { TagAlias } from "./entities/TagAlias";
export class GuildTagAliases extends BaseGuildRepository {
private tagAliases: Repository<TagAlias>;
constructor(guildId) {
super(guildId);
this.tagAliases = getRepository(TagAlias);
}
async all(): Promise<TagAlias[]> {
return this.tagAliases.find({
where: {
guild_id: this.guildId,
},
});
}
async find(alias): Promise<TagAlias | undefined> {
return this.tagAliases.findOne({
where: {
guild_id: this.guildId,
alias,
},
});
}
async findAllWithTag(tag): Promise<TagAlias[] | undefined> {
const all = await this.all();
const aliases = all.filter((a) => a.tag === tag);
return aliases.length > 0 ? aliases : undefined;
}
async createOrUpdate(alias, tag, userId) {
const existingTagAlias = await this.find(alias);
if (existingTagAlias) {
await this.tagAliases
.createQueryBuilder()
.update()
.set({
tag,
user_id: userId,
created_at: () => "NOW()",
})
.where("guild_id = :guildId", { guildId: this.guildId })
.andWhere("alias = :alias", { alias })
.execute();
} else {
await this.tagAliases.insert({
guild_id: this.guildId,
user_id: userId,
alias,
tag,
});
}
}
async delete(alias) {
await this.tagAliases.delete({
guild_id: this.guildId,
alias,
});
}
}

View file

@ -0,0 +1,20 @@
import { Column, Entity, PrimaryColumn } from "typeorm";
@Entity("tag_aliases")
export class TagAlias {
@Column()
@PrimaryColumn()
guild_id: string;
@Column()
@PrimaryColumn()
alias: string;
@Column()
@PrimaryColumn()
tag: string;
@Column() user_id: string;
@Column() created_at: string;
}

View file

@ -22,6 +22,7 @@ import { onMessageCreate } from "./util/onMessageCreate";
import { onMessageDelete } from "./util/onMessageDelete"; import { onMessageDelete } from "./util/onMessageDelete";
import { renderTagBody } from "./util/renderTagBody"; import { renderTagBody } from "./util/renderTagBody";
import { LogsPlugin } from "../Logs/LogsPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin";
import { GuildTagAliases } from "src/data/GuildTagAliases";
const defaultOptions: PluginOptions<TagsPluginType> = { const defaultOptions: PluginOptions<TagsPluginType> = {
config: { config: {
@ -110,6 +111,7 @@ export const TagsPlugin = zeppelinGuildPlugin<TagsPluginType>()({
state.archives = GuildArchives.getGuildInstance(guild.id); state.archives = GuildArchives.getGuildInstance(guild.id);
state.tags = GuildTags.getGuildInstance(guild.id); state.tags = GuildTags.getGuildInstance(guild.id);
state.tagAliases = GuildTagAliases.getGuildInstance(guild.id);
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
state.logs = new GuildLogs(guild.id); state.logs = new GuildLogs(guild.id);

View file

@ -8,11 +8,19 @@ export const TagCreateCmd = tagsCmd({
permission: "can_create", permission: "can_create",
signature: { signature: {
alias: ct.bool({ option: true, shortcut: "a", isSwitch: true }),
tag: ct.string(), tag: ct.string(),
body: ct.string({ catchAll: true }), body: ct.string({ catchAll: true }),
}, },
async run({ message: msg, args, pluginData }) { async run({ message: msg, args, pluginData }) {
const prefix = pluginData.config.get().prefix;
if (args.alias) {
await pluginData.state.tagAliases.createOrUpdate(args.tag, args.body, msg.author.id);
sendSuccessMessage(pluginData, msg.channel, `Alias set! Use it with: \`${prefix}${args.tag}\``);
return;
}
try { try {
parseTemplate(args.body); parseTemplate(args.body);
} catch (e) { } catch (e) {
@ -26,7 +34,6 @@ export const TagCreateCmd = tagsCmd({
await pluginData.state.tags.createOrUpdate(args.tag, args.body, msg.author.id); 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}\``); sendSuccessMessage(pluginData, msg.channel, `Tag set! Use it with: \`${prefix}${args.tag}\``);
}, },
}); });

View file

@ -11,13 +11,25 @@ export const TagDeleteCmd = tagsCmd({
}, },
async run({ message: msg, args, pluginData }) { async run({ message: msg, args, pluginData }) {
const alias = await pluginData.state.tagAliases.find(args.tag);
const tag = await pluginData.state.tags.find(args.tag); const tag = await pluginData.state.tags.find(args.tag);
if (!tag) {
if (!tag && !alias) {
sendErrorMessage(pluginData, msg.channel, "No tag with that name"); sendErrorMessage(pluginData, msg.channel, "No tag with that name");
return; return;
} }
await pluginData.state.tags.delete(args.tag); if (tag) {
sendSuccessMessage(pluginData, msg.channel, "Tag deleted!"); const aliasesOfTag = await pluginData.state.tagAliases.findAllWithTag(tag?.tag);
if (aliasesOfTag) {
// tslint:disable-next-line:no-shadowed-variable
aliasesOfTag.forEach((alias) => pluginData.state.tagAliases.delete(alias.alias));
}
await pluginData.state.tags.delete(args.tag);
} else {
await pluginData.state.tagAliases.delete(alias?.alias);
}
sendSuccessMessage(pluginData, msg.channel, `${tag ? "Tag" : "Alias"} deleted!`);
}, },
}); });

View file

@ -7,6 +7,7 @@ export const TagListCmd = tagsCmd({
async run({ message: msg, pluginData }) { async run({ message: msg, pluginData }) {
const tags = await pluginData.state.tags.all(); const tags = await pluginData.state.tags.all();
const aliases = await pluginData.state.tagAliases.all();
if (tags.length === 0) { if (tags.length === 0) {
msg.channel.send(`No tags created yet! Use \`tag create\` command to create one.`); msg.channel.send(`No tags created yet! Use \`tag create\` command to create one.`);
return; return;
@ -14,7 +15,11 @@ export const TagListCmd = tagsCmd({
const prefix = (await pluginData.config.getForMessage(msg)).prefix; const prefix = (await pluginData.config.getForMessage(msg)).prefix;
const tagNames = tags.map((tag) => tag.tag).sort(); const tagNames = tags.map((tag) => tag.tag).sort();
const tagAliasesNames = aliases.map((alias) => alias.alias).sort();
const tagAndAliasesNames = tagNames
.join(", ")
.concat(tagAliasesNames.length > 0 ? `, ${tagAliasesNames.join(", ")}` : "");
createChunkedMessage(msg.channel, `Available tags (use with ${prefix}tag): \`\`\`${tagNames.join(", ")}\`\`\``); createChunkedMessage(msg.channel, `Available tags (use with ${prefix}tag): \`\`\`${tagAndAliasesNames}\`\`\``);
}, },
}); });

View file

@ -14,19 +14,23 @@ export const TagSourceCmd = tagsCmd({
}, },
async run({ message: msg, args, pluginData }) { async run({ message: msg, args, pluginData }) {
const alias = await pluginData.state.tagAliases.find(args.tag);
const tag = (await pluginData.state.tags.find(args.tag)) || (await pluginData.state.tags.find(alias?.tag ?? null));
if (args.delete) { if (args.delete) {
const actualTag = await pluginData.state.tags.find(args.tag); const actualTag = await pluginData.state.tags.find(args.tag);
if (!actualTag) { const aliasedTag = await pluginData.state.tags.find(alias?.tag ?? null);
if (!actualTag && !aliasedTag) {
sendErrorMessage(pluginData, msg.channel, "No tag with that name"); sendErrorMessage(pluginData, msg.channel, "No tag with that name");
return; return;
} }
await pluginData.state.tags.delete(args.tag); actualTag ? pluginData.state.tags.delete(args.tag) : pluginData.state.tagAliases.delete(args.tag);
sendSuccessMessage(pluginData, msg.channel, "Tag deleted!"); sendSuccessMessage(pluginData, msg.channel, `${actualTag ? "Tag" : "Alias"} deleted!`);
return; return;
} }
const tag = await pluginData.state.tags.find(args.tag);
if (!tag) { if (!tag) {
sendErrorMessage(pluginData, msg.channel, "No tag with that name"); sendErrorMessage(pluginData, msg.channel, "No tag with that name");
return; return;

View file

@ -4,6 +4,7 @@ import { GuildArchives } from "../../data/GuildArchives";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildTags } from "../../data/GuildTags"; import { GuildTags } from "../../data/GuildTags";
import { GuildTagAliases } from "../../data/GuildTagAliases";
import { tEmbed, tNullable } from "../../utils"; import { tEmbed, tNullable } from "../../utils";
export const Tag = t.union([t.string, tEmbed]); export const Tag = t.union([t.string, tEmbed]);
@ -50,6 +51,7 @@ export interface TagsPluginType extends BasePluginType {
state: { state: {
archives: GuildArchives; archives: GuildArchives;
tags: GuildTags; tags: GuildTags;
tagAliases: GuildTagAliases;
savedMessages: GuildSavedMessages; savedMessages: GuildSavedMessages;
logs: GuildLogs; logs: GuildLogs;

View file

@ -20,8 +20,18 @@ export async function findTagByName(
return config.categories[categoryName]?.tags[tagName] ?? null; return config.categories[categoryName]?.tags[tagName] ?? null;
} }
let tag: string | null;
// Dynamic tag // Dynamic tag
// Format: "tag" // Format: "tag"
const dynamicTag = await pluginData.state.tags.find(name); const dynamicTag = await pluginData.state.tags.find(name);
return dynamicTag?.body ?? null; tag = dynamicTag?.body ?? null;
// Aliased tag
// Format: "alias"
const aliasedTagName = await pluginData.state.tagAliases.find(name);
const aliasedTag = await pluginData.state.tags.find(aliasedTagName?.tag);
tag ? (tag = tag) : (tag = aliasedTag?.body ?? null);
return tag;
} }

View file

@ -44,7 +44,8 @@ export async function matchAndRenderTagFromString(
const withoutPrefix = str.slice(prefix.length); const withoutPrefix = str.slice(prefix.length);
for (const [tagName, tagBody] of Object.entries(category.tags)) { // tslint:disable-next-line:no-shadowed-variable
for (const [tagName, _tagBody] of Object.entries(category.tags)) {
const regex = new RegExp(`^${escapeStringRegexp(tagName)}(?:\\s|$)`); const regex = new RegExp(`^${escapeStringRegexp(tagName)}(?:\\s|$)`);
if (regex.test(withoutPrefix)) { if (regex.test(withoutPrefix)) {
const renderedContent = await renderTagFromString( const renderedContent = await renderTagFromString(
@ -70,43 +71,46 @@ export async function matchAndRenderTagFromString(
} }
} }
// Dynamic tags // Dynamic + Aliased tags
if (config.can_use !== true) { if (config.can_use !== true) {
return null; return null;
} }
const dynamicTagPrefix = config.prefix; const tagPrefix = config.prefix;
if (!str.startsWith(dynamicTagPrefix)) { if (!str.startsWith(tagPrefix)) {
return null; return null;
} }
const dynamicTagNameMatch = str.slice(dynamicTagPrefix.length).match(/^\S+/); const tagNameMatch = str.slice(tagPrefix.length).match(/^[a-z0-9_-]*/);
if (dynamicTagNameMatch === null) { if (tagNameMatch == null) {
return null; return null;
} }
const dynamicTagName = dynamicTagNameMatch[0]; const tagName = tagNameMatch[0];
const dynamicTag = await pluginData.state.tags.find(dynamicTagName);
if (!dynamicTag) { const aliasName = await pluginData.state.tagAliases.find(tagName);
const aliasedTag = await pluginData.state.tags.find(aliasName?.tag);
const dynamicTag = await pluginData.state.tags.find(tagName);
if (!aliasedTag && !dynamicTag) {
return null; return null;
} }
const renderedDynamicTagContent = await renderTagFromString( const tagBody = aliasedTag?.body ?? dynamicTag?.body;
pluginData,
str,
dynamicTagPrefix,
dynamicTagName,
dynamicTag.body,
member,
);
if (renderedDynamicTagContent == null) { if (!tagBody) {
return null;
}
const renderedTagContent = await renderTagFromString(pluginData, str, tagPrefix, tagName, tagBody, member);
if (renderedTagContent == null) {
return null; return null;
} }
return { return {
renderedContent: renderedDynamicTagContent, renderedContent: renderedTagContent,
tagName: dynamicTagName, tagName,
categoryName: null, categoryName: null,
category: null, category: null,
}; };