trying to

This commit is contained in:
legop3
2026-05-01 01:50:35 -04:00
parent 7e7eb97ff6
commit 02d5d6ca13
7 changed files with 238 additions and 87 deletions
@@ -23,6 +23,15 @@ function formatChargeState(batteryState) {
return `${chargeText} (${percentText})`; 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) { function formatOiEmoji(oiMode) {
if (oiMode === 'full') return '🕹️'; if (oiMode === 'full') return '🕹️';
if (oiMode === 'safe') 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 lockLabel = snapshot.locked ? `locked${snapshot.lockReason ? ` (${snapshot.lockReason})` : ''}` : 'unlocked';
const dockLabel = snapshot.docked ? 'docked' : 'undocked'; const dockLabel = snapshot.docked ? 'docked' : 'undocked';
const chargingLabel = snapshot.charging ? `charging (${snapshot.chargingLabel})` : 'not charging'; 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 = [ const lines = [
`Dock: ${dockLabel}`, `Dock: ${dockLabel}`,
@@ -77,12 +92,12 @@ function buildBatteryStatusEmbed({ color = 0x2196f3, records = [] }) {
`Battery: ${formatChargeState(snapshot.batteryState)}`, `Battery: ${formatChargeState(snapshot.batteryState)}`,
`Voltage: ${formatVoltage(snapshot.voltageMv)}`, `Voltage: ${formatVoltage(snapshot.voltageMv)}`,
`Current: ${formatCurrent(snapshot.currentMa)}`, `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({ embed.addFields({
name: snapshot.name, name: `${header} ${snapshot.name}`,
value: lines.join('\n'), value: lines.join('\n'),
inline: true, inline: true,
}); });
@@ -16,8 +16,11 @@ function formatHelp() {
'`rs mode <open|turns|admin|lockdown>` — change server mode', '`rs mode <open|turns|admin|lockdown>` — change server mode',
'`rs reason [text|clear]` — show or set admin mode reason', '`rs reason [text|clear]` — show or set admin mode reason',
'`rs goal [text|clear]` — show or set global objective', '`rs goal [text|clear]` — show or set global objective',
'`rs verify list|remove ...` — manage verified users', '`rs verify list` — list verified users (lockdown admins)',
'`rs deter list|ban|unban ...` — manage deterred users', '`rs verify remove <cookieUserId|nickname>` — remove verified user (lockdown admins)',
'`rs deter list` — list deterred users (lockdown admins)',
'`rs deter ban <cookieUserId|nickname|ip> [reason]` — deter a user (lockdown admins)',
'`rs deter unban <id|cookieUserId|nickname|ip>` — remove deterrence (lockdown admins)',
'`ts` — show time status', '`ts` — show time status',
].join('\n'); ].join('\n');
} }
@@ -12,30 +12,57 @@ function createReplayCommand({ getMode, MODES, tryTriggerReplay, getReplaySource
} }
function buildDefaultReplayTitle(requester, sources = []) { function buildDefaultReplayTitle(requester, sources = []) {
const requesterLabel = String(requester || 'Someone').trim() || 'Someone'; const requesterLabel = String(requester || 'Someone').trim() || 'Someone';
const labels = (Array.isArray(sources) ? sources : []) const roverSource = (Array.isArray(sources) ? sources : []).find((entry) => entry?.type === 'rover');
.map((entry) => String(entry?.label || entry?.id || '').trim()) const roverLabel = roverSource?.label || roverSource?.id || 'a rover';
.filter(Boolean); return `${requesterLabel} driving ${roverLabel}`;
if (!labels.length) return `${requesterLabel} replay`;
if (labels.length === 1) return `${requesterLabel} replay: ${labels[0]}`;
return `${requesterLabel} replay: ${labels.slice(0, 3).join(' + ')}`;
} }
function buildDriverSummary(sources = []) { function buildReplayDriverLines(requester, sources = []) {
const activeDrivers = getActiveDrivers(); const activeDrivers = getActiveDrivers();
const roverSources = (Array.isArray(sources) ? sources : []).filter((entry) => entry?.type === 'rover'); const roverSources = (Array.isArray(sources) ? sources : []).filter((entry) => entry?.type === 'rover');
const requestedRoverIds = new Set(roverSources.map((entry) => String(entry.id)));
const lines = []; const lines = [];
roverSources.forEach((source) => { requestedRoverIds.forEach((roverId) => {
const roverId = String(source.id);
const socketId = activeDrivers?.[roverId]; const socketId = activeDrivers?.[roverId];
if (!socketId) return; if (!socketId) return;
const socket = io.sockets.sockets.get(socketId); const socket = io.sockets.sockets.get(socketId);
const nickname = getNickname(socket) || socket?.data?.user?.username || socketId; const nickname = getNickname(socket) || socket?.data?.user?.username || socketId;
const record = rovers.get(roverId); const record = rovers.get(roverId);
const roverName = record?.meta?.name || source?.label || roverId; const roverName = record?.meta?.name || record?.id || roverId;
lines.push(`${nickname}${roverName}`); 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 lines;
return `Drivers: ${lines.join(' | ')}`; }
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) { function resolveReplaySources(query) {
const cleaned = normalizeReplayQuery(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 requester = message.member?.nickname || message.author?.globalName || message.author?.username || 'Discord';
const title = buildDefaultReplayTitle(requester, resolved.sources || []); const title = buildDefaultReplayTitle(requester, resolved.sources || []);
try { 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 attachment = new AttachmentBuilder(buffer, { name: `${sanitizeReplayTitleForFilename(title)}.mp4` });
const summary = buildDriverSummary(resolved.sources || []); const body = buildReplayCaption({ requester, usedSources, missingSources, title });
const body = [ `**${title}**`, summary ].filter(Boolean).join('\n');
await message.reply({ content: sanitizeMentions(body), files: [attachment], allowedMentions: { parse: [], repliedUser: false } }); await message.reply({ content: sanitizeMentions(body), files: [attachment], allowedMentions: { parse: [], repliedUser: false } });
} catch (err) { } catch (err) {
await message.reply({ content: sanitizeMentions(`Replay failed: ${err.message}`), allowedMentions: { parse: [], repliedUser: false } }); await message.reply({ content: sanitizeMentions(`Replay failed: ${err.message}`), allowedMentions: { parse: [], repliedUser: false } });
@@ -26,24 +26,32 @@ function createTimeStatusCommand({ config, discordConfig }) {
const serverTimezone = getServerTimezone(); const serverTimezone = getServerTimezone();
const now = new Date(); const now = new Date();
const zones = [ const zones = [
'UTC', { label: 'UTC', zone: 'UTC' },
'America/Los_Angeles', { label: 'US Pacific', zone: 'America/Los_Angeles' },
'America/Denver', { label: 'US Mountain', zone: 'America/Denver' },
'America/Chicago', { label: 'US Central', zone: 'America/Chicago' },
'America/New_York', { label: 'US Eastern', zone: 'America/New_York' },
'America/Halifax', { label: 'Europe London', zone: 'Europe/London' },
'Pacific/Honolulu', { label: 'Europe Berlin', zone: 'Europe/Berlin' },
'Europe/London', { label: 'Asia Kolkata', zone: 'Asia/Kolkata' },
'Europe/Berlin', { label: 'Asia Shanghai', zone: 'Asia/Shanghai' },
'Asia/Tokyo', { label: 'Asia Tokyo', zone: 'Asia/Tokyo' },
'Australia/Sydney', { label: 'Australia Sydney', zone: 'Australia/Sydney' },
'Pacific/Auckland', { label: 'New Zealand Auckland', zone: 'Pacific/Auckland' },
]; ];
if (!zones.includes(serverTimezone)) { const entries = zones.map((entry) => {
zones.push(serverTimezone); 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: entries.join('\n'), color: 0x2196f3 });
const embed = buildEmbed({ title: 'Time Status', description: lines.join('\n'), color: 0x2196f3 });
embed.setFooter({ text: `Server local timezone: ${serverTimezone}` }); embed.setFooter({ text: `Server local timezone: ${serverTimezone}` });
await message.reply({ embeds: [embed], allowedMentions: { parse: [], repliedUser: false } }); await message.reply({ embeds: [embed], allowedMentions: { parse: [], repliedUser: false } });
}; };
@@ -6,7 +6,7 @@ const io = require('../../../globals/io');
const { buildBatteryStatusEmbed, buildBatteryCaption } = require('../batteryEmbeds'); const { buildBatteryStatusEmbed, buildBatteryCaption } = require('../batteryEmbeds');
function createBusEventHandler(deps) { 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']); 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; 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; if (!channelId) return;
const prefix = pingRoleId ? `<@&${pingRoleId}> ` : ''; const prefix = prefixMentions && pingRoleId ? `<@&${pingRoleId}> ` : '';
const payloadEmbeds = Array.isArray(embeds) && embeds.length ? embeds : [buildEmbed({ title, description, color })]; 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); 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) { function sanitizeReplayTitleForFilename(title) {
const cleaned = String(title || '').replace(/[\\/:*?"<>|]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 96); const cleaned = String(title || '').replace(/[\\/:*?"<>|]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 96);
return cleaned || 'replay'; return cleaned || 'replay';
@@ -34,39 +65,75 @@ function createBusEventHandler(deps) {
function buildDefaultReplayTitle(requester, sources = []) { function buildDefaultReplayTitle(requester, sources = []) {
const requesterLabel = String(requester || 'Someone').trim() || 'Someone'; const requesterLabel = String(requester || 'Someone').trim() || 'Someone';
const labels = (Array.isArray(sources) ? sources : []) const roverSource = (Array.isArray(sources) ? sources : []).find((entry) => entry?.type === 'rover');
.map((entry) => String(entry?.label || entry?.id || '').trim()) const roverLabel = roverSource?.label || roverSource?.id || 'a rover';
.filter(Boolean); return `${requesterLabel} driving ${roverLabel}`;
if (!labels.length) return `${requesterLabel} replay`;
if (labels.length === 1) return `${requesterLabel} replay: ${labels[0]}`;
return `${requesterLabel} replay: ${labels.slice(0, 3).join(' + ')}`;
} }
function buildDriverSummary(sources = []) { function buildReplayDriverLines(requester, sources = []) {
const activeDrivers = getActiveDrivers(); const activeDrivers = getActiveDrivers();
const roverSources = (Array.isArray(sources) ? sources : []).filter((entry) => entry?.type === 'rover'); const roverSources = (Array.isArray(sources) ? sources : []).filter((entry) => entry?.type === 'rover');
const requestedRoverIds = new Set(roverSources.map((entry) => String(entry.id)));
const lines = []; const lines = [];
roverSources.forEach((source) => { requestedRoverIds.forEach((roverId) => {
const roverId = String(source.id);
const socketId = activeDrivers?.[roverId]; const socketId = activeDrivers?.[roverId];
if (!socketId) return; if (!socketId) return;
const socket = io.sockets.sockets.get(socketId); const socket = io.sockets.sockets.get(socketId);
const nickname = getNickname(socket) || socket?.data?.user?.username || socketId; const nickname = getNickname(socket) || socket?.data?.user?.username || socketId;
const record = rovers.get(roverId); const record = rovers.get(roverId);
const roverName = record?.meta?.name || source?.label || roverId; const roverName = record?.meta?.name || record?.id || roverId;
lines.push(`${nickname}${roverName}`); 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 lines;
return `Drivers: ${lines.join(' | ')}`; }
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) { async function sendReplayToChannel(channelId, requester, sources = [], explicitTitle = '', includeSidebar = true) {
if (!channelId) throw new Error('Replay channel not configured'); if (!channelId) throw new Error('Replay channel not configured');
const resolvedTitle = String(explicitTitle || '').trim() || buildDefaultReplayTitle(requester, sources); 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 attachment = new AttachmentBuilder(buffer, { name: `${sanitizeReplayTitleForFilename(resolvedTitle)}.mp4` });
const summary = buildDriverSummary(sources); const body = buildReplayCaption({
const body = [ `**${resolvedTitle}**`, summary ].filter(Boolean).join('\n'); requester,
usedSources,
missingSources,
title: resolvedTitle,
});
await sendToChannel(channelId, body, { files: [attachment] }, { parse: [] }); await sendToChannel(channelId, body, { files: [attachment] }, { parse: [] });
} }
@@ -94,19 +161,36 @@ function createBusEventHandler(deps) {
case 'mode.changed': case 'mode.changed':
if (!skippedFirstModeAnnouncement) { skippedFirstModeAnnouncement = true; schedulePresenceRotation(); break; } if (!skippedFirstModeAnnouncement) { skippedFirstModeAnnouncement = true; schedulePresenceRotation(); break; }
if (payload?.mode === MODES.OPEN || payload?.mode === MODES.TURNS) { 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(); schedulePresenceRotation();
break; break;
case 'globalObjective.updated': 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(); schedulePresenceRotation();
break; break;
case 'rover.online': case 'rover.online':
announce({ channelId: channels.adminAlerts, pingRoleId: roles.adminPing || null, color: 0x4caf50, title: 'Rover Online', description: `${payload?.roverId} is online.` }); announce({ channelId: channels.adminAlerts, pingRoleId: roles.adminPing || null, color: 0x4caf50, title: 'Rover Online', description: `${payload?.roverId} is online.` });
schedulePresenceRotation();
break; break;
case 'rover.offline': case 'rover.offline':
announce({ channelId: channels.adminAlerts, pingRoleId: roles.adminPing || null, color: 0xe53935, title: 'Rover Offline', description: `${payload?.roverId} went offline.` }); announce({ channelId: channels.adminAlerts, pingRoleId: roles.adminPing || null, color: 0xe53935, title: 'Rover Offline', description: `${payload?.roverId} went offline.` });
schedulePresenceRotation();
break; break;
case 'rover.dockGuard': case 'rover.dockGuard':
announce({ channelId: channels.adminAlerts, color: 0xf0b651, title: 'Dock Guard Triggered', description: `${payload?.roverId} (${payload?.reasonText || 'undocked'}) for ${formatDuration(payload?.idleMs)}.` }); 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.charging.stop':
case 'battery.locked': 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()) })] }); 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; break;
case 'battery.unlocked': 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()) })] }); 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; break;
case 'humanAlert.buttonPressed': { case 'humanAlert.buttonPressed': {
const imageBase64 = payload?.imageBase64 ? String(payload.imageBase64) : ''; 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' }); 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); } } 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; break;
} }
case 'replay.requested': case 'replay.requested':
handleReplayRequested(event); handleReplayRequested(event);
break; 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: default:
break; break;
} }
@@ -25,13 +25,14 @@ function createDmModerationHandlers(deps) {
const payload = event?.payload || {}; const payload = event?.payload || {};
const requestId = payload.id; const requestId = payload.id;
if (!requestId) return; if (!requestId) return;
const createdAt = payload.createdAt ? new Date(payload.createdAt).toLocaleString() : 'unknown';
const content = [ const content = [
'**Verification Request**', '**Verification Request**',
`Request ID: \`${requestId}\``, `Request ID: \`${requestId}\``,
`Nickname: ${sanitizeMentions(payload.nickname || 'unknown')}`, `Nickname: ${sanitizeMentions(payload.nickname || 'unknown')}`,
`Identity Key: \`${String(payload.cookieUserId || 'unknown')}\``, `Identity key: \`${String(payload.cookieUserId || 'unknown')}\``,
`IP: \`${String(payload.ip || 'unknown')}\``, `IP: \`${String(payload.ip || 'unknown')}\``,
`Socket: \`${String(payload.socketId || 'unknown')}\``, `Created: ${createdAt}`,
'', '',
`React with ${APPROVE} to approve or ${DENY} to deny.`, `React with ${APPROVE} to approve or ${DENY} to deny.`,
].join('\n'); ].join('\n');
@@ -54,16 +55,17 @@ function createDmModerationHandlers(deps) {
const requestId = payload.id; const requestId = payload.id;
if (!requestId) return; if (!requestId) return;
const requester = payload?.requester || {}; const requester = payload?.requester || {};
const createdAt = payload.createdAt ? new Date(payload.createdAt).toLocaleString() : 'unknown';
const content = [ const content = [
'**Private Rover Access Request**', '**Private Rover Access Request**',
`Request ID: \`${requestId}\``, `Request ID: \`${requestId}\``,
`Rover: ${sanitizeMentions(payload.roverName || payload.roverId || 'unknown')} (\`${String(payload.roverId || 'unknown')}\`)`, `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')}\``, `Role: \`${String(requester.role || 'unknown')}\``,
`Verified: \`${requester.isVerified ? 'yes' : 'no'}\``, `Verified: \`${requester.isVerified ? 'yes' : 'no'}\``,
`Identity Key: \`${String(requester.cookieUserId || 'unknown')}\``, `Identity key: \`${String(requester.cookieUserId || 'unknown')}\``,
`IP: \`${String(requester.ip || 'unknown')}\``, `IP: \`${String(requester.ip || 'unknown')}\``,
`Socket: \`${String(requester.socketId || 'unknown')}\``, `Created: ${createdAt}`,
'', '',
`React with ${APPROVE} to approve or ${DENY} to deny.`, `React with ${APPROVE} to approve or ${DENY} to deny.`,
].join('\n'); ].join('\n');
@@ -93,21 +95,13 @@ function createDmModerationHandlers(deps) {
if (emoji === APPROVE) { if (emoji === APPROVE) {
approveRequest(linked.request.id, user.id); approveRequest(linked.request.id, user.id);
await reaction.message.reply({ await reaction.message.reply({
content: [ content: `Approved request \`${linked.request.id}\`.`,
`✅ 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'),
allowedMentions: { parse: [] }, allowedMentions: { parse: [] },
}); });
} else { } else {
denyRequest(linked.request.id, user.id); denyRequest(linked.request.id, user.id);
await reaction.message.reply({ await reaction.message.reply({
content: [ content: `Denied request \`${linked.request.id}\`.`,
`❌ 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'),
allowedMentions: { parse: [] }, allowedMentions: { parse: [] },
}); });
} }
@@ -126,23 +120,18 @@ function createDmModerationHandlers(deps) {
const linked = getPrivateAccessRequestByMessageId(reaction.message?.id); const linked = getPrivateAccessRequestByMessageId(reaction.message?.id);
if (!linked?.request || linked.request.status !== 'pending') return; if (!linked?.request || linked.request.status !== 'pending') return;
if (emoji === APPROVE) { 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({ await reaction.message.reply({
content: [ content: `Approved private access request \`${linked.request.id}\`.${assignmentNote}`,
`✅ 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'),
allowedMentions: { parse: [] }, allowedMentions: { parse: [] },
}); });
} else { } else {
denyPrivateAccessRequest(linked.request.id, user.id); denyPrivateAccessRequest(linked.request.id, user.id);
await reaction.message.reply({ await reaction.message.reply({
content: [ content: `Denied private access request \`${linked.request.id}\`.`,
`❌ 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'),
allowedMentions: { parse: [] }, allowedMentions: { parse: [] },
}); });
} }
@@ -22,7 +22,7 @@ function createPresenceManager({ client, logger, getMode, getGlobalObjective, co
const objectiveText = objective?.text ? String(objective.text).trim() : ''; const objectiveText = objective?.text ? String(objective.text).trim() : '';
if (presenceShowObjective && objectiveText) { if (presenceShowObjective && objectiveText) {
const trimmed = truncatePresenceText(objectiveText, 110); const trimmed = truncatePresenceText(objectiveText, 110);
return `Objective: ${trimmed}`; return `Goal: ${trimmed}`;
} }
return `${mode} · ${ready}/${total} Rovers Ready`; return `${mode} · ${ready}/${total} Rovers Ready`;
} }