generated from nhcarrigan/template
feat: rewrite as moderation bot (#11)
## Summary - Replaces the old AI companion bot with a full Discord moderation system - Adds 8 slash commands: `warn`, `mute`, `unmute`, `kick`, `softban`, `ban`, `unban`, `prune` - Adds logging for member join/leave, activity (messages, threads, voice), and moderation actions - Audit log integration captures manual bans, kicks, timeouts, and unbans - All applicable actions post sanctions to the Hikari sanction API - All commands are ephemeral, use Components v2, and enforce permission + role hierarchy checks ## Test plan - [ ] Run `pnpm register` to register all 8 commands to the guild - [ ] Verify each command appears in Discord and is only visible to members with the appropriate permissions - [ ] Test each command against a valid target and confirm mod log entry, DM notification, and sanction record - [ ] Test each command against an invalid target (equal/higher role, self, bot) and confirm correct error response - [ ] Perform a manual ban, kick, and timeout in the Discord UI and confirm audit log handler picks them up - [ ] Perform a manual unban and confirm it logs correctly without creating a sanction - [ ] Verify join/leave messages appear in the welcome log channel - [ ] Verify message edits, deletes, thread events, and voice state changes appear in the activity log channel ✨ This issue was created with help from Hikari~ 🌸 Reviewed-on: #11 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #11.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
/* eslint-disable unicorn/no-keyword-prefix -- old/new prefixes are the established Discord.js event parameter names */
|
||||
|
||||
import { logActivity } from "../modules/logActivity.js";
|
||||
import { logger } from "../utils/logger.js";
|
||||
import type { AnyThreadChannel } from "discord.js";
|
||||
|
||||
/**
|
||||
* Logs thread update events to the activity log channel.
|
||||
* Only logs when the thread name changes to avoid noise from archive state changes.
|
||||
* @param oldThread - The thread state before the update.
|
||||
* @param newThread - The thread state after the update.
|
||||
* @returns A promise that resolves when the event has been logged.
|
||||
*/
|
||||
export const onThreadUpdate = async(
|
||||
oldThread: AnyThreadChannel,
|
||||
newThread: AnyThreadChannel,
|
||||
): Promise<void> => {
|
||||
if (
|
||||
oldThread.name === newThread.name
|
||||
&& oldThread.archived === newThread.archived
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const changes: Array<string> = [];
|
||||
|
||||
if (oldThread.name !== newThread.name) {
|
||||
changes.push(`**Name**: \`${oldThread.name}\` → \`${newThread.name}\``);
|
||||
}
|
||||
|
||||
if (oldThread.archived !== newThread.archived) {
|
||||
const wasArchived = oldThread.archived === true
|
||||
? "Yes"
|
||||
: "No";
|
||||
const isArchived = newThread.archived === true
|
||||
? "Yes"
|
||||
: "No";
|
||||
changes.push(`**Archived**: ${wasArchived} → ${isArchived}`);
|
||||
}
|
||||
|
||||
const fields = [
|
||||
`**Thread**: ${newThread.name} (\`${newThread.id}\`)`,
|
||||
`**Parent**: <#${newThread.parentId ?? "Unknown"}>`,
|
||||
...changes,
|
||||
].join("\n");
|
||||
|
||||
await logActivity({
|
||||
client: newThread.client,
|
||||
emoji: "🔄",
|
||||
fields: fields,
|
||||
title: "Thread Updated",
|
||||
});
|
||||
} catch (error) {
|
||||
await logger.error(
|
||||
"Failed to log thread update",
|
||||
error instanceof Error
|
||||
? error
|
||||
: new Error(String(error)),
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user