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

templateFormatter: return empty string for null/undefined variables; Tags: catch template syntax errors on tag creation; add mention() tag function

This commit is contained in:
Dragory 2019-03-16 16:59:01 +02:00
parent db5d93b5c2
commit f27da2c56f
2 changed files with 55 additions and 23 deletions

View file

@ -1,4 +1,4 @@
import { Plugin, decorators as d, IPluginOptions } from "knub";
import { decorators as d, IPluginOptions } from "knub";
import { Message, TextChannel } from "eris";
import { errorMessage, successMessage } from "../utils";
import { GuildTags } from "../data/GuildTags";
@ -7,20 +7,9 @@ import { SavedMessage } from "../data/entities/SavedMessage";
import moment from "moment-timezone";
import humanizeDuration from "humanize-duration";
import { ZeppelinPlugin } from "./ZeppelinPlugin";
import { renderTemplate } from "../templateFormatter";
import { escapeBacktickString } from "jest-snapshot/build/utils";
import { parseTemplate, renderTemplate, TemplateParseError } from "../templateFormatter";
import { GuildArchives } from "../data/GuildArchives";
const TAG_FUNCTIONS = {
countdown(toDate) {
const now = moment();
const target = moment(toDate, "YYYY-MM-DD HH:mm:ss");
const diff = target.diff(now);
const result = humanizeDuration(diff, { largest: 2, round: true });
return diff >= 0 ? result : `${result} ago`;
},
};
interface ITagsPluginConfig {
prefix: string;
delete_with_command: boolean;
@ -42,6 +31,8 @@ export class TagsPlugin extends ZeppelinPlugin<ITagsPluginConfig, ITagsPluginPer
private onMessageCreateFn;
private onMessageDeleteFn;
protected tagFunctions;
getDefaultOptions(): IPluginOptions<ITagsPluginConfig, ITagsPluginPermissions> {
return {
config: {
@ -77,6 +68,37 @@ export class TagsPlugin extends ZeppelinPlugin<ITagsPluginConfig, ITagsPluginPer
this.onMessageDeleteFn = this.onMessageDelete.bind(this);
this.savedMessages.events.on("delete", this.onMessageDeleteFn);
this.tagFunctions = {
countdown(toDate) {
if (typeof toDate !== "string") return "";
const now = moment();
const target = moment(toDate, "YYYY-MM-DD HH:mm:ss");
if (!target.isValid()) return "";
const diff = target.diff(now);
const result = humanizeDuration(diff, { largest: 2, round: true });
return diff >= 0 ? result : `${result} ago`;
},
mention: input => {
if (typeof input !== "string") return "";
if (input.match(/^<(@#)(!&)\d+>$/)) {
return input;
}
if (this.guild.members.has(input) || this.bot.users.has(input)) {
return `<@!${input}>`;
}
if (this.guild.channels.has(input) || this.bot.channelGuildMap[input]) {
return `<#${input}>`;
}
return input;
},
};
}
onUnload() {
@ -118,6 +140,17 @@ export class TagsPlugin extends ZeppelinPlugin<ITagsPluginConfig, ITagsPluginPer
@d.command("tag", "<tag:string> <body:string$>")
@d.permission("create")
async tagCmd(msg: Message, args: { tag: string; body: string }) {
try {
parseTemplate(args.body);
} catch (e) {
if (e instanceof TemplateParseError) {
msg.channel.createMessage(errorMessage(`Invalid tag syntax: ${e.message}`));
return;
} else {
throw e;
}
}
await this.tags.createOrUpdate(args.tag, args.body, msg.author.id);
const prefix = this.getConfig().prefix;
@ -164,7 +197,7 @@ export class TagsPlugin extends ZeppelinPlugin<ITagsPluginConfig, ITagsPluginPer
// Format the string
body = await renderTemplate(body, {
args: tagArgs,
...TAG_FUNCTIONS,
...this.tagFunctions,
});
const channel = this.guild.channels.get(msg.channel_id) as TextChannel;

View file

@ -94,9 +94,7 @@ export function parseTemplate(str: string): ParsedTemplate {
inVar = false;
};
let i = -1;
for (const char of chars) {
i++;
for (const [i, char] of chars.entries()) {
if (inVar) {
if (currentVar) {
if (currentVar._state.inArg) {
@ -130,7 +128,7 @@ export function parseTemplate(str: string): ParsedTemplate {
} else if (char === '"') {
// A double quote can start a string argument, but only if we haven't committed to some other type of argument already
if (currentVar._state.currentArgType !== null) {
throw new TemplateParseError(`Unexpected char: ${char} at ${i}`);
throw new TemplateParseError(`Unexpected char ${char} at ${i}`);
}
currentVar._state.currentArgType = "string";
@ -138,7 +136,7 @@ export function parseTemplate(str: string): ParsedTemplate {
} else if (char.match(/\d/)) {
// A number can start a string argument, but only if we haven't committed to some other type of argument already
if (currentVar._state.currentArgType !== null) {
throw new TemplateParseError(`Unexpected char: ${char}`);
throw new TemplateParseError(`Unexpected char ${char} at ${i}`);
}
currentVar._state.currentArgType = "number";
@ -153,7 +151,7 @@ export function parseTemplate(str: string): ParsedTemplate {
currentVar._state.currentArg = newVar;
currentVar = newVar;
} else {
throw new TemplateParseError(`Unexpected char: ${char}`);
throw new TemplateParseError(`Unexpected char ${char} at ${i}`);
}
} else {
if (char === "(") {
@ -180,7 +178,7 @@ export function parseTemplate(str: string): ParsedTemplate {
if (char === "}") {
exitInjectedVar();
} else {
throw new TemplateParseError(`Unexpected char: ${char}`);
throw new TemplateParseError(`Unexpected char ${char} at ${i}`);
}
}
} else {
@ -233,10 +231,11 @@ async function evaluateTemplateVariable(theVar: ITemplateVar, values) {
}
}
return value(...args);
const result = await value(...args);
return result == null ? "" : result;
}
return value;
return value == null ? "" : value;
}
export async function renderParsedTemplate(parsedTemplate: ParsedTemplate, values: any) {