From 296a482b37e473787deedba86b1d7563b8a5cee9 Mon Sep 17 00:00:00 2001 From: seeyebe Date: Mon, 27 Apr 2026 16:19:05 +0300 Subject: [PATCH] feat: support Discord timestamps in reminders --- .../plugins/Reminders/commands/RemindCmd.ts | 20 ++++++++++++------- .../utils/parseDiscordTimestampToMS.test.ts | 15 ++++++++++++++ .../src/utils/parseDiscordTimestampToMS.ts | 13 ++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 backend/src/utils/parseDiscordTimestampToMS.test.ts create mode 100644 backend/src/utils/parseDiscordTimestampToMS.ts diff --git a/backend/src/plugins/Reminders/commands/RemindCmd.ts b/backend/src/plugins/Reminders/commands/RemindCmd.ts index b6b1e04db..ff6451d1c 100644 --- a/backend/src/plugins/Reminders/commands/RemindCmd.ts +++ b/backend/src/plugins/Reminders/commands/RemindCmd.ts @@ -3,6 +3,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes.js"; import { registerUpcomingReminder } from "../../../data/loops/upcomingRemindersLoop.js"; import { humanizeDuration } from "../../../humanizeDuration.js"; import { convertDelayStringToMS, messageLink } from "../../../utils.js"; +import { parseDiscordTimestampToMS } from "../../../utils/parseDiscordTimestampToMS.js"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin.js"; import { remindersCmd } from "../types.js"; @@ -34,14 +35,19 @@ export const RemindCmd = remindersCmd({ // Date and time in YYYY-MM-DD[T]HH:mm format reminderTime = moment.tz(args.time, "YYYY-M-D[T]HH:mm", tz).second(0); } else { - // "Delay string" i.e. e.g. "2h30m" - const ms = convertDelayStringToMS(args.time); - if (ms === null) { - void pluginData.state.common.sendErrorMessage(msg, "Invalid reminder time"); - return; - } + const timestampMs = parseDiscordTimestampToMS(args.time); + if (timestampMs !== null) { + reminderTime = moment.utc(timestampMs); + } else { + // "Delay string" i.e. e.g. "2h30m" + const ms = convertDelayStringToMS(args.time); + if (ms === null) { + void pluginData.state.common.sendErrorMessage(msg, "Invalid reminder time"); + return; + } - reminderTime = moment.utc().add(ms, "millisecond"); + reminderTime = moment.utc().add(ms, "millisecond"); + } } if (!reminderTime.isValid() || reminderTime.isBefore(now)) { diff --git a/backend/src/utils/parseDiscordTimestampToMS.test.ts b/backend/src/utils/parseDiscordTimestampToMS.test.ts new file mode 100644 index 000000000..d9297c233 --- /dev/null +++ b/backend/src/utils/parseDiscordTimestampToMS.test.ts @@ -0,0 +1,15 @@ +import test from "ava"; +import { parseDiscordTimestampToMS } from "./parseDiscordTimestampToMS.js"; + +test("parseDiscordTimestampToMS: accepts timestamp markup", (t) => { + t.is(parseDiscordTimestampToMS(""), 1_767_200_400_000); + t.is(parseDiscordTimestampToMS(""), 1_767_200_400_000); + t.is(parseDiscordTimestampToMS(""), 1_767_200_400_000); + t.is(parseDiscordTimestampToMS(""), 1_767_200_400_000); +}); + +test("parseDiscordTimestampToMS: rejects invalid timestamp markup", (t) => { + t.is(parseDiscordTimestampToMS("1767200400"), null); + t.is(parseDiscordTimestampToMS(""), null); + t.is(parseDiscordTimestampToMS(""), null); +}); diff --git a/backend/src/utils/parseDiscordTimestampToMS.ts b/backend/src/utils/parseDiscordTimestampToMS.ts new file mode 100644 index 000000000..a7b0e7d96 --- /dev/null +++ b/backend/src/utils/parseDiscordTimestampToMS.ts @@ -0,0 +1,13 @@ +export function parseDiscordTimestampToMS(str: string): number | null { + const match = str.match(/^$/); + if (!match) { + return null; + } + + const timestamp = Number(match[1]); + if (!Number.isSafeInteger(timestamp)) { + return null; + } + + return timestamp * 1000; +}