From d7f34af66f55b48a649a24975f4c62f6632e1391 Mon Sep 17 00:00:00 2001 From: legop3 Date: Wed, 29 Apr 2026 13:35:24 -0400 Subject: [PATCH] replay beroke --- .../src/services/discordBotService/index.js | 3 +- .../integrations/busEvents.js | 37 +++++++++++++++++-- .../discordBotService/integrations/index.js | 3 +- .../services/replayEngineV2/socketHooks.js | 6 ++- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/server/src/services/discordBotService/index.js b/server/src/services/discordBotService/index.js index 3f651f47..b0b85490 100644 --- a/server/src/services/discordBotService/index.js +++ b/server/src/services/discordBotService/index.js @@ -153,7 +153,7 @@ const commands = createCommandHandlers({ config, }); -const integrations = createIntegrations({ + const integrations = createIntegrations({ logger, client, config, @@ -184,6 +184,7 @@ const integrations = createIntegrations({ clearTypingMessage: channelIO.clearTypingMessage, sendTypingMessage: channelIO.sendTypingMessage, schedulePresenceRotation: presence.schedulePresenceRotation, + buildReplayVideo, }); const integrationHandlers = integrations.register(); diff --git a/server/src/services/discordBotService/integrations/busEvents.js b/server/src/services/discordBotService/integrations/busEvents.js index 08a924f8..273d898e 100644 --- a/server/src/services/discordBotService/integrations/busEvents.js +++ b/server/src/services/discordBotService/integrations/busEvents.js @@ -5,7 +5,7 @@ const { EmbedBuilder, AttachmentBuilder } = require('discord.js'); const { buildBatteryStatusEmbed, buildBatteryCaption } = require('../batteryEmbeds'); function createBusEventHandler(deps) { - const { logger, discordConfig, MODES, roverManager, rovers, schedulePresenceRotation, formatDuration, sendToChannel } = deps; + const { logger, discordConfig, MODES, roverManager, rovers, schedulePresenceRotation, formatDuration, sendToChannel, buildReplayVideo } = 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; @@ -26,7 +26,33 @@ function createBusEventHandler(deps) { await sendToChannel(channelId, `${prefix}${content || ''}`.trim(), { embeds: payloadEmbeds, files: Array.isArray(files) ? files : undefined }, { parse: [], roles: pingRoleId ? [pingRoleId] : [] }, !pingRoleId); } - return function handleBusEvent(event) { + function sanitizeReplayTitleForFilename(title) { + const cleaned = String(title || '').replace(/[\\/:*?"<>|]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 96); + return cleaned || 'replay'; + } + + async function sendReplayToChannel(channelId, requester, sources = [], explicitTitle = '', includeSidebar = true) { + if (!channelId) throw new Error('Replay channel not configured'); + const resolvedTitle = String(explicitTitle || '').trim() || `${String(requester || 'Someone').trim() || 'Someone'} replay`; + const { buffer } = await buildReplayVideo({ sources, title: resolvedTitle, requester, includeSidebar }); + const attachment = new AttachmentBuilder(buffer, { name: `${sanitizeReplayTitleForFilename(resolvedTitle)}.mp4` }); + await sendToChannel(channelId, `**${resolvedTitle}**`, { files: [attachment] }, { parse: [] }); + } + + function handleReplayRequested(event) { + const payload = event?.payload || {}; + sendReplayToChannel( + payload?.channelId, + payload?.requester, + payload?.sources || [], + payload?.title || '', + payload?.includeSidebar !== false, + ).catch((err) => { + logger.warn('Replay send failed', { error: err.message }); + }); + } + + function handleBusEvent(event) { const { type, payload } = event || {}; const channels = discordConfig.channels || {}; const roles = discordConfig.roles || {}; @@ -84,10 +110,15 @@ function createBusEventHandler(deps) { 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] : [] }); break; } + case 'replay.requested': + handleReplayRequested(event); + break; default: break; } - }; + } + + return { handleBusEvent, handleReplayRequested }; } module.exports = { createBusEventHandler }; diff --git a/server/src/services/discordBotService/integrations/index.js b/server/src/services/discordBotService/integrations/index.js index 9b5557dd..88bd22bf 100644 --- a/server/src/services/discordBotService/integrations/index.js +++ b/server/src/services/discordBotService/integrations/index.js @@ -19,7 +19,7 @@ function createIntegrations(deps) { const dm = createDmModerationHandlers({ ...deps, sanitizeMentions }); const chat = createChatBridgeHandlers({ ...deps, clearTypingMessage, sendTypingMessage, formatWebhookUsername, getTypingId }); - const handleBusEvent = createBusEventHandler({ ...deps, sendToChannel, schedulePresenceRotation, formatDuration }); + const { handleBusEvent, handleReplayRequested } = createBusEventHandler({ ...deps, sendToChannel, schedulePresenceRotation, formatDuration }); function register() { client.on('typingStart', (typing) => { @@ -32,6 +32,7 @@ function createIntegrations(deps) { }); subscribe('*', handleBusEvent); + subscribe('replay.requested', handleReplayRequested); subscribe('verification.requested', dm.sendVerificationRequestDms); subscribe('privateRoverAccess.requested', dm.sendPrivateRoverAccessRequestDms); subscribe('chat:message', chat.handleChatBridgeOutbound); diff --git a/server/src/services/replayEngineV2/socketHooks.js b/server/src/services/replayEngineV2/socketHooks.js index f753f6c2..31f56147 100644 --- a/server/src/services/replayEngineV2/socketHooks.js +++ b/server/src/services/replayEngineV2/socketHooks.js @@ -28,7 +28,7 @@ function normalizeIncludeSidebar(value) { function registerReplaySocketHooks({ tryTriggerReplay, validateSources, getDefaultWebSources }) { io.on('connection', (socket) => { - socket.on('replay:trigger', (payload = {}, cb = () => {}) => { + const handleReplayTrigger = (payload = {}, cb = () => {}) => { if (getMode() === MODES.LOCKDOWN) { cb({ error: 'Replay disabled in lockdown', state: null }); return; @@ -72,7 +72,9 @@ function registerReplaySocketHooks({ tryTriggerReplay, validateSources, getDefau }); logger.info('Replay requested via web', { socketId: socket.id }); cb({ success: true, state: attempt.state }); - }); + }; + + socket.on('replay:trigger', handleReplayTrigger); }); }