generated from nhcarrigan/template
1c31a49bc4
## 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>
75 lines
2.2 KiB
TypeScript
75 lines
2.2 KiB
TypeScript
/**
|
|
* @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 */
|
|
/* eslint-disable max-lines-per-function -- Event handler checks multiple independent change types */
|
|
|
|
import { logActivity } from "../modules/logActivity.js";
|
|
import { logger } from "../utils/logger.js";
|
|
import type { PartialUser, User } from "discord.js";
|
|
|
|
/**
|
|
* Logs user profile update events (username, display name, avatar) to the
|
|
* activity log channel. Skips bots.
|
|
* @param oldUser - The user before the update.
|
|
* @param newUser - The user after the update.
|
|
* @returns A promise that resolves when all applicable changes have been logged.
|
|
*/
|
|
export const onUserUpdate = async(
|
|
oldUser: PartialUser | User,
|
|
newUser: User,
|
|
): Promise<void> => {
|
|
if (newUser.bot) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (oldUser.username !== newUser.username) {
|
|
await logActivity({
|
|
client: newUser.client,
|
|
emoji: "✏️",
|
|
fields: [
|
|
`**User**: ${newUser.username} (\`${newUser.id}\`)`,
|
|
`**Before**: ${oldUser.username ?? "*(unknown)*"}`,
|
|
`**After**: ${newUser.username}`,
|
|
].join("\n"),
|
|
title: "Username Changed",
|
|
});
|
|
}
|
|
|
|
if (oldUser.globalName !== newUser.globalName) {
|
|
await logActivity({
|
|
client: newUser.client,
|
|
emoji: "📛",
|
|
fields: [
|
|
`**User**: ${newUser.username} (\`${newUser.id}\`)`,
|
|
`**Before**: ${oldUser.globalName ?? "*(none)*"}`,
|
|
`**After**: ${newUser.globalName ?? "*(none)*"}`,
|
|
].join("\n"),
|
|
title: "Display Name Changed",
|
|
});
|
|
}
|
|
|
|
if (oldUser.avatar !== newUser.avatar) {
|
|
await logActivity({
|
|
client: newUser.client,
|
|
emoji: "🖼️",
|
|
fields: [
|
|
`**User**: ${newUser.username} (\`${newUser.id}\`)`,
|
|
`**New Avatar**: ${newUser.displayAvatarURL()}`,
|
|
].join("\n"),
|
|
title: "Avatar Changed",
|
|
});
|
|
}
|
|
} catch (error) {
|
|
await logger.error(
|
|
"Failed to log user update",
|
|
error instanceof Error
|
|
? error
|
|
: new Error(String(error)),
|
|
);
|
|
}
|
|
};
|