From 02d5d6ca137dd6b1d3bcc3fc9543dbe90d97f2ba Mon Sep 17 00:00:00 2001 From: legop3 Date: Fri, 1 May 2026 01:50:35 -0400 Subject: [PATCH] trying to --- .../discordBotService/batteryEmbeds.js | 21 ++- .../discordBotService/commands/help.js | 7 +- .../discordBotService/commands/replay.js | 62 +++++-- .../discordBotService/commands/timeStatus.js | 40 +++-- .../integrations/busEvents.js | 152 +++++++++++++++--- .../integrations/dmModeration.js | 41 ++--- .../services/discordBotService/presence.js | 2 +- 7 files changed, 238 insertions(+), 87 deletions(-) diff --git a/server/src/services/discordBotService/batteryEmbeds.js b/server/src/services/discordBotService/batteryEmbeds.js index 513d9b2a..6dcdddd4 100644 --- a/server/src/services/discordBotService/batteryEmbeds.js +++ b/server/src/services/discordBotService/batteryEmbeds.js @@ -23,6 +23,15 @@ function formatChargeState(batteryState) { return `${chargeText} (${percentText})`; } +function formatDockEmoji(docked) { return docked ? '๐Ÿ ' : '๐Ÿงญ'; } +function formatChargeEmoji(charging) { return charging ? 'โšก' : '๐Ÿ”Œ'; } +function formatLockEmoji(locked) { return locked ? '๐Ÿ”’' : '๐Ÿ”“'; } +function formatBatteryEmoji(batteryState) { + if (batteryState?.urgentActive) return '๐Ÿ›‘'; + if (batteryState?.warnActive) return 'โš ๏ธ'; + return '๐Ÿ”‹'; +} + function formatOiEmoji(oiMode) { if (oiMode === 'full') return '๐Ÿ•น๏ธ'; if (oiMode === 'safe') return '๐Ÿงฐ'; @@ -70,6 +79,12 @@ function buildBatteryStatusEmbed({ color = 0x2196f3, records = [] }) { const lockLabel = snapshot.locked ? `locked${snapshot.lockReason ? ` (${snapshot.lockReason})` : ''}` : 'unlocked'; const dockLabel = snapshot.docked ? 'docked' : 'undocked'; const chargingLabel = snapshot.charging ? `charging (${snapshot.chargingLabel})` : 'not charging'; + const header = [ + formatBatteryEmoji(snapshot.batteryState), + formatDockEmoji(snapshot.docked), + formatChargeEmoji(snapshot.charging), + formatLockEmoji(snapshot.locked), + ].join(' '); const lines = [ `Dock: ${dockLabel}`, @@ -77,12 +92,12 @@ function buildBatteryStatusEmbed({ color = 0x2196f3, records = [] }) { `Battery: ${formatChargeState(snapshot.batteryState)}`, `Voltage: ${formatVoltage(snapshot.voltageMv)}`, `Current: ${formatCurrent(snapshot.currentMa)}`, + `OI: ${snapshot.oiMode} ${formatOiEmoji(snapshot.oiMode)}`, + `Lock: ${lockLabel}`, ]; - lines.push(`${formatOiEmoji(snapshot.oiMode)} OI mode: ${snapshot.oiMode}`); - lines.push(`Lock: ${lockLabel}`); embed.addFields({ - name: snapshot.name, + name: `${header} ${snapshot.name}`, value: lines.join('\n'), inline: true, }); diff --git a/server/src/services/discordBotService/commands/help.js b/server/src/services/discordBotService/commands/help.js index b4c0a031..cb6258a5 100644 --- a/server/src/services/discordBotService/commands/help.js +++ b/server/src/services/discordBotService/commands/help.js @@ -16,8 +16,11 @@ function formatHelp() { '`rs mode ` โ€” change server mode', '`rs reason [text|clear]` โ€” show or set admin mode reason', '`rs goal [text|clear]` โ€” show or set global objective', - '`rs verify list|remove ...` โ€” manage verified users', - '`rs deter list|ban|unban ...` โ€” manage deterred users', + '`rs verify list` โ€” list verified users (lockdown admins)', + '`rs verify remove ` โ€” remove verified user (lockdown admins)', + '`rs deter list` โ€” list deterred users (lockdown admins)', + '`rs deter ban [reason]` โ€” deter a user (lockdown admins)', + '`rs deter unban ` โ€” remove deterrence (lockdown admins)', '`ts` โ€” show time status', ].join('\n'); } diff --git a/server/src/services/discordBotService/commands/replay.js b/server/src/services/discordBotService/commands/replay.js index 1d4bc288..56366bd1 100644 --- a/server/src/services/discordBotService/commands/replay.js +++ b/server/src/services/discordBotService/commands/replay.js @@ -12,30 +12,57 @@ function createReplayCommand({ getMode, MODES, tryTriggerReplay, getReplaySource } function buildDefaultReplayTitle(requester, sources = []) { const requesterLabel = String(requester || 'Someone').trim() || 'Someone'; - const labels = (Array.isArray(sources) ? sources : []) - .map((entry) => String(entry?.label || entry?.id || '').trim()) - .filter(Boolean); - if (!labels.length) return `${requesterLabel} replay`; - if (labels.length === 1) return `${requesterLabel} replay: ${labels[0]}`; - return `${requesterLabel} replay: ${labels.slice(0, 3).join(' + ')}`; + const roverSource = (Array.isArray(sources) ? sources : []).find((entry) => entry?.type === 'rover'); + const roverLabel = roverSource?.label || roverSource?.id || 'a rover'; + return `${requesterLabel} driving ${roverLabel}`; } - function buildDriverSummary(sources = []) { + function buildReplayDriverLines(requester, sources = []) { const activeDrivers = getActiveDrivers(); const roverSources = (Array.isArray(sources) ? sources : []).filter((entry) => entry?.type === 'rover'); + const requestedRoverIds = new Set(roverSources.map((entry) => String(entry.id))); const lines = []; - roverSources.forEach((source) => { - const roverId = String(source.id); + requestedRoverIds.forEach((roverId) => { const socketId = activeDrivers?.[roverId]; if (!socketId) return; const socket = io.sockets.sockets.get(socketId); const nickname = getNickname(socket) || socket?.data?.user?.username || socketId; const record = rovers.get(roverId); - const roverName = record?.meta?.name || source?.label || roverId; - lines.push(`${nickname} โ†’ ${roverName}`); + const roverName = record?.meta?.name || record?.id || roverId; + const isAuthor = String(nickname).toLowerCase() === String(requester || '').toLowerCase(); + lines.push(`${nickname} driving ${roverName}${isAuthor ? ' **author**' : ''}`); }); - if (!lines.length) return 'Drivers: none active for selected rover sources'; - return `Drivers: ${lines.join(' | ')}`; + return lines; + } + + function buildDriverCaption() { + const activeDrivers = getActiveDrivers(); + const roster = Array.from(rovers.values()); + if (!roster.length) return 'Drivers: no rovers online.'; + const entries = roster.map((record) => { + const driverId = activeDrivers[record.id]; + if (!driverId) return `${record.meta?.name || record.id}: none`; + const socket = io.sockets.sockets.get(driverId); + const nickname = getNickname(socket) || socket?.data?.user?.username || driverId; + return `${record.meta?.name || record.id}: ${nickname}`; + }); + return `Drivers: ${entries.join(', ')}`; + } + + function buildReplayCaption({ requester, usedSources = [], missingSources = [], title }) { + const lines = []; + if (title) { + lines.push(`**${title}**`); + lines.push(''); + } + const driverLines = buildReplayDriverLines(requester, usedSources); + if (driverLines.length) lines.push(...driverLines); + if (missingSources.length) { + if (driverLines.length) lines.push(''); + lines.push(`Missing: ${missingSources.map((source) => source.label || `${source.type}:${source.id}`).join(', ')}`); + } + if (!lines.length) lines.push(buildDriverCaption()); + return lines.join('\n'); } function resolveReplaySources(query) { const cleaned = normalizeReplayQuery(query); @@ -70,10 +97,13 @@ function createReplayCommand({ getMode, MODES, tryTriggerReplay, getReplaySource const requester = message.member?.nickname || message.author?.globalName || message.author?.username || 'Discord'; const title = buildDefaultReplayTitle(requester, resolved.sources || []); try { - const { buffer } = await buildReplayVideo({ sources: resolved.sources || [], title, requester }); + const { buffer, usedSources = resolved.sources || [], missingSources = [] } = await buildReplayVideo({ + sources: resolved.sources || [], + title, + requester, + }); const attachment = new AttachmentBuilder(buffer, { name: `${sanitizeReplayTitleForFilename(title)}.mp4` }); - const summary = buildDriverSummary(resolved.sources || []); - const body = [ `**${title}**`, summary ].filter(Boolean).join('\n'); + const body = buildReplayCaption({ requester, usedSources, missingSources, title }); await message.reply({ content: sanitizeMentions(body), files: [attachment], allowedMentions: { parse: [], repliedUser: false } }); } catch (err) { await message.reply({ content: sanitizeMentions(`Replay failed: ${err.message}`), allowedMentions: { parse: [], repliedUser: false } }); diff --git a/server/src/services/discordBotService/commands/timeStatus.js b/server/src/services/discordBotService/commands/timeStatus.js index 33f9a9b9..7b7e4d10 100644 --- a/server/src/services/discordBotService/commands/timeStatus.js +++ b/server/src/services/discordBotService/commands/timeStatus.js @@ -26,24 +26,32 @@ function createTimeStatusCommand({ config, discordConfig }) { const serverTimezone = getServerTimezone(); const now = new Date(); const zones = [ - 'UTC', - 'America/Los_Angeles', - 'America/Denver', - 'America/Chicago', - 'America/New_York', - 'America/Halifax', - 'Pacific/Honolulu', - 'Europe/London', - 'Europe/Berlin', - 'Asia/Tokyo', - 'Australia/Sydney', - 'Pacific/Auckland', + { label: 'UTC', zone: 'UTC' }, + { label: 'US Pacific', zone: 'America/Los_Angeles' }, + { label: 'US Mountain', zone: 'America/Denver' }, + { label: 'US Central', zone: 'America/Chicago' }, + { label: 'US Eastern', zone: 'America/New_York' }, + { label: 'Europe London', zone: 'Europe/London' }, + { label: 'Europe Berlin', zone: 'Europe/Berlin' }, + { label: 'Asia Kolkata', zone: 'Asia/Kolkata' }, + { label: 'Asia Shanghai', zone: 'Asia/Shanghai' }, + { label: 'Asia Tokyo', zone: 'Asia/Tokyo' }, + { label: 'Australia Sydney', zone: 'Australia/Sydney' }, + { label: 'New Zealand Auckland', zone: 'Pacific/Auckland' }, ]; - if (!zones.includes(serverTimezone)) { - zones.push(serverTimezone); + const entries = zones.map((entry) => { + const time = formatTimeInZone(now, entry.zone); + const highlight = + String(entry.zone).toLowerCase() === String(serverTimezone).toLowerCase() + ? ' **(server local timezone)**' + : ''; + return `${entry.label} โ€” ${time}${highlight}`; + }); + if (!zones.some((entry) => String(entry.zone).toLowerCase() === String(serverTimezone).toLowerCase())) { + const time = formatTimeInZone(now, serverTimezone); + entries.push(`Server Local โ€” ${time} **(server local timezone)**`); } - const lines = zones.map((zone) => `${zone} โ€” ${formatTimeInZone(now, zone)}${zone === serverTimezone ? ' **(server local timezone)**' : ''}`); - const embed = buildEmbed({ title: 'Time Status', description: lines.join('\n'), color: 0x2196f3 }); + const embed = buildEmbed({ title: 'Time Status', description: entries.join('\n'), color: 0x2196f3 }); embed.setFooter({ text: `Server local timezone: ${serverTimezone}` }); await message.reply({ embeds: [embed], allowedMentions: { parse: [], repliedUser: false } }); }; diff --git a/server/src/services/discordBotService/integrations/busEvents.js b/server/src/services/discordBotService/integrations/busEvents.js index b5e4c715..efe1d5f8 100644 --- a/server/src/services/discordBotService/integrations/busEvents.js +++ b/server/src/services/discordBotService/integrations/busEvents.js @@ -6,7 +6,7 @@ const io = require('../../../globals/io'); const { buildBatteryStatusEmbed, buildBatteryCaption } = require('../batteryEmbeds'); function createBusEventHandler(deps) { - const { logger, discordConfig, MODES, roverManager, rovers, schedulePresenceRotation, formatDuration, sendToChannel, buildReplayVideo, getActiveDrivers, getNickname } = deps; + const { logger, discordConfig, getMode, MODES, roverManager, rovers, schedulePresenceRotation, formatDuration, sendToChannel, buildReplayVideo, getActiveDrivers, getNickname } = deps; const ADMIN_ALERT_EVENT_TYPES = new Set(['rover.online', 'rover.offline', 'rover.dockGuard', 'battery.warn', 'battery.urgent', 'battery.docked', 'battery.undocked', 'battery.charging.start', 'battery.charging.stop', 'battery.locked', 'battery.unlocked']); let skippedFirstModeAnnouncement = false; @@ -20,13 +20,44 @@ function createBusEventHandler(deps) { } - async function announce({ channelId, content, pingRoleId, color, title, description, embeds, files }) { + async function announce({ + channelId, + content, + pingRoleId, + prefixMentions = true, + includeSiteUrl = true, + color, + title, + description, + embeds, + files, + }) { if (!channelId) return; - const prefix = pingRoleId ? `<@&${pingRoleId}> ` : ''; - const payloadEmbeds = Array.isArray(embeds) && embeds.length ? embeds : [buildEmbed({ title, description, color })]; + const prefix = prefixMentions && pingRoleId ? `<@&${pingRoleId}> ` : ''; + const payloadEmbeds = Array.isArray(embeds) && embeds.length + ? embeds + : [buildEmbed({ title, description, color, includeSiteUrl })]; await sendToChannel(channelId, `${prefix}${content || ''}`.trim(), { embeds: payloadEmbeds, files: Array.isArray(files) ? files : undefined }, { parse: [], roles: pingRoleId ? [pingRoleId] : [] }, !pingRoleId); } + async function announceUserStatus({ channelId, content, color, title, description, embeds }) { + const roles = discordConfig.roles || {}; + const mode = getMode(); + const allowPing = mode !== MODES.ADMIN && mode !== MODES.LOCKDOWN; + const pingRoleId = allowPing ? roles.announcementPing || null : null; + const mainLine = pingRoleId ? `<@&${pingRoleId}> ${content || ''}`.trim() : content; + await announce({ + channelId, + content: mainLine, + pingRoleId, + prefixMentions: false, + color, + title, + description, + embeds, + }); + } + function sanitizeReplayTitleForFilename(title) { const cleaned = String(title || '').replace(/[\\/:*?"<>|]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 96); return cleaned || 'replay'; @@ -34,39 +65,75 @@ function createBusEventHandler(deps) { function buildDefaultReplayTitle(requester, sources = []) { const requesterLabel = String(requester || 'Someone').trim() || 'Someone'; - const labels = (Array.isArray(sources) ? sources : []) - .map((entry) => String(entry?.label || entry?.id || '').trim()) - .filter(Boolean); - if (!labels.length) return `${requesterLabel} replay`; - if (labels.length === 1) return `${requesterLabel} replay: ${labels[0]}`; - return `${requesterLabel} replay: ${labels.slice(0, 3).join(' + ')}`; + const roverSource = (Array.isArray(sources) ? sources : []).find((entry) => entry?.type === 'rover'); + const roverLabel = roverSource?.label || roverSource?.id || 'a rover'; + return `${requesterLabel} driving ${roverLabel}`; } - function buildDriverSummary(sources = []) { + function buildReplayDriverLines(requester, sources = []) { const activeDrivers = getActiveDrivers(); const roverSources = (Array.isArray(sources) ? sources : []).filter((entry) => entry?.type === 'rover'); + const requestedRoverIds = new Set(roverSources.map((entry) => String(entry.id))); const lines = []; - roverSources.forEach((source) => { - const roverId = String(source.id); + requestedRoverIds.forEach((roverId) => { const socketId = activeDrivers?.[roverId]; if (!socketId) return; const socket = io.sockets.sockets.get(socketId); const nickname = getNickname(socket) || socket?.data?.user?.username || socketId; const record = rovers.get(roverId); - const roverName = record?.meta?.name || source?.label || roverId; - lines.push(`${nickname} โ†’ ${roverName}`); + const roverName = record?.meta?.name || record?.id || roverId; + const isAuthor = String(nickname).toLowerCase() === String(requester || '').toLowerCase(); + lines.push(`${nickname} driving ${roverName}${isAuthor ? ' **author**' : ''}`); }); - if (!lines.length) return 'Drivers: none active for selected rover sources'; - return `Drivers: ${lines.join(' | ')}`; + return lines; + } + + function buildDriverCaption() { + const activeDrivers = getActiveDrivers(); + const roster = Array.from(rovers.values()); + if (!roster.length) return 'Drivers: no rovers online.'; + const entries = roster.map((record) => { + const driverId = activeDrivers[record.id]; + if (!driverId) return `${record.meta?.name || record.id}: none`; + const socket = io.sockets.sockets.get(driverId); + const nickname = getNickname(socket) || socket?.data?.user?.username || driverId; + return `${record.meta?.name || record.id}: ${nickname}`; + }); + return `Drivers: ${entries.join(', ')}`; + } + + function buildReplayCaption({ requester, usedSources = [], missingSources = [], title }) { + const lines = []; + if (title) { + lines.push(`**${title}**`); + lines.push(''); + } + const driverLines = buildReplayDriverLines(requester, usedSources); + if (driverLines.length) lines.push(...driverLines); + if (missingSources.length) { + if (driverLines.length) lines.push(''); + lines.push(`Missing: ${missingSources.map((source) => source.label || `${source.type}:${source.id}`).join(', ')}`); + } + if (!lines.length) lines.push(buildDriverCaption()); + return lines.join('\n'); } async function sendReplayToChannel(channelId, requester, sources = [], explicitTitle = '', includeSidebar = true) { if (!channelId) throw new Error('Replay channel not configured'); const resolvedTitle = String(explicitTitle || '').trim() || buildDefaultReplayTitle(requester, sources); - const { buffer } = await buildReplayVideo({ sources, title: resolvedTitle, requester, includeSidebar }); + const { buffer, usedSources = sources, missingSources = [] } = await buildReplayVideo({ + sources, + title: resolvedTitle, + requester, + includeSidebar, + }); const attachment = new AttachmentBuilder(buffer, { name: `${sanitizeReplayTitleForFilename(resolvedTitle)}.mp4` }); - const summary = buildDriverSummary(sources); - const body = [ `**${resolvedTitle}**`, summary ].filter(Boolean).join('\n'); + const body = buildReplayCaption({ + requester, + usedSources, + missingSources, + title: resolvedTitle, + }); await sendToChannel(channelId, body, { files: [attachment] }, { parse: [] }); } @@ -94,19 +161,36 @@ function createBusEventHandler(deps) { case 'mode.changed': if (!skippedFirstModeAnnouncement) { skippedFirstModeAnnouncement = true; schedulePresenceRotation(); break; } if (payload?.mode === MODES.OPEN || payload?.mode === MODES.TURNS) { - announce({ channelId: channels.announcements, content: `Access mode set to ${payload?.mode}.`, color: 0x2196f3, title: 'Access Mode Updated', description: `Access mode set to **${payload?.mode}**` }); + announceUserStatus({ channelId: channels.announcements, content: `Access mode set to ${payload?.mode}.`, color: 0x2196f3, title: 'Access Mode Updated', description: `Access mode set to **${payload?.mode}**` }); } schedulePresenceRotation(); break; case 'globalObjective.updated': - announce({ channelId: channels.announcements, content: payload?.text ? `Global objective: ${payload.text}` : 'Global objective cleared.', color: 0x8bc34a, title: 'Global Objective', description: payload?.text || 'Global objective cleared.' }); + announceUserStatus({ channelId: channels.announcements, content: payload?.text ? `Global objective: ${payload.text}` : 'Global objective cleared.', color: 0x8bc34a, title: 'Global Objective', description: payload?.text || 'Global objective cleared.' }); + schedulePresenceRotation(); + break; + case 'communityGoal.updated': + announceUserStatus({ channelId: channels.announcements, content: payload?.text ? `Global objective: ${payload.text}` : 'Global objective cleared.', color: 0x8bc34a, title: 'Global Objective', description: payload?.text || 'Global objective cleared.' }); + schedulePresenceRotation(); + break; + case 'rover.locked': + announceUserStatus({ channelId: channels.announcements, content: `${payload?.roverId} locked${payload?.reason ? ` (${payload.reason})` : ''}.`, color: 0xf0b651, title: 'Rover Locked', description: `${payload?.roverId} locked${payload?.reason ? ` (${payload.reason})` : ''}.` }); + schedulePresenceRotation(); + break; + case 'rover.unlocked': + schedulePresenceRotation(); + break; + case 'rovers.allUnlocked': + announceUserStatus({ channelId: channels.announcements, content: 'All rovers are now unlocked.', color: 0x4caf50, title: 'All Rovers Unlocked', description: 'All rovers are now unlocked.' }); schedulePresenceRotation(); break; case 'rover.online': announce({ channelId: channels.adminAlerts, pingRoleId: roles.adminPing || null, color: 0x4caf50, title: 'Rover Online', description: `${payload?.roverId} is online.` }); + schedulePresenceRotation(); break; case 'rover.offline': announce({ channelId: channels.adminAlerts, pingRoleId: roles.adminPing || null, color: 0xe53935, title: 'Rover Offline', description: `${payload?.roverId} went offline.` }); + schedulePresenceRotation(); break; case 'rover.dockGuard': announce({ channelId: channels.adminAlerts, color: 0xf0b651, title: 'Dock Guard Triggered', description: `${payload?.roverId} (${payload?.reasonText || 'undocked'}) for ${formatDuration(payload?.idleMs)}.` }); @@ -125,9 +209,11 @@ function createBusEventHandler(deps) { case 'battery.charging.stop': case 'battery.locked': announce({ channelId: channels.adminAlerts, color: 0xf0b651, content: buildBatteryCaption(type, rovers.get(payload?.roverId || 'unknown')), embeds: [buildBatteryStatusEmbed({ color: 0xf0b651, records: Array.from(rovers.values()) })] }); + schedulePresenceRotation(); break; case 'battery.unlocked': announce({ channelId: channels.adminAlerts, color: 0x4caf50, content: buildBatteryCaption(type, rovers.get(payload?.roverId || 'unknown')), embeds: [buildBatteryStatusEmbed({ color: 0x4caf50, records: Array.from(rovers.values()) })] }); + schedulePresenceRotation(); break; case 'humanAlert.buttonPressed': { const imageBase64 = payload?.imageBase64 ? String(payload.imageBase64) : ''; @@ -138,12 +224,32 @@ function createBusEventHandler(deps) { if (imageBuffer.length > 0) attachment = new AttachmentBuilder(imageBuffer, { name: 'human-alert-mosaic.jpg' }); } catch (err) { logger.warn('Failed to decode human alert image for Discord', err.message); } } - announce({ channelId: channels.humanAlerts, pingRoleId: roles.humanAlertPing || null, content: payload?.message || 'Human alert button pressed.', embeds: [buildEmbed({ title: 'Human Alert Button Pressed', description: null, color: 0xe53935 })], files: attachment ? [attachment] : [] }); + announce({ + channelId: channels.humanAlerts, + pingRoleId: roles.humanAlertPing || null, + content: payload?.message || 'Human alert button pressed.', + embeds: [buildEmbed({ title: 'Human Alert Button Pressed', description: null, color: 0xe53935, includeSiteUrl: false })], + files: attachment ? [attachment] : [], + includeSiteUrl: false, + }); break; } case 'replay.requested': handleReplayRequested(event); break; + case 'buttonBox.discordStalkerPing': { + const message = payload?.message ? String(payload.message) : 'Button box chaos reward triggered.'; + announce({ + channelId: channels.general, + pingRoleId: roles.stalkerPing || null, + content: message, + color: 0xe91e63, + title: 'Button Box', + description: message, + includeSiteUrl: false, + }); + break; + } default: break; } diff --git a/server/src/services/discordBotService/integrations/dmModeration.js b/server/src/services/discordBotService/integrations/dmModeration.js index 73ec302c..742c07bb 100644 --- a/server/src/services/discordBotService/integrations/dmModeration.js +++ b/server/src/services/discordBotService/integrations/dmModeration.js @@ -25,13 +25,14 @@ function createDmModerationHandlers(deps) { const payload = event?.payload || {}; const requestId = payload.id; if (!requestId) return; + const createdAt = payload.createdAt ? new Date(payload.createdAt).toLocaleString() : 'unknown'; const content = [ '**Verification Request**', `Request ID: \`${requestId}\``, `Nickname: ${sanitizeMentions(payload.nickname || 'unknown')}`, - `Identity Key: \`${String(payload.cookieUserId || 'unknown')}\``, + `Identity key: \`${String(payload.cookieUserId || 'unknown')}\``, `IP: \`${String(payload.ip || 'unknown')}\``, - `Socket: \`${String(payload.socketId || 'unknown')}\``, + `Created: ${createdAt}`, '', `React with ${APPROVE} to approve or ${DENY} to deny.`, ].join('\n'); @@ -54,16 +55,17 @@ function createDmModerationHandlers(deps) { const requestId = payload.id; if (!requestId) return; const requester = payload?.requester || {}; + const createdAt = payload.createdAt ? new Date(payload.createdAt).toLocaleString() : 'unknown'; const content = [ '**Private Rover Access Request**', `Request ID: \`${requestId}\``, `Rover: ${sanitizeMentions(payload.roverName || payload.roverId || 'unknown')} (\`${String(payload.roverId || 'unknown')}\`)`, - `Nickname: ${sanitizeMentions(requester.nickname || 'unknown')}`, + `Requester: ${sanitizeMentions(requester.nickname || requester.socketId || 'unknown')}`, `Role: \`${String(requester.role || 'unknown')}\``, `Verified: \`${requester.isVerified ? 'yes' : 'no'}\``, - `Identity Key: \`${String(requester.cookieUserId || 'unknown')}\``, + `Identity key: \`${String(requester.cookieUserId || 'unknown')}\``, `IP: \`${String(requester.ip || 'unknown')}\``, - `Socket: \`${String(requester.socketId || 'unknown')}\``, + `Created: ${createdAt}`, '', `React with ${APPROVE} to approve or ${DENY} to deny.`, ].join('\n'); @@ -93,21 +95,13 @@ function createDmModerationHandlers(deps) { if (emoji === APPROVE) { approveRequest(linked.request.id, user.id); await reaction.message.reply({ - content: [ - `โœ… Verification request \`${linked.request.id}\` approved by ${sanitizeMentions(user.username || user.tag || user.id)}.`, - `Nickname: ${sanitizeMentions(linked.request.nickname || 'unknown')}`, - `Identity Key: \`${String(linked.request.cookieUserId || 'unknown')}\``, - ].join('\n'), + content: `Approved request \`${linked.request.id}\`.`, allowedMentions: { parse: [] }, }); } else { denyRequest(linked.request.id, user.id); await reaction.message.reply({ - content: [ - `โŒ Verification request \`${linked.request.id}\` denied by ${sanitizeMentions(user.username || user.tag || user.id)}.`, - `Nickname: ${sanitizeMentions(linked.request.nickname || 'unknown')}`, - `Identity Key: \`${String(linked.request.cookieUserId || 'unknown')}\``, - ].join('\n'), + content: `Denied request \`${linked.request.id}\`.`, allowedMentions: { parse: [] }, }); } @@ -126,23 +120,18 @@ function createDmModerationHandlers(deps) { const linked = getPrivateAccessRequestByMessageId(reaction.message?.id); if (!linked?.request || linked.request.status !== 'pending') return; if (emoji === APPROVE) { - approvePrivateAccessRequest(linked.request.id, user.id); + const { assignedSocketId } = approvePrivateAccessRequest(linked.request.id, user.id); + const assignmentNote = assignedSocketId + ? ` Granted and assigned to socket \`${assignedSocketId}\`.` + : ' Granted.'; await reaction.message.reply({ - content: [ - `โœ… Private access request \`${linked.request.id}\` approved by ${sanitizeMentions(user.username || user.tag || user.id)}.`, - `Rover: ${sanitizeMentions(linked.request.roverName || linked.request.roverId || 'unknown')} (\`${String(linked.request.roverId || 'unknown')}\`)`, - `Requester: ${sanitizeMentions(linked.request.requester?.nickname || 'unknown')}`, - ].join('\n'), + content: `Approved private access request \`${linked.request.id}\`.${assignmentNote}`, allowedMentions: { parse: [] }, }); } else { denyPrivateAccessRequest(linked.request.id, user.id); await reaction.message.reply({ - content: [ - `โŒ Private access request \`${linked.request.id}\` denied by ${sanitizeMentions(user.username || user.tag || user.id)}.`, - `Rover: ${sanitizeMentions(linked.request.roverName || linked.request.roverId || 'unknown')} (\`${String(linked.request.roverId || 'unknown')}\`)`, - `Requester: ${sanitizeMentions(linked.request.requester?.nickname || 'unknown')}`, - ].join('\n'), + content: `Denied private access request \`${linked.request.id}\`.`, allowedMentions: { parse: [] }, }); } diff --git a/server/src/services/discordBotService/presence.js b/server/src/services/discordBotService/presence.js index e84e5316..1b478c27 100644 --- a/server/src/services/discordBotService/presence.js +++ b/server/src/services/discordBotService/presence.js @@ -22,7 +22,7 @@ function createPresenceManager({ client, logger, getMode, getGlobalObjective, co const objectiveText = objective?.text ? String(objective.text).trim() : ''; if (presenceShowObjective && objectiveText) { const trimmed = truncatePresenceText(objectiveText, 110); - return `Objective: ${trimmed}`; + return `Goal: ${trimmed}`; } return `${mode} ยท ${ready}/${total} Rovers Ready`; }