From 2b4be2e98dd922fff3c1e723e048e89fc415a296 Mon Sep 17 00:00:00 2001 From: seeyebe Date: Wed, 10 Dec 2025 14:36:13 +0200 Subject: [PATCH] feat: show relative timestamps for cases and mod actions --- .../plugins/Cases/functions/getCaseEmbed.ts | 6 ++++- .../ModActions/commands/ban/actualBanCmd.ts | 23 +++++++++++++++++-- .../plugins/ModActions/functions/banUserId.ts | 17 +++++++++++--- .../src/plugins/Mutes/commands/MutesCmd.ts | 10 ++++---- .../src/plugins/Mutes/functions/muteUser.ts | 22 ++++++++++++++++-- 5 files changed, 64 insertions(+), 14 deletions(-) diff --git a/backend/src/plugins/Cases/functions/getCaseEmbed.ts b/backend/src/plugins/Cases/functions/getCaseEmbed.ts index 6c02e98cb..cdc47ee51 100644 --- a/backend/src/plugins/Cases/functions/getCaseEmbed.ts +++ b/backend/src/plugins/Cases/functions/getCaseEmbed.ts @@ -40,11 +40,15 @@ export async function getCaseEmbed( const createdAtWithTz = requestMemberId ? await timeAndDate.inMemberTz(requestMemberId, createdAt) : timeAndDate.inGuildTz(createdAt); + const createdAtTs = Math.ceil(createdAt.valueOf() / 1000); + const createdAtRelative = ``; const embed: any = { title: `${actionTypeStr} - Case #${theCase.case_number}`, footer: { - text: `Case created on ${createdAtWithTz.format(timeAndDate.getDateFormat("pretty_datetime"))}`, + text: `Case created on ${createdAtWithTz.format( + timeAndDate.getDateFormat("pretty_datetime"), + )} (${createdAtRelative})`, }, fields: [ { diff --git a/backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts b/backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts index 3146b9039..154cbf306 100644 --- a/backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts +++ b/backend/src/plugins/ModActions/commands/ban/actualBanCmd.ts @@ -97,13 +97,25 @@ export async function actualBanCmd( } // Create a new case for the updated ban since we never stored the old case id and log the action + const expiryTimestamp = time && time > 0 ? Math.ceil((Date.now() + time) / 1000) : null; + const expiryRelative = expiryTimestamp ? `` : null; + const noteDetails: string[] = []; + if (time && time > 0) { + noteDetails.push(`Ban updated to ${humanizeDuration(time)}`); + if (expiryRelative) { + noteDetails.push(`Expires ${expiryRelative}`); + } + } else { + noteDetails.push("Ban updated to indefinite"); + } + const casesPlugin = pluginData.getPlugin(CasesPlugin); const createdCase = await casesPlugin.createCase({ modId: mod.id, type: CaseTypes.Ban, userId: user.id, reason: formattedReason, - noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`], + noteDetails, }); if (time) { pluginData.getPlugin(LogsPlugin).logMemberTimedBan({ @@ -124,7 +136,7 @@ export async function actualBanCmd( pluginData.state.common.sendSuccessMessage( context, - `Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`, + `Ban updated to ${time && expiryRelative ? `expire ${expiryRelative}` : "indefinite"}`, ); lock.unlock(); return; @@ -171,6 +183,9 @@ export async function actualBanCmd( return; } + const banExpiryTimestamp = time && time > 0 ? Math.ceil((Date.now() + time) / 1000) : null; + const banExpiryRelative = banExpiryTimestamp ? `` : null; + let forTime = ""; if (time && time > 0) { forTime = `for ${humanizeDuration(time)} `; @@ -185,6 +200,10 @@ export async function actualBanCmd( response = `Member forcebanned ${forTime}(Case #${banResult.case.case_number})`; } + if (banExpiryRelative) { + response += ` ⏰ ${banExpiryRelative}`; + } + lock.unlock(); pluginData.state.common.sendSuccessMessage(context, response); } diff --git a/backend/src/plugins/ModActions/functions/banUserId.ts b/backend/src/plugins/ModActions/functions/banUserId.ts index a1bc14656..46893f012 100644 --- a/backend/src/plugins/ModActions/functions/banUserId.ts +++ b/backend/src/plugins/ModActions/functions/banUserId.ts @@ -133,6 +133,9 @@ export async function banUserId( const tempbanLock = await pluginData.locks.acquire(`tempban-${user.id}`); const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id); + const banExpiresAt = banTime && banTime > 0 ? Date.now() + banTime : null; + const banExpiryTimestamp = banExpiresAt ? Math.ceil(banExpiresAt / 1000) : null; + const banExpiryRelative = banExpiryTimestamp ? `` : null; if (banTime && banTime > 0) { const selfId = pluginData.client.user!.id; if (existingTempban) { @@ -151,9 +154,17 @@ export async function banUserId( const noteDetails: string[] = []; const timeUntilUnban = banTime ? humanizeDuration(banTime) : "indefinite"; - const timeDetails = `Banned ${banTime ? `for ${timeUntilUnban}` : "indefinitely"}`; - if (notifyResult.text) noteDetails.push(ucfirst(notifyResult.text)); - noteDetails.push(timeDetails); + if (notifyResult.text) { + noteDetails.push(ucfirst(notifyResult.text)); + } + if (banTime && banTime > 0) { + noteDetails.push(`Banned for ${timeUntilUnban}`); + if (banExpiryRelative) { + noteDetails.push(`Expires ${banExpiryRelative}`); + } + } else { + noteDetails.push("Banned indefinitely"); + } const createdCase = await casesPlugin.createCase({ ...(banOptions.caseArgs || {}), diff --git a/backend/src/plugins/Mutes/commands/MutesCmd.ts b/backend/src/plugins/Mutes/commands/MutesCmd.ts index eda0de18b..7a09bdf44 100644 --- a/backend/src/plugins/Mutes/commands/MutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/MutesCmd.ts @@ -130,16 +130,14 @@ export const MutesCmd = mutesCmd({ let line = `<@!${mute.user_id}> (**${username}**, \`${mute.user_id}\`) 📋 ${caseName}`; if (mute.expires_at) { - const timeUntilExpiry = moment.utc().diff(moment.utc(mute.expires_at, DBDateFormat)); - const humanizedTime = humanizeDurationShort(timeUntilExpiry, { largest: 2, round: true }); - line += ` ⏰ Expires in ${humanizedTime}`; + const expiresAtTs = Math.ceil(moment.utc(mute.expires_at).valueOf() / 1000); + line += ` ⏰ Expires `; } else { line += ` ⏰ Indefinite`; } - const timeFromMute = moment.utc(mute.created_at, DBDateFormat).diff(moment.utc()); - const humanizedTimeFromMute = humanizeDurationShort(timeFromMute, { largest: 2, round: true }); - line += ` 🕒 Muted ${humanizedTimeFromMute} ago`; + const mutedAtTs = Math.ceil(moment.utc(mute.created_at).valueOf() / 1000); + line += ` 🕒 Muted `; if (mute.banned) { line += ` 🔨 Banned`; diff --git a/backend/src/plugins/Mutes/functions/muteUser.ts b/backend/src/plugins/Mutes/functions/muteUser.ts index b40aeefa1..52e56d918 100644 --- a/backend/src/plugins/Mutes/functions/muteUser.ts +++ b/backend/src/plugins/Mutes/functions/muteUser.ts @@ -46,6 +46,8 @@ export async function muteUser( const muteType = getDefaultMuteType(pluginData); const muteExpiresAt = muteTime ? Date.now() + muteTime : null; const timeoutUntil = getTimeoutExpiryTime(muteExpiresAt); + const muteExpiryTimestamp = muteExpiresAt ? Math.ceil(muteExpiresAt / 1000) : null; + const muteExpiryRelative = muteExpiryTimestamp ? `` : null; // No mod specified -> mark Zeppelin as the mod if (!muteOptions.caseArgs?.modId) { @@ -245,7 +247,15 @@ export async function muteUser( if (theCase) { // Update old case - const noteDetails = [`Mute updated to ${muteTime ? timeUntilUnmuteStr : "indefinite"}`]; + const noteDetails: string[] = []; + if (muteTime) { + noteDetails.push(`Mute updated to ${timeUntilUnmuteStr}`); + if (muteExpiryRelative) { + noteDetails.push(`Expires ${muteExpiryRelative}`); + } + } else { + noteDetails.push("Mute updated to indefinite"); + } const reasons = reason ? [reason] : [""]; // Empty string so that there is a case update even without reason if (muteOptions.caseArgs?.extraNotes) { @@ -263,7 +273,15 @@ export async function muteUser( } } else { // Create new case - const noteDetails = [`Muted ${muteTime ? `for ${timeUntilUnmuteStr}` : "indefinitely"}`]; + const noteDetails: string[] = []; + if (muteTime) { + noteDetails.push(`Muted for ${timeUntilUnmuteStr}`); + if (muteExpiryRelative) { + noteDetails.push(`Expires ${muteExpiryRelative}`); + } + } else { + noteDetails.push("Muted indefinitely"); + } if (notifyResult.text) { noteDetails.push(ucfirst(notifyResult.text)); }