mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-11-14 18:58:49 -05:00
green mode slop 1
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
// Operator Green Command
|
||||
// Purpose: Toggles the intentionally silly server-wide green visual and room-light mode.
|
||||
// Scope: Keeps command presentation here while Home Assistant owns the runtime policy.
|
||||
const { getCommandConfig } = require('../../operatorCommandService/config');
|
||||
|
||||
function createGreenCommand({ greenModeService, sanitizeMentions, config }) {
|
||||
const { prefix: commandPrefix } = getCommandConfig(config);
|
||||
|
||||
return async function handleGreenCommand(message, tokens = []) {
|
||||
const action = String(tokens.shift() || '').trim().toLowerCase();
|
||||
if (action !== 'on' && action !== 'off') {
|
||||
await message.reply({
|
||||
content: `Invalid green command. Use \`${commandPrefix} green on\` or \`${commandPrefix} green off\`.`,
|
||||
allowedMentions: { parse: [], repliedUser: false },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const enabled = action === 'on';
|
||||
const result = await greenModeService.setEnabled(enabled, {
|
||||
source: `bot-command:green:${action}`,
|
||||
});
|
||||
await message.reply({
|
||||
content: sanitizeMentions(result ? 'Green mode enabled.' : 'Green mode disabled.'),
|
||||
allowedMentions: { parse: [], repliedUser: false },
|
||||
});
|
||||
} catch (err) {
|
||||
await message.reply({
|
||||
content: sanitizeMentions(`Failed to update green mode: ${err.message}`),
|
||||
allowedMentions: { parse: [], repliedUser: false },
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { createGreenCommand };
|
||||
@@ -10,6 +10,7 @@ const { createVerifyCommand } = require('./commands/verify');
|
||||
const { createDeterCommand } = require('./commands/deter');
|
||||
const { createPermissionsCommand } = require('./commands/permissions');
|
||||
const { createLightsCommand } = require('./commands/lights');
|
||||
const { createGreenCommand } = require('./commands/green');
|
||||
const { createKickCommand } = require('./commands/kick');
|
||||
const { createLiftCommand } = require('./commands/lift');
|
||||
const { createNeatoCommand } = require('./commands/neato');
|
||||
@@ -61,6 +62,7 @@ function createCommandHandlers(deps) {
|
||||
const handleBridgeCommand = transportHandlers.bridge;
|
||||
const handleTimeStatusCommand = transportHandlers.timeStatus;
|
||||
const handleLightsCommand = createLightsCommand(deps);
|
||||
const handleGreenCommand = createGreenCommand(deps);
|
||||
const handleKickCommand = createKickCommand(deps);
|
||||
const handleLiftCommand = createLiftCommand(deps);
|
||||
const handleNeatoCommand = createNeatoCommand(deps);
|
||||
@@ -108,7 +110,7 @@ function createCommandHandlers(deps) {
|
||||
// is included because its lock/unlock subcommands change room policy. Its
|
||||
// ordinary on/off/color actions are also intentionally restricted to a
|
||||
// lockdown admin while the entire server is in lockdown.
|
||||
const moderationActions = new Set(['lock', 'unlock', 'mode', 'goal', 'reason', 'verify', 'deter', 'permissions', 'lights', 'kick', 'lift', 'neato']);
|
||||
const moderationActions = new Set(['lock', 'unlock', 'mode', 'goal', 'reason', 'verify', 'deter', 'permissions', 'lights', 'green', 'kick', 'lift', 'neato']);
|
||||
const isAccessModeCommand = commandDefinition?.permission === 'access-mode';
|
||||
|
||||
// Feature commands are public activities while access is open or managed
|
||||
@@ -144,6 +146,8 @@ function createCommandHandlers(deps) {
|
||||
return handleBridgeCommand(request, tokens);
|
||||
case 'lights':
|
||||
return handleLightsCommand(request, tokens);
|
||||
case 'green':
|
||||
return handleGreenCommand(request, tokens);
|
||||
case 'kick':
|
||||
return handleKickCommand(request, rest);
|
||||
case 'lift':
|
||||
|
||||
@@ -30,6 +30,9 @@ function createRouter({ mode = MODES.OPEN, featureEnabled = true } = {}) {
|
||||
isFeatureEnabled: () => featureEnabled,
|
||||
sanitizeMentions: (text) => String(text || ''),
|
||||
homeAssistantService: { getLightPolicyState: () => ({}), setAllControllableEntitiesState: () => Promise.resolve() },
|
||||
// Green mode is admin-only at the dispatcher, so this double only needs to
|
||||
// prove that an authorized command reaches the small standalone service.
|
||||
greenModeService: { setEnabled: (enabled) => Promise.resolve(Boolean(enabled)) },
|
||||
liftService: null,
|
||||
neatoService: null,
|
||||
listVerifiedUsers: () => [],
|
||||
@@ -72,7 +75,7 @@ const admin = { id: 's1', userId: 'u-alice', label: 'alice', isAdmin: true, isLo
|
||||
|
||||
test('admin-only commands stay admin-only for a non-admin', async () => {
|
||||
const run = createRouter();
|
||||
for (const command of ['rs lock rover-1', 'rs unlock rover-1', 'rs mode open', 'rs kick alice', 'rs permissions list']) {
|
||||
for (const command of ['rs lock rover-1', 'rs unlock rover-1', 'rs mode open', 'rs green on', 'rs kick alice', 'rs permissions list']) {
|
||||
assert.match(await run(command, nonAdmin), ADMIN_DENIAL, `${command} must stay admin-only`);
|
||||
}
|
||||
});
|
||||
@@ -100,11 +103,22 @@ test('admin mode restricts access-mode feature commands', async () => {
|
||||
|
||||
test('lockdown still suspends the pre-existing moderation-sensitive commands', async () => {
|
||||
const run = createRouter({ mode: MODES.LOCKDOWN });
|
||||
for (const command of ['rs lock rover-1', 'rs mode open', 'rs lights on', 'rs goal', 'rs kick alice']) {
|
||||
for (const command of ['rs lock rover-1', 'rs mode open', 'rs lights on', 'rs green on', 'rs goal', 'rs kick alice']) {
|
||||
assert.match(await run(command, admin), LOCKDOWN_DENIAL, `${command} should stay lockdown-gated`);
|
||||
}
|
||||
});
|
||||
|
||||
test('admins can toggle green mode through the shared command path', async () => {
|
||||
const run = createRouter();
|
||||
assert.match(await run('rs green on', admin), /Green mode enabled/);
|
||||
assert.match(await run('rs green off', admin), /Green mode disabled/);
|
||||
});
|
||||
|
||||
test('green mode rejects unknown actions with focused usage guidance', async () => {
|
||||
const run = createRouter();
|
||||
assert.match(await run('rs green maybe', admin), /rs green on.*rs green off/);
|
||||
});
|
||||
|
||||
test('status and help survive lockdown', async () => {
|
||||
const run = createRouter({ mode: MODES.LOCKDOWN });
|
||||
assert.match(await run('rs status', nonAdmin), /\[status handler\]/);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Scope: Supplies transport-neutral metadata; execution handlers remain focused on server operations.
|
||||
const CATEGORIES = {
|
||||
system: { title: 'System', names: ['help', 'status', 'replay', 'time-status'] },
|
||||
admin: { title: 'Admin', names: ['lock', 'unlock', 'mode', 'reason', 'goal', 'kick', 'verify', 'deter', 'permissions'] },
|
||||
admin: { title: 'Admin', names: ['lock', 'unlock', 'mode', 'reason', 'goal', 'green', 'kick', 'verify', 'deter', 'permissions'] },
|
||||
features: { title: 'Features', names: ['lights', 'lift', 'neato'] },
|
||||
discord: { title: 'Discord', names: ['bridge'] },
|
||||
};
|
||||
@@ -19,6 +19,7 @@ function buildCommandRegistry(prefix, timeCommand) {
|
||||
mode: { category: 'admin', summary: 'Change the server mode.', usage: [`${prefix} mode <open|turns|admin|lockdown>`], access: 'Admin', permission: 'admin' },
|
||||
reason: { category: 'admin', summary: 'Show, set, or clear the admin-mode reason.', usage: [`${prefix} reason [text|clear]`], access: 'Admin to change' },
|
||||
goal: { category: 'admin', summary: 'Show, set, or clear the global objective.', usage: [`${prefix} goal [text|clear]`], access: 'Admin to change' },
|
||||
green: { category: 'admin', summary: 'Toggle green room and page mode.', usage: [`${prefix} green <on|off>`], access: 'Admin', permission: 'admin', requiredFeature: 'homeAssistant', unavailableLabel: 'Home Assistant' },
|
||||
lights: {
|
||||
category: 'features',
|
||||
summary: 'Control room lights or manage the admin light lock.',
|
||||
|
||||
Reference in New Issue
Block a user