mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 17:40:46 -04:00
driver removal information finaly!
This commit is contained in:
@@ -12,6 +12,7 @@ function formatHelp() {
|
||||
'`rs bridge mode <global|private>` — change chat bridge mode',
|
||||
'`rs bridge off` — disable chat bridge for this server',
|
||||
'`rs lights <status|lock|unlock>` — show or change room light lock state',
|
||||
'`rs kick <user> [reason]` — remove a user from their current rover; use `user | reason` for multi-word names',
|
||||
'`rs lock <rover>` — lock a rover; rover names can be fuzzy',
|
||||
'`rs unlock <rover>` — unlock a rover; rover names can be fuzzy',
|
||||
'`rs mode <open|turns|admin|lockdown>` — change server mode',
|
||||
|
||||
@@ -13,6 +13,7 @@ const { createDeterCommand } = require('./deter');
|
||||
const { createBridgeCommand } = require('./bridge');
|
||||
const { createTimeStatusCommand } = require('./timeStatus');
|
||||
const { createLightsCommand } = require('./lights');
|
||||
const { createKickCommand } = require('./kick');
|
||||
|
||||
function createCommandHandlers(deps) {
|
||||
const {
|
||||
@@ -35,6 +36,7 @@ function createCommandHandlers(deps) {
|
||||
const handleBridgeCommand = createBridgeCommand(deps);
|
||||
const handleTimeStatusCommand = createTimeStatusCommand(deps);
|
||||
const handleLightsCommand = createLightsCommand(deps);
|
||||
const handleKickCommand = createKickCommand(deps);
|
||||
|
||||
async function handleCommand(message) {
|
||||
if (message.author.bot) return;
|
||||
@@ -58,7 +60,7 @@ function createCommandHandlers(deps) {
|
||||
// lockdown mode narrows them from normal admins to lockdown admins. Room
|
||||
// light locking belongs here because it can force the physical room lights
|
||||
// on and disables ordinary Home Assistant room controls for everyone else.
|
||||
const moderationActions = new Set(['lock', 'unlock', 'mode', 'goal', 'reason', 'verify', 'deter', 'lights']);
|
||||
const moderationActions = new Set(['lock', 'unlock', 'mode', 'goal', 'reason', 'verify', 'deter', 'lights', 'kick']);
|
||||
|
||||
if (!isAdmin && action !== '' && action !== 'status' && action !== 'help' && action !== 'replay' && action !== 'bridge' && action !== 'goal' && action !== 'reason' && action !== 'verify' && action !== 'deter') {
|
||||
await message.reply({ content: 'Only admins can run that command.', allowedMentions: { parse: [], repliedUser: false } });
|
||||
@@ -82,6 +84,8 @@ function createCommandHandlers(deps) {
|
||||
return handleBridgeCommand(message, tokens);
|
||||
case 'lights':
|
||||
return handleLightsCommand(message, tokens);
|
||||
case 'kick':
|
||||
return handleKickCommand(message, rest);
|
||||
case 'lock':
|
||||
return handleLockCommand(message, rest, true);
|
||||
case 'unlock':
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// Discord Kick Command
|
||||
// Purpose: Removes a connected user from their current rover without applying any persistent moderation state.
|
||||
// Scope: Resolves an online driver, sends them a UI-visible reason, and releases their current rover assignment.
|
||||
const Fuse = require('fuse.js');
|
||||
|
||||
const DEFAULT_KICK_REASON = 'Removed from rover by admin.';
|
||||
|
||||
function normalizeText(value) {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function normalizeSearchText(value) {
|
||||
return normalizeText(value).toLowerCase().replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
function splitSelectorAndReason(rawText) {
|
||||
const text = normalizeText(rawText);
|
||||
if (!text) return { selector: '', reason: '' };
|
||||
const pipeIndex = text.indexOf('|');
|
||||
if (pipeIndex >= 0) {
|
||||
/*
|
||||
A pipe delimiter is the escape hatch for multi-word nicknames. Without a
|
||||
delimiter the command intentionally treats the first token as the selector
|
||||
so quick admin commands stay short: `rs kick bob being reckless`.
|
||||
*/
|
||||
return {
|
||||
selector: normalizeText(text.slice(0, pipeIndex)),
|
||||
reason: normalizeText(text.slice(pipeIndex + 1)),
|
||||
};
|
||||
}
|
||||
const parts = text.split(/\s+/);
|
||||
return {
|
||||
selector: normalizeText(parts.shift()),
|
||||
reason: normalizeText(parts.join(' ')),
|
||||
};
|
||||
}
|
||||
|
||||
function buildKickCandidates({ io, roverManager, assignmentService, getNickname }) {
|
||||
return Array.from(io.sockets.sockets.values())
|
||||
.map((socket) => {
|
||||
const socketId = normalizeText(socket?.id);
|
||||
const assignedRoverId = assignmentService?.getAssignedRover?.(socketId) || null;
|
||||
const primaryRoverId = roverManager.getPrimaryRoverForSocket(socketId);
|
||||
const roverId = assignedRoverId || primaryRoverId || null;
|
||||
if (!socketId || !roverId) return null;
|
||||
const nickname = normalizeText(getNickname(socket));
|
||||
const username = normalizeText(socket?.data?.user?.username);
|
||||
return {
|
||||
socket,
|
||||
socketId,
|
||||
roverId,
|
||||
nickname,
|
||||
username,
|
||||
label: nickname || username || socketId.slice(0, 6),
|
||||
searchSocketId: normalizeSearchText(socketId),
|
||||
searchShortSocketId: normalizeSearchText(socketId.slice(0, 6)),
|
||||
searchNickname: normalizeSearchText(nickname),
|
||||
searchUsername: normalizeSearchText(username),
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function resolveKickTarget(selector, candidates) {
|
||||
const query = normalizeSearchText(selector);
|
||||
if (!query) return { error: 'Specify a user to kick. Example: `rs kick nickname reason`' };
|
||||
const exact = candidates.filter((entry) => (
|
||||
entry.searchSocketId === query ||
|
||||
entry.searchShortSocketId === query ||
|
||||
entry.searchNickname === query ||
|
||||
entry.searchUsername === query
|
||||
));
|
||||
if (exact.length === 1) return { target: exact[0] };
|
||||
if (exact.length > 1) {
|
||||
return { error: `User matched multiple drivers: ${exact.map((entry) => entry.label).join(', ')}.` };
|
||||
}
|
||||
const fuse = new Fuse(candidates, {
|
||||
includeScore: true,
|
||||
threshold: 0.38,
|
||||
ignoreLocation: true,
|
||||
keys: [
|
||||
{ name: 'nickname', weight: 0.7 },
|
||||
{ name: 'username', weight: 0.2 },
|
||||
{ name: 'socketId', weight: 0.1 },
|
||||
],
|
||||
});
|
||||
const results = fuse.search(selector);
|
||||
if (!results.length) return { error: 'User not found among current rover drivers.' };
|
||||
const first = results[0];
|
||||
const second = results[1];
|
||||
if (second && Math.abs(Number(second.score || 0) - Number(first.score || 0)) < 0.08) {
|
||||
return {
|
||||
error: `User matched multiple drivers: ${results.slice(0, 5).map((entry) => entry.item.label).join(', ')}.`,
|
||||
};
|
||||
}
|
||||
return { target: first.item };
|
||||
}
|
||||
|
||||
function createKickCommand({ io, roverManager, getNickname, sanitizeMentions }) {
|
||||
return async function handleKickCommand(message, rawText) {
|
||||
const { selector, reason } = splitSelectorAndReason(rawText);
|
||||
const assignmentService = require('../../assignmentService');
|
||||
const candidates = buildKickCandidates({
|
||||
io,
|
||||
roverManager,
|
||||
assignmentService,
|
||||
getNickname,
|
||||
});
|
||||
const resolved = resolveKickTarget(selector, candidates);
|
||||
if (resolved.error) {
|
||||
await message.reply({ content: sanitizeMentions(resolved.error), allowedMentions: { parse: [], repliedUser: false } });
|
||||
return;
|
||||
}
|
||||
const target = resolved.target;
|
||||
const removalReason = reason || DEFAULT_KICK_REASON;
|
||||
/*
|
||||
The command deliberately calls the notice-aware release helper instead of
|
||||
roverManager.releaseControl. That keeps admin kicks aligned with automated
|
||||
removals and gives the driver a stable explanation in the video panel.
|
||||
*/
|
||||
assignmentService.forceReleaseWithNotice(target.roverId, target.socketId, {
|
||||
title: 'Removed by admin',
|
||||
message: removalReason,
|
||||
reasonCode: 'admin-kick',
|
||||
actor: message.author?.id || null,
|
||||
});
|
||||
await message.reply({
|
||||
content: sanitizeMentions(`Removed ${target.label} from ${target.roverId}: ${removalReason}`),
|
||||
allowedMentions: { parse: [], repliedUser: false },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createKickCommand,
|
||||
};
|
||||
Reference in New Issue
Block a user