mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-03-16 22:21: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:
parent
db5d93b5c2
commit
f27da2c56f
2 changed files with 55 additions and 23 deletions
|
@ -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 { Message, TextChannel } from "eris";
|
||||||
import { errorMessage, successMessage } from "../utils";
|
import { errorMessage, successMessage } from "../utils";
|
||||||
import { GuildTags } from "../data/GuildTags";
|
import { GuildTags } from "../data/GuildTags";
|
||||||
|
@ -7,20 +7,9 @@ import { SavedMessage } from "../data/entities/SavedMessage";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import humanizeDuration from "humanize-duration";
|
import humanizeDuration from "humanize-duration";
|
||||||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||||
import { renderTemplate } from "../templateFormatter";
|
import { parseTemplate, renderTemplate, TemplateParseError } from "../templateFormatter";
|
||||||
import { escapeBacktickString } from "jest-snapshot/build/utils";
|
|
||||||
import { GuildArchives } from "../data/GuildArchives";
|
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 {
|
interface ITagsPluginConfig {
|
||||||
prefix: string;
|
prefix: string;
|
||||||
delete_with_command: boolean;
|
delete_with_command: boolean;
|
||||||
|
@ -42,6 +31,8 @@ export class TagsPlugin extends ZeppelinPlugin<ITagsPluginConfig, ITagsPluginPer
|
||||||
private onMessageCreateFn;
|
private onMessageCreateFn;
|
||||||
private onMessageDeleteFn;
|
private onMessageDeleteFn;
|
||||||
|
|
||||||
|
protected tagFunctions;
|
||||||
|
|
||||||
getDefaultOptions(): IPluginOptions<ITagsPluginConfig, ITagsPluginPermissions> {
|
getDefaultOptions(): IPluginOptions<ITagsPluginConfig, ITagsPluginPermissions> {
|
||||||
return {
|
return {
|
||||||
config: {
|
config: {
|
||||||
|
@ -77,6 +68,37 @@ export class TagsPlugin extends ZeppelinPlugin<ITagsPluginConfig, ITagsPluginPer
|
||||||
|
|
||||||
this.onMessageDeleteFn = this.onMessageDelete.bind(this);
|
this.onMessageDeleteFn = this.onMessageDelete.bind(this);
|
||||||
this.savedMessages.events.on("delete", this.onMessageDeleteFn);
|
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() {
|
onUnload() {
|
||||||
|
@ -118,6 +140,17 @@ export class TagsPlugin extends ZeppelinPlugin<ITagsPluginConfig, ITagsPluginPer
|
||||||
@d.command("tag", "<tag:string> <body:string$>")
|
@d.command("tag", "<tag:string> <body:string$>")
|
||||||
@d.permission("create")
|
@d.permission("create")
|
||||||
async tagCmd(msg: Message, args: { tag: string; body: string }) {
|
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);
|
await this.tags.createOrUpdate(args.tag, args.body, msg.author.id);
|
||||||
|
|
||||||
const prefix = this.getConfig().prefix;
|
const prefix = this.getConfig().prefix;
|
||||||
|
@ -164,7 +197,7 @@ export class TagsPlugin extends ZeppelinPlugin<ITagsPluginConfig, ITagsPluginPer
|
||||||
// Format the string
|
// Format the string
|
||||||
body = await renderTemplate(body, {
|
body = await renderTemplate(body, {
|
||||||
args: tagArgs,
|
args: tagArgs,
|
||||||
...TAG_FUNCTIONS,
|
...this.tagFunctions,
|
||||||
});
|
});
|
||||||
|
|
||||||
const channel = this.guild.channels.get(msg.channel_id) as TextChannel;
|
const channel = this.guild.channels.get(msg.channel_id) as TextChannel;
|
||||||
|
|
|
@ -94,9 +94,7 @@ export function parseTemplate(str: string): ParsedTemplate {
|
||||||
inVar = false;
|
inVar = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let i = -1;
|
for (const [i, char] of chars.entries()) {
|
||||||
for (const char of chars) {
|
|
||||||
i++;
|
|
||||||
if (inVar) {
|
if (inVar) {
|
||||||
if (currentVar) {
|
if (currentVar) {
|
||||||
if (currentVar._state.inArg) {
|
if (currentVar._state.inArg) {
|
||||||
|
@ -130,7 +128,7 @@ export function parseTemplate(str: string): ParsedTemplate {
|
||||||
} else if (char === '"') {
|
} 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
|
// 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) {
|
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";
|
currentVar._state.currentArgType = "string";
|
||||||
|
@ -138,7 +136,7 @@ export function parseTemplate(str: string): ParsedTemplate {
|
||||||
} else if (char.match(/\d/)) {
|
} 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
|
// 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) {
|
if (currentVar._state.currentArgType !== null) {
|
||||||
throw new TemplateParseError(`Unexpected char: ${char}`);
|
throw new TemplateParseError(`Unexpected char ${char} at ${i}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentVar._state.currentArgType = "number";
|
currentVar._state.currentArgType = "number";
|
||||||
|
@ -153,7 +151,7 @@ export function parseTemplate(str: string): ParsedTemplate {
|
||||||
currentVar._state.currentArg = newVar;
|
currentVar._state.currentArg = newVar;
|
||||||
currentVar = newVar;
|
currentVar = newVar;
|
||||||
} else {
|
} else {
|
||||||
throw new TemplateParseError(`Unexpected char: ${char}`);
|
throw new TemplateParseError(`Unexpected char ${char} at ${i}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (char === "(") {
|
if (char === "(") {
|
||||||
|
@ -180,7 +178,7 @@ export function parseTemplate(str: string): ParsedTemplate {
|
||||||
if (char === "}") {
|
if (char === "}") {
|
||||||
exitInjectedVar();
|
exitInjectedVar();
|
||||||
} else {
|
} else {
|
||||||
throw new TemplateParseError(`Unexpected char: ${char}`);
|
throw new TemplateParseError(`Unexpected char ${char} at ${i}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
export async function renderParsedTemplate(parsedTemplate: ParsedTemplate, values: any) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue