From 753ceda5ec4eeb1fa99e9c370f37fa0e54a1b7d2 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Wed, 19 Aug 2020 00:47:31 +0300 Subject: [PATCH] !timezone: add fuzzy matching for timezone name; add reset --- backend/src/data/GuildMemberTimezones.ts | 7 ++++ .../plugins/TimeAndDate/TimeAndDatePlugin.ts | 8 ++++- .../TimeAndDate/commands/ResetTimezoneCmd.ts | 21 ++++++++++++ .../TimeAndDate/commands/SetTimezoneCmd.ts | 25 ++++++++++++--- .../Utility/functions/getServerInfoEmbed.ts | 4 +-- backend/src/utils/parseFuzzyTimezone.ts | 32 +++++++++++++++++++ 6 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts create mode 100644 backend/src/utils/parseFuzzyTimezone.ts diff --git a/backend/src/data/GuildMemberTimezones.ts b/backend/src/data/GuildMemberTimezones.ts index df4633aa..9e1a1b68 100644 --- a/backend/src/data/GuildMemberTimezones.ts +++ b/backend/src/data/GuildMemberTimezones.ts @@ -45,4 +45,11 @@ export class GuildMemberTimezones extends BaseGuildRepository { } }); } + + reset(memberId: string) { + return this.memberTimezones.delete({ + guild_id: this.guildId, + member_id: memberId, + }); + } } diff --git a/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts b/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts index d9e2e05f..55101537 100644 --- a/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts +++ b/backend/src/plugins/TimeAndDate/TimeAndDatePlugin.ts @@ -12,6 +12,7 @@ import { getGuildTz } from "./functions/getGuildTz"; import { getMemberTz } from "./functions/getMemberTz"; import { getDateFormat } from "./functions/getDateFormat"; import { inMemberTz } from "./functions/inMemberTz"; +import { ResetTimezoneCmd } from "./commands/ResetTimezoneCmd"; const defaultOptions: PluginOptions = { config: { @@ -34,7 +35,12 @@ export const TimeAndDatePlugin = zeppelinPlugin()("time_a configSchema: ConfigSchema, defaultOptions, - commands: [SetTimezoneCmd, ViewTimezoneCmd], + // prettier-ignore + commands: [ + ResetTimezoneCmd, + SetTimezoneCmd, + ViewTimezoneCmd, + ], public: { getGuildTz: mapToPublicFn(getGuildTz), diff --git a/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts new file mode 100644 index 00000000..56cd21b7 --- /dev/null +++ b/backend/src/plugins/TimeAndDate/commands/ResetTimezoneCmd.ts @@ -0,0 +1,21 @@ +import { timeAndDateCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendSuccessMessage } from "../../../pluginUtils"; +import { getGuildTz } from "../functions/getGuildTz"; + +export const ResetTimezoneCmd = timeAndDateCmd({ + trigger: "timezone reset", + permission: "can_set_timezone", + + signature: {}, + + async run({ pluginData, message }) { + await pluginData.state.memberTimezones.reset(message.author.id); + const serverTimezone = getGuildTz(pluginData); + sendSuccessMessage( + pluginData, + message.channel, + `Your timezone has been reset to server default, **${serverTimezone}**`, + ); + }, +}); diff --git a/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts b/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts index e802f6c5..0d69f47c 100644 --- a/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts +++ b/backend/src/plugins/TimeAndDate/commands/SetTimezoneCmd.ts @@ -1,17 +1,34 @@ import { timeAndDateCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; -import { sendSuccessMessage } from "../../../pluginUtils"; +import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { isValidTimezone } from "../../../utils/isValidTimezone"; +import { disableInlineCode, trimLines } from "../../../utils"; +import { parseFuzzyTimezone } from "../../../utils/parseFuzzyTimezone"; export const SetTimezoneCmd = timeAndDateCmd({ trigger: "timezone", permission: "can_set_timezone", signature: { - timezone: ct.timezone(), + timezone: ct.string(), }, async run({ pluginData, message, args }) { - await pluginData.state.memberTimezones.set(message.author.id, args.timezone); - sendSuccessMessage(pluginData, message.channel, `Your timezone is now set to **${args.timezone}**`); + const parsedTz = parseFuzzyTimezone(args.timezone); + if (!parsedTz) { + sendErrorMessage( + pluginData, + message.channel, + trimLines(` + Invalid timezone: \`${disableInlineCode(args.timezone)}\` + Zeppelin uses timezone locations rather than specific timezone names. + See the **TZ database name** column at for a list of valid options. + `), + ); + return; + } + + await pluginData.state.memberTimezones.set(message.author.id, parsedTz); + sendSuccessMessage(pluginData, message.channel, `Your timezone is now set to **${parsedTz}**`); }, }); diff --git a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts index a5117a43..5cad208f 100644 --- a/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getServerInfoEmbed.ts @@ -51,13 +51,13 @@ export async function getServerInfoEmbed( }); const basicInformation = []; - basicInformation.push(`Created: **${serverAge} ago** (${prettyCreatedAt})`); + basicInformation.push(`Created: **${serverAge} ago** (\`${prettyCreatedAt}\`)`); if (thisServer) { const owner = await resolveUser(pluginData.client, thisServer.ownerID); const ownerName = `${owner.username}#${owner.discriminator}`; - basicInformation.push(`Owner: **${ownerName}** (${thisServer.ownerID})`); + basicInformation.push(`Owner: **${ownerName}** (\`${thisServer.ownerID}\`)`); basicInformation.push(`Voice region: **${thisServer.region}**`); } diff --git a/backend/src/utils/parseFuzzyTimezone.ts b/backend/src/utils/parseFuzzyTimezone.ts new file mode 100644 index 00000000..05d3b1c7 --- /dev/null +++ b/backend/src/utils/parseFuzzyTimezone.ts @@ -0,0 +1,32 @@ +import moment from "moment-timezone"; +import escapeStringRegexp from "escape-string-regexp"; + +const normalizeTzName = str => str.replace(/[^a-zA-Z0-9+\-]/g, "").toLowerCase(); + +const validTimezones = moment.tz.names(); +const normalizedTimezoneMap = validTimezones.reduce((map, tz) => { + map.set(normalizeTzName(tz), tz); + return map; +}, new Map()); +const normalizedTimezones = Array.from(normalizedTimezoneMap.keys()); + +export function parseFuzzyTimezone(input: string) { + const normalizedInput = normalizeTzName(input); + + if (normalizedTimezoneMap.has(normalizedInput)) { + return normalizedTimezoneMap.get(normalizedInput); + } + + const searchRegex = new RegExp(`.*${escapeStringRegexp(normalizedInput)}.*`); + for (const tz of normalizedTimezones) { + if (searchRegex.test(tz)) { + const result = normalizedTimezoneMap.get(tz); + // Ignore Etc/GMT timezones unless explicitly specified, as they have confusing functionality + // with the inverted +/- sign + if (result.startsWith("Etc/GMT")) continue; + return result; + } + } + + return null; +}