Files
keiko/src/events/voiceStateUpdate.ts
T
hikari 76e559876b
Node.js CI / CI (pull_request) Failing after 15s
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 54s
feat: rewrite as moderation bot (Keiko)
Replaces the old AI companion bot with a full Discord moderation system.

Commands: warn, mute, unmute, kick, softban, ban, unban, prune
Logging: member join/leave, activity (messages/threads/voice), mod actions
Audit log: captures manual bans, kicks, timeouts, and unbans
Sanctions: posts to Hikari sanction API for all applicable actions

All commands are ephemeral and use Components v2. Permission and role
hierarchy checks are enforced on every applicable command.
2026-03-24 20:15:41 -07:00

76 lines
2.0 KiB
TypeScript

/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable unicorn/no-keyword-prefix -- oldState/newState are the established Discord.js parameter names */
import { logActivity } from "../modules/logActivity.js";
import { logger } from "../utils/logger.js";
import type { VoiceState } from "discord.js";
/**
* Resolves the action string for a voice state change.
* @param oldState - The voice state before the update.
* @param newState - The voice state after the update.
* @returns A human-readable description of the voice action.
*/
const resolveVoiceAction = (
oldState: VoiceState,
newState: VoiceState,
): string => {
if (!oldState.channel && newState.channel) {
return `Joined <#${newState.channelId ?? "Unknown"}>`;
}
if (oldState.channel && !newState.channel) {
return `Left <#${oldState.channelId ?? "Unknown"}>`;
}
return `Moved from <#${oldState.channelId ?? "Unknown"}> to <#${newState.channelId ?? "Unknown"}>`;
};
/**
* Logs voice channel join, leave, and move events to the activity log channel.
* @param oldState - The voice state before the update.
* @param newState - The voice state after the update.
* @returns A promise that resolves when the event has been logged.
*/
export const onVoiceStateUpdate = async(
oldState: VoiceState,
newState: VoiceState,
): Promise<void> => {
if (oldState.channelId === newState.channelId) {
return;
}
const user = newState.member?.user ?? oldState.member?.user;
if (!user || user.bot) {
return;
}
try {
const action = resolveVoiceAction(oldState, newState);
const fields = [
`**User**: ${user.username} (\`${user.id}\`)`,
`**Action**: ${action}`,
].join("\n");
await logActivity({
client: user.client,
emoji: "🔊",
fields: fields,
title: "Voice State Update",
});
} catch (error) {
await logger.error(
"Failed to log voice state update",
error instanceof Error
? error
: new Error(String(error)),
);
}
};