This commit is contained in:
legop3
2026-07-12 15:36:48 -04:00
parent 60eacf982c
commit e254eea9e4
16 changed files with 129 additions and 44 deletions
@@ -40,6 +40,30 @@ function resolveRoverName(roverId) {
return record?.meta?.name || null;
}
function isPtzChatTargetId(roverId) {
/*
PTZ is intentionally treated as a virtual rover for chat identity only. It
does not live in roverManager.rovers because movement, video authorization,
and queue ownership are PTZ-service concerns, but chat needs one stable
"rover-like" id so the existing web UI, Discord bridge, and AI transcript
code can all render the same badge without learning PTZ internals.
*/
return Boolean(roverId) && String(roverId) === ptzCameraService.PTZ_CAMERA_ID;
}
function isPublicChatTargetId(roverId, socket = null) {
if (!roverId) return false;
/*
Normal rovers remain governed by the existing replay visibility rule, which
is also the rule chat historically used to avoid exposing closed private
rover activity. PTZ gets an explicit allow-list entry here because it is a
public chat target that deliberately pretends to be a rover, even though it
is not a roverManager record.
*/
if (isPtzChatTargetId(roverId)) return true;
return roverManager.canReplayRoverId(roverId, socket) === true;
}
function isPrivateClosedRoverId(roverId) {
if (!roverId) return false;
/*
@@ -47,8 +71,7 @@ function isPrivateClosedRoverId(roverId) {
but it is not a private rover. Let PTZ-badged messages broadcast normally
instead of falling into the closed-private rover path for unknown ids.
*/
if (String(roverId) === ptzCameraService.PTZ_CAMERA_ID) return false;
return roverManager.canReplayRoverId(roverId) !== true;
return !isPublicChatTargetId(roverId);
}
function normalizeProfileImageUrl(value) {
@@ -208,6 +231,8 @@ function buildTypingPayload(socket, meta = {}) {
module.exports = {
resolveRoverId,
isPtzChatTargetId,
isPublicChatTargetId,
isPrivateClosedRoverId,
buildRoverCtxSnapshot,
buildMessage,
@@ -7,6 +7,7 @@ const { getRole } = require('../roleService');
const roverManager = require('../roverManager');
const { issueCommand } = require('../commandService');
const { getAdminReason } = require('../adminReasonService');
const ptzCameraService = require('../ptzCameraService');
const {
TYPING_NOTE_DURATION,
ACCESS_NOTICE_COOLDOWN_MS,
@@ -18,6 +19,13 @@ const { getLastAccessNoticeAt, setLastAccessNoticeAt } = require('./state');
function playTypingNote(roverId, note, socketId) {
if (!roverId) return;
/*
PTZ borrows the roverId field for chat badges, but it has no rover command
channel. Skipping the song command here keeps PTZ chat from producing noisy
"unknown rover" command attempts while still allowing the message itself to
behave like rover chat everywhere else.
*/
if (String(roverId) === ptzCameraService.PTZ_CAMERA_ID) return;
try {
issueCommand(roverId, {
type: 'song',
@@ -83,6 +91,12 @@ function maybeSendAccessNotice(message, sendSystemMessage) {
function maybeSpeak(socket, message, ttsOptions) {
if (!ttsOptions || !message?.roverId) return;
/*
TTS is a physical-rover capability backed by commandService and rover audio
metadata. PTZ is only rover-like for chat identity, so a PTZ chat message
should not try to speak through a non-existent rover record.
*/
if (String(message.roverId) === ptzCameraService.PTZ_CAMERA_ID) return;
const record = roverManager.rovers.get(message.roverId);
const ttsEnabled = Boolean(record?.meta?.audio?.ttsEnabled);
if (!ttsEnabled) return;