forked from syntaxbullet/aurorabot
feat: standardize command error handling (Sprint 4)
- Create withCommandErrorHandling utility in bot/lib/commandUtils.ts - Migrate economy commands: daily, exam, pay, trivia - Migrate inventory command: use - Migrate admin/moderation commands: warn, case, cases, clearwarning, warnings, note, notes, create_color, listing, webhook, refresh, terminal, featureflags, settings, prune - Add 9 unit tests for the utility - Update AGENTS.md with new recommended error handling pattern
This commit is contained in:
@@ -2,6 +2,7 @@ import { createCommand } from "@shared/lib/utils";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
|
||||
import { moderationService } from "@shared/modules/moderation/moderation.service";
|
||||
import { getCaseEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const moderationCase = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -16,39 +17,35 @@ export const moderationCase = createCommand({
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const caseId = interaction.options.getString("case_id", true).toUpperCase();
|
||||
|
||||
try {
|
||||
const caseId = interaction.options.getString("case_id", true).toUpperCase();
|
||||
// Validate case ID format
|
||||
if (!caseId.match(/^CASE-\d+$/)) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("Invalid case ID format. Expected format: CASE-0001")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate case ID format
|
||||
if (!caseId.match(/^CASE-\d+$/)) {
|
||||
// Get the case
|
||||
const moderationCase = await moderationService.getCaseById(caseId);
|
||||
|
||||
if (!moderationCase) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed(`Case **${caseId}** not found.`)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Display the case
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("Invalid case ID format. Expected format: CASE-0001")]
|
||||
embeds: [getCaseEmbed(moderationCase)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the case
|
||||
const moderationCase = await moderationService.getCaseById(caseId);
|
||||
|
||||
if (!moderationCase) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed(`Case **${caseId}** not found.`)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Display the case
|
||||
await interaction.editReply({
|
||||
embeds: [getCaseEmbed(moderationCase)]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Case command error:", error);
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("An error occurred while fetching the case.")]
|
||||
});
|
||||
}
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createCommand } from "@shared/lib/utils";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
||||
import { moderationService } from "@shared/modules/moderation/moderation.service";
|
||||
import { getCasesListEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
|
||||
import { getCasesListEmbed } from "@/modules/moderation/moderation.view";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const cases = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -22,33 +23,29 @@ export const cases = createCommand({
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const targetUser = interaction.options.getUser("user", true);
|
||||
const activeOnly = interaction.options.getBoolean("active_only") || false;
|
||||
|
||||
try {
|
||||
const targetUser = interaction.options.getUser("user", true);
|
||||
const activeOnly = interaction.options.getBoolean("active_only") || false;
|
||||
// Get cases for the user
|
||||
const userCases = await moderationService.getUserCases(targetUser.id, activeOnly);
|
||||
|
||||
// Get cases for the user
|
||||
const userCases = await moderationService.getUserCases(targetUser.id, activeOnly);
|
||||
const title = activeOnly
|
||||
? `⚠️ Active Cases for ${targetUser.username}`
|
||||
: `📋 All Cases for ${targetUser.username}`;
|
||||
|
||||
const title = activeOnly
|
||||
? `⚠️ Active Cases for ${targetUser.username}`
|
||||
: `📋 All Cases for ${targetUser.username}`;
|
||||
const description = userCases.length === 0
|
||||
? undefined
|
||||
: `Total cases: **${userCases.length}**`;
|
||||
|
||||
const description = userCases.length === 0
|
||||
? undefined
|
||||
: `Total cases: **${userCases.length}**`;
|
||||
|
||||
// Display the cases
|
||||
await interaction.editReply({
|
||||
embeds: [getCasesListEmbed(userCases, title, description)]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Cases command error:", error);
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("An error occurred while fetching cases.")]
|
||||
});
|
||||
}
|
||||
// Display the cases
|
||||
await interaction.editReply({
|
||||
embeds: [getCasesListEmbed(userCases, title, description)]
|
||||
});
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createCommand } from "@shared/lib/utils";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
||||
import { moderationService } from "@shared/modules/moderation/moderation.service";
|
||||
import { getClearSuccessEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const clearwarning = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -23,62 +24,58 @@ export const clearwarning = createCommand({
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const caseId = interaction.options.getString("case_id", true).toUpperCase();
|
||||
const reason = interaction.options.getString("reason") || "Cleared by moderator";
|
||||
|
||||
try {
|
||||
const caseId = interaction.options.getString("case_id", true).toUpperCase();
|
||||
const reason = interaction.options.getString("reason") || "Cleared by moderator";
|
||||
// Validate case ID format
|
||||
if (!caseId.match(/^CASE-\d+$/)) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("Invalid case ID format. Expected format: CASE-0001")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate case ID format
|
||||
if (!caseId.match(/^CASE-\d+$/)) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("Invalid case ID format. Expected format: CASE-0001")]
|
||||
// Check if case exists and is active
|
||||
const existingCase = await moderationService.getCaseById(caseId);
|
||||
|
||||
if (!existingCase) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed(`Case **${caseId}** not found.`)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!existingCase.active) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed(`Case **${caseId}** is already resolved.`)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingCase.type !== 'warn') {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed(`Case **${caseId}** is not a warning. Only warnings can be cleared.`)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the warning
|
||||
await moderationService.clearCase({
|
||||
caseId,
|
||||
clearedBy: interaction.user.id,
|
||||
clearedByName: interaction.user.username,
|
||||
reason
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if case exists and is active
|
||||
const existingCase = await moderationService.getCaseById(caseId);
|
||||
|
||||
if (!existingCase) {
|
||||
// Send success message
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed(`Case **${caseId}** not found.`)]
|
||||
embeds: [getClearSuccessEmbed(caseId)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!existingCase.active) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed(`Case **${caseId}** is already resolved.`)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingCase.type !== 'warn') {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed(`Case **${caseId}** is not a warning. Only warnings can be cleared.`)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the warning
|
||||
await moderationService.clearCase({
|
||||
caseId,
|
||||
clearedBy: interaction.user.id,
|
||||
clearedByName: interaction.user.username,
|
||||
reason
|
||||
});
|
||||
|
||||
// Send success message
|
||||
await interaction.editReply({
|
||||
embeds: [getClearSuccessEmbed(caseId)]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Clear warning command error:", error);
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("An error occurred while clearing the warning.")]
|
||||
});
|
||||
}
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { createCommand } from "@shared/lib/utils";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, AttachmentBuilder } from "discord.js";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
||||
import { getGuildConfig, invalidateGuildConfigCache } from "@shared/lib/config";
|
||||
import { guildSettingsService } from "@shared/modules/guild-settings/guild-settings.service";
|
||||
import { DrizzleClient } from "@shared/db/DrizzleClient";
|
||||
import { items } from "@db/schema";
|
||||
import { createSuccessEmbed, createErrorEmbed } from "@lib/embeds";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const createColor = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -32,62 +33,60 @@ export const createColor = createCommand({
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply();
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const name = interaction.options.getString("name", true);
|
||||
const colorInput = interaction.options.getString("color", true);
|
||||
const price = interaction.options.getNumber("price") || 500;
|
||||
const imageUrl = interaction.options.getString("image") || "https://cdn.discordapp.com/attachments/1450061247365124199/1453122950822760559/Main_Chip_1.png";
|
||||
|
||||
const name = interaction.options.getString("name", true);
|
||||
const colorInput = interaction.options.getString("color", true);
|
||||
const price = interaction.options.getNumber("price") || 500;
|
||||
const imageUrl = interaction.options.getString("image") || "https://cdn.discordapp.com/attachments/1450061247365124199/1453122950822760559/Main_Chip_1.png";
|
||||
// 1. Validate Color
|
||||
const colorRegex = /^#([0-9A-F]{3}){1,2}$/i;
|
||||
if (!colorRegex.test(colorInput)) {
|
||||
await interaction.editReply({ embeds: [createErrorEmbed("Invalid Hex Color code. Format: #RRGGBB")] });
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Validate Color
|
||||
const colorRegex = /^#([0-9A-F]{3}){1,2}$/i;
|
||||
if (!colorRegex.test(colorInput)) {
|
||||
await interaction.editReply({ embeds: [createErrorEmbed("Invalid Hex Color code. Format: #RRGGBB")] });
|
||||
return;
|
||||
}
|
||||
// 2. Create Role
|
||||
const role = await interaction.guild?.roles.create({
|
||||
name: name,
|
||||
color: colorInput as any,
|
||||
reason: `Created via /createcolor by ${interaction.user.tag}`
|
||||
});
|
||||
|
||||
try {
|
||||
// 2. Create Role
|
||||
const role = await interaction.guild?.roles.create({
|
||||
name: name,
|
||||
color: colorInput as any,
|
||||
reason: `Created via /createcolor by ${interaction.user.tag}`
|
||||
});
|
||||
if (!role) {
|
||||
throw new Error("Failed to create role.");
|
||||
}
|
||||
|
||||
if (!role) {
|
||||
throw new Error("Failed to create role.");
|
||||
}
|
||||
// 3. Add to guild settings
|
||||
await guildSettingsService.addColorRole(interaction.guildId!, role.id);
|
||||
invalidateGuildConfigCache(interaction.guildId!);
|
||||
|
||||
// 3. Add to guild settings
|
||||
await guildSettingsService.addColorRole(interaction.guildId!, role.id);
|
||||
invalidateGuildConfigCache(interaction.guildId!);
|
||||
// 4. Create Item
|
||||
await DrizzleClient.insert(items).values({
|
||||
name: `Color Role - ${name}`,
|
||||
description: `Use this item to apply the ${name} color to your name.`,
|
||||
type: "CONSUMABLE",
|
||||
rarity: "Common",
|
||||
price: BigInt(price),
|
||||
iconUrl: "",
|
||||
imageUrl: imageUrl,
|
||||
usageData: {
|
||||
consume: false,
|
||||
effects: [{ type: "COLOR_ROLE", roleId: role.id }]
|
||||
} as any
|
||||
});
|
||||
|
||||
// 4. Create Item
|
||||
await DrizzleClient.insert(items).values({
|
||||
name: `Color Role - ${name}`,
|
||||
description: `Use this item to apply the ${name} color to your name.`,
|
||||
type: "CONSUMABLE",
|
||||
rarity: "Common",
|
||||
price: BigInt(price),
|
||||
iconUrl: "",
|
||||
imageUrl: imageUrl,
|
||||
usageData: {
|
||||
consume: false,
|
||||
effects: [{ type: "COLOR_ROLE", roleId: role.id }]
|
||||
} as any
|
||||
});
|
||||
|
||||
// 5. Success
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(
|
||||
`**Role:** <@&${role.id}> (${colorInput})\n**Item:** Color Role - ${name}\n**Price:** ${price} 🪙`,
|
||||
"✅ Color Role & Item Created"
|
||||
)]
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Error in createcolor:", error);
|
||||
await interaction.editReply({ embeds: [createErrorEmbed(`Failed to create color role: ${error.message}`)] });
|
||||
}
|
||||
// 5. Success
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(
|
||||
`**Role:** <@&${role.id}> (${colorInput})\n**Item:** Color Role - ${name}\n**Price:** ${price} 🪙`,
|
||||
"✅ Color Role & Item Created"
|
||||
)]
|
||||
});
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createCommand } from "@shared/lib/utils";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, Colors, userMention, roleMention, ChatInputCommandInteraction } from "discord.js";
|
||||
import { featureFlagsService } from "@shared/modules/feature-flags/feature-flags.service";
|
||||
import { createBaseEmbed, createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
|
||||
import { UserError } from "@shared/lib/errors";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const featureflags = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -98,57 +98,53 @@ export const featureflags = createCommand({
|
||||
),
|
||||
autocomplete: async (interaction) => {
|
||||
const focused = interaction.options.getFocused(true);
|
||||
|
||||
|
||||
if (focused.name === "name") {
|
||||
const flags = await featureFlagsService.listFlags();
|
||||
const filtered = flags
|
||||
.filter(f => f.name.toLowerCase().includes(focused.value.toLowerCase()))
|
||||
.slice(0, 25);
|
||||
|
||||
|
||||
await interaction.respond(
|
||||
filtered.map(f => ({ name: `${f.name} (${f.enabled ? "enabled" : "disabled"})`, value: f.name }))
|
||||
);
|
||||
}
|
||||
},
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply();
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
try {
|
||||
switch (subcommand) {
|
||||
case "list":
|
||||
await handleList(interaction);
|
||||
break;
|
||||
case "create":
|
||||
await handleCreate(interaction);
|
||||
break;
|
||||
case "delete":
|
||||
await handleDelete(interaction);
|
||||
break;
|
||||
case "enable":
|
||||
await handleEnable(interaction);
|
||||
break;
|
||||
case "disable":
|
||||
await handleDisable(interaction);
|
||||
break;
|
||||
case "grant":
|
||||
await handleGrant(interaction);
|
||||
break;
|
||||
case "revoke":
|
||||
await handleRevoke(interaction);
|
||||
break;
|
||||
case "access":
|
||||
await handleAccess(interaction);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof UserError) {
|
||||
await interaction.editReply({ embeds: [createErrorEmbed(error.message)] });
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
switch (subcommand) {
|
||||
case "list":
|
||||
await handleList(interaction);
|
||||
break;
|
||||
case "create":
|
||||
await handleCreate(interaction);
|
||||
break;
|
||||
case "delete":
|
||||
await handleDelete(interaction);
|
||||
break;
|
||||
case "enable":
|
||||
await handleEnable(interaction);
|
||||
break;
|
||||
case "disable":
|
||||
await handleDisable(interaction);
|
||||
break;
|
||||
case "grant":
|
||||
await handleGrant(interaction);
|
||||
break;
|
||||
case "revoke":
|
||||
await handleRevoke(interaction);
|
||||
break;
|
||||
case "access":
|
||||
await handleAccess(interaction);
|
||||
break;
|
||||
}
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -177,44 +173,44 @@ async function handleCreate(interaction: ChatInputCommandInteraction) {
|
||||
const description = interaction.options.getString("description");
|
||||
|
||||
const flag = await featureFlagsService.createFlag(name, description ?? undefined);
|
||||
|
||||
|
||||
if (!flag) {
|
||||
await interaction.editReply({ embeds: [createErrorEmbed("Failed to create feature flag.")] });
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" created successfully. Use \`/featureflags enable\` to enable it.`)]
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" created successfully. Use \`/featureflags enable\` to enable it.`)]
|
||||
});
|
||||
}
|
||||
|
||||
async function handleDelete(interaction: ChatInputCommandInteraction) {
|
||||
const name = interaction.options.getString("name", true);
|
||||
|
||||
|
||||
const flag = await featureFlagsService.deleteFlag(name);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" has been deleted.`)]
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" has been deleted.`)]
|
||||
});
|
||||
}
|
||||
|
||||
async function handleEnable(interaction: ChatInputCommandInteraction) {
|
||||
const name = interaction.options.getString("name", true);
|
||||
|
||||
|
||||
const flag = await featureFlagsService.setFlagEnabled(name, true);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" has been enabled.`)]
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" has been enabled.`)]
|
||||
});
|
||||
}
|
||||
|
||||
async function handleDisable(interaction: ChatInputCommandInteraction) {
|
||||
const name = interaction.options.getString("name", true);
|
||||
|
||||
|
||||
const flag = await featureFlagsService.setFlagEnabled(name, false);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" has been disabled.`)]
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" has been disabled.`)]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -224,8 +220,8 @@ async function handleGrant(interaction: ChatInputCommandInteraction) {
|
||||
const role = interaction.options.getRole("role");
|
||||
|
||||
if (!user && !role) {
|
||||
await interaction.editReply({
|
||||
embeds: [createErrorEmbed("You must specify either a user or a role to grant access to.")]
|
||||
await interaction.editReply({
|
||||
embeds: [createErrorEmbed("You must specify either a user or a role to grant access to.")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -250,29 +246,29 @@ async function handleGrant(interaction: ChatInputCommandInteraction) {
|
||||
target = "Unknown";
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Access to "**${name}**" granted to ${target} (ID: ${access.id})`)]
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Access to "**${name}**" granted to ${target} (ID: ${access.id})`)]
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRevoke(interaction: ChatInputCommandInteraction) {
|
||||
const id = interaction.options.getInteger("id", true);
|
||||
|
||||
|
||||
const access = await featureFlagsService.revokeAccess(id);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Access record #${access.id} has been revoked.`)]
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [createSuccessEmbed(`Access record #${access.id} has been revoked.`)]
|
||||
});
|
||||
}
|
||||
|
||||
async function handleAccess(interaction: ChatInputCommandInteraction) {
|
||||
const name = interaction.options.getString("name", true);
|
||||
|
||||
|
||||
const accessRecords = await featureFlagsService.listAccess(name);
|
||||
|
||||
if (accessRecords.length === 0) {
|
||||
await interaction.editReply({
|
||||
embeds: [createBaseEmbed("Feature Flag Access", `No access records for "**${name}**".`, Colors.Blue)]
|
||||
await interaction.editReply({
|
||||
embeds: [createBaseEmbed("Feature Flag Access", `No access records for "**${name}**".`, Colors.Blue)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
} from "discord.js";
|
||||
import { inventoryService } from "@shared/modules/inventory/inventory.service";
|
||||
import { createErrorEmbed } from "@lib/embeds";
|
||||
import { UserError } from "@shared/lib/errors";
|
||||
import { items } from "@db/schema";
|
||||
import { ilike, isNotNull, and, inArray } from "drizzle-orm";
|
||||
import { DrizzleClient } from "@shared/db/DrizzleClient";
|
||||
import { getShopListingMessage } from "@/modules/economy/shop.view";
|
||||
import { EffectType, LootType } from "@shared/lib/constants";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const listing = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -31,72 +31,67 @@ export const listing = createCommand({
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const itemId = interaction.options.getNumber("item", true);
|
||||
const targetChannel = (interaction.options.getChannel("channel") as BaseGuildTextChannel) || interaction.channel as BaseGuildTextChannel;
|
||||
|
||||
const itemId = interaction.options.getNumber("item", true);
|
||||
const targetChannel = (interaction.options.getChannel("channel") as BaseGuildTextChannel) || interaction.channel as BaseGuildTextChannel;
|
||||
|
||||
if (!targetChannel || !targetChannel.isSendable()) {
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed("Target channel is invalid or not sendable.")] });
|
||||
return;
|
||||
}
|
||||
|
||||
const item = await inventoryService.getItem(itemId);
|
||||
if (!item) {
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed(`Item with ID ${itemId} not found.`)] });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.price) {
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed(`Item "${item.name}" is not for sale (no price set).`)] });
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare context for lootboxes
|
||||
const context: { referencedItems: Map<number, { name: string; rarity: string }> } = { referencedItems: new Map() };
|
||||
|
||||
const usageData = item.usageData as any;
|
||||
const lootboxEffect = usageData?.effects?.find((e: any) => e.type === EffectType.LOOTBOX);
|
||||
|
||||
if (lootboxEffect && lootboxEffect.pool) {
|
||||
const itemIds = lootboxEffect.pool
|
||||
.filter((drop: any) => drop.type === LootType.ITEM && drop.itemId)
|
||||
.map((drop: any) => drop.itemId);
|
||||
|
||||
if (itemIds.length > 0) {
|
||||
// Remove duplicates
|
||||
const uniqueIds = [...new Set(itemIds)] as number[];
|
||||
|
||||
const referencedItems = await DrizzleClient.select({
|
||||
id: items.id,
|
||||
name: items.name,
|
||||
rarity: items.rarity
|
||||
}).from(items).where(inArray(items.id, uniqueIds));
|
||||
|
||||
for (const ref of referencedItems) {
|
||||
context.referencedItems.set(ref.id, { name: ref.name, rarity: ref.rarity || 'C' });
|
||||
if (!targetChannel || !targetChannel.isSendable()) {
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed("Target channel is invalid or not sendable.")] });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const listingMessage = getShopListingMessage({
|
||||
...item,
|
||||
rarity: item.rarity || undefined,
|
||||
formattedPrice: `${item.price} 🪙`,
|
||||
price: item.price
|
||||
}, context);
|
||||
const item = await inventoryService.getItem(itemId);
|
||||
if (!item) {
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed(`Item with ID ${itemId} not found.`)] });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await targetChannel.send(listingMessage as any);
|
||||
await interaction.editReply({ content: `✅ Listing for **${item.name}** posted in ${targetChannel}.` });
|
||||
} catch (error: any) {
|
||||
if (error instanceof UserError) {
|
||||
await interaction.editReply({ embeds: [createErrorEmbed(error.message)] });
|
||||
} else {
|
||||
console.error("Error creating listing:", error);
|
||||
await interaction.editReply({ embeds: [createErrorEmbed("An unexpected error occurred.")] });
|
||||
}
|
||||
}
|
||||
if (!item.price) {
|
||||
await interaction.editReply({ content: "", embeds: [createErrorEmbed(`Item "${item.name}" is not for sale (no price set).`)] });
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare context for lootboxes
|
||||
const context: { referencedItems: Map<number, { name: string; rarity: string }> } = { referencedItems: new Map() };
|
||||
|
||||
const usageData = item.usageData as any;
|
||||
const lootboxEffect = usageData?.effects?.find((e: any) => e.type === EffectType.LOOTBOX);
|
||||
|
||||
if (lootboxEffect && lootboxEffect.pool) {
|
||||
const itemIds = lootboxEffect.pool
|
||||
.filter((drop: any) => drop.type === LootType.ITEM && drop.itemId)
|
||||
.map((drop: any) => drop.itemId);
|
||||
|
||||
if (itemIds.length > 0) {
|
||||
// Remove duplicates
|
||||
const uniqueIds = [...new Set(itemIds)] as number[];
|
||||
|
||||
const referencedItems = await DrizzleClient.select({
|
||||
id: items.id,
|
||||
name: items.name,
|
||||
rarity: items.rarity
|
||||
}).from(items).where(inArray(items.id, uniqueIds));
|
||||
|
||||
for (const ref of referencedItems) {
|
||||
context.referencedItems.set(ref.id, { name: ref.name, rarity: ref.rarity || 'C' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const listingMessage = getShopListingMessage({
|
||||
...item,
|
||||
rarity: item.rarity || undefined,
|
||||
formattedPrice: `${item.price} 🪙`,
|
||||
price: item.price
|
||||
}, context);
|
||||
|
||||
await targetChannel.send(listingMessage as any);
|
||||
await interaction.editReply({ content: `✅ Listing for **${item.name}** posted in ${targetChannel}.` });
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
},
|
||||
autocomplete: async (interaction) => {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createCommand } from "@shared/lib/utils";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
||||
import { moderationService } from "@shared/modules/moderation/moderation.service";
|
||||
import { CaseType } from "@shared/lib/constants";
|
||||
import { getNoteSuccessEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const note = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -24,39 +25,35 @@ export const note = createCommand({
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const targetUser = interaction.options.getUser("user", true);
|
||||
const noteText = interaction.options.getString("note", true);
|
||||
|
||||
try {
|
||||
const targetUser = interaction.options.getUser("user", true);
|
||||
const noteText = interaction.options.getString("note", true);
|
||||
|
||||
// Create the note case
|
||||
const moderationCase = await moderationService.createCase({
|
||||
type: CaseType.NOTE,
|
||||
userId: targetUser.id,
|
||||
username: targetUser.username,
|
||||
moderatorId: interaction.user.id,
|
||||
moderatorName: interaction.user.username,
|
||||
reason: noteText,
|
||||
});
|
||||
|
||||
if (!moderationCase) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("Failed to create note.")]
|
||||
// Create the note case
|
||||
const moderationCase = await moderationService.createCase({
|
||||
type: CaseType.NOTE,
|
||||
userId: targetUser.id,
|
||||
username: targetUser.username,
|
||||
moderatorId: interaction.user.id,
|
||||
moderatorName: interaction.user.username,
|
||||
reason: noteText,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Send success message
|
||||
await interaction.editReply({
|
||||
embeds: [getNoteSuccessEmbed(moderationCase.caseId, targetUser.username)]
|
||||
});
|
||||
if (!moderationCase) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("Failed to create note.")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Note command error:", error);
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("An error occurred while adding the note.")]
|
||||
});
|
||||
}
|
||||
// Send success message
|
||||
await interaction.editReply({
|
||||
embeds: [getNoteSuccessEmbed(moderationCase.caseId, targetUser.username)]
|
||||
});
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createCommand } from "@shared/lib/utils";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
||||
import { moderationService } from "@shared/modules/moderation/moderation.service";
|
||||
import { getCasesListEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
|
||||
import { getCasesListEmbed } from "@/modules/moderation/moderation.view";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const notes = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -16,28 +17,24 @@ export const notes = createCommand({
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const targetUser = interaction.options.getUser("user", true);
|
||||
|
||||
try {
|
||||
const targetUser = interaction.options.getUser("user", true);
|
||||
// Get all notes for the user
|
||||
const userNotes = await moderationService.getUserNotes(targetUser.id);
|
||||
|
||||
// Get all notes for the user
|
||||
const userNotes = await moderationService.getUserNotes(targetUser.id);
|
||||
|
||||
// Display the notes
|
||||
await interaction.editReply({
|
||||
embeds: [getCasesListEmbed(
|
||||
userNotes,
|
||||
`📝 Staff Notes for ${targetUser.username}`,
|
||||
userNotes.length === 0 ? undefined : `Total notes: **${userNotes.length}**`
|
||||
)]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Notes command error:", error);
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("An error occurred while fetching notes.")]
|
||||
});
|
||||
}
|
||||
// Display the notes
|
||||
await interaction.editReply({
|
||||
embeds: [getCasesListEmbed(
|
||||
userNotes,
|
||||
`📝 Staff Notes for ${targetUser.username}`,
|
||||
userNotes.length === 0 ? undefined : `Total notes: **${userNotes.length}**`
|
||||
)]
|
||||
});
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
getPruneWarningEmbed,
|
||||
getCancelledEmbed
|
||||
} from "@/modules/moderation/prune.view";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const prune = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -38,142 +39,126 @@ export const prune = createCommand({
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const amount = interaction.options.getInteger("amount");
|
||||
const user = interaction.options.getUser("user");
|
||||
const all = interaction.options.getBoolean("all") || false;
|
||||
|
||||
try {
|
||||
const amount = interaction.options.getInteger("amount");
|
||||
const user = interaction.options.getUser("user");
|
||||
const all = interaction.options.getBoolean("all") || false;
|
||||
|
||||
// Validate inputs
|
||||
if (!amount && !all) {
|
||||
// Default to 10 messages
|
||||
} else if (amount && all) {
|
||||
await interaction.editReply({
|
||||
embeds: [getPruneErrorEmbed("Cannot specify both `amount` and `all`. Please use one or the other.")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const finalAmount = all ? 'all' : (amount || 10);
|
||||
const confirmThreshold = config.moderation.prune.confirmThreshold;
|
||||
|
||||
// Check if confirmation is needed
|
||||
const needsConfirmation = all || (typeof finalAmount === 'number' && finalAmount > confirmThreshold);
|
||||
|
||||
if (needsConfirmation) {
|
||||
// Estimate message count for confirmation
|
||||
let estimatedCount: number | undefined;
|
||||
if (all) {
|
||||
try {
|
||||
estimatedCount = await pruneService.estimateMessageCount(interaction.channel!);
|
||||
} catch {
|
||||
estimatedCount = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const { embeds, components } = getConfirmationMessage(finalAmount, estimatedCount);
|
||||
const response = await interaction.editReply({ embeds, components });
|
||||
|
||||
try {
|
||||
const confirmation = await response.awaitMessageComponent({
|
||||
filter: (i) => i.user.id === interaction.user.id,
|
||||
componentType: ComponentType.Button,
|
||||
time: 30000
|
||||
});
|
||||
|
||||
if (confirmation.customId === "cancel_prune") {
|
||||
await confirmation.update({
|
||||
embeds: [getCancelledEmbed()],
|
||||
components: []
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// User confirmed, proceed with deletion
|
||||
await confirmation.update({
|
||||
embeds: [getProgressEmbed({ current: 0, total: estimatedCount || finalAmount as number })],
|
||||
components: []
|
||||
});
|
||||
|
||||
// Execute deletion with progress callback for 'all' mode
|
||||
const result = await pruneService.deleteMessages(
|
||||
interaction.channel!,
|
||||
{
|
||||
amount: typeof finalAmount === 'number' ? finalAmount : undefined,
|
||||
userId: user?.id,
|
||||
all
|
||||
},
|
||||
all ? async (progress) => {
|
||||
await interaction.editReply({
|
||||
embeds: [getProgressEmbed(progress)]
|
||||
});
|
||||
} : undefined
|
||||
);
|
||||
|
||||
// Show success
|
||||
// Validate inputs
|
||||
if (!amount && !all) {
|
||||
// Default to 10 messages
|
||||
} else if (amount && all) {
|
||||
await interaction.editReply({
|
||||
embeds: [getSuccessEmbed(result)],
|
||||
components: []
|
||||
embeds: [getPruneErrorEmbed("Cannot specify both `amount` and `all`. Please use one or the other.")]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes("time")) {
|
||||
await interaction.editReply({
|
||||
embeds: [getPruneWarningEmbed("Confirmation timed out. Please try again.")],
|
||||
components: []
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No confirmation needed, proceed directly
|
||||
const result = await pruneService.deleteMessages(
|
||||
interaction.channel!,
|
||||
{
|
||||
amount: finalAmount as number,
|
||||
userId: user?.id,
|
||||
all: false
|
||||
}
|
||||
);
|
||||
|
||||
// Check if no messages were found
|
||||
if (result.deletedCount === 0) {
|
||||
if (user) {
|
||||
await interaction.editReply({
|
||||
embeds: [getPruneWarningEmbed(`No messages found from **${user.username}** in the last ${finalAmount} messages.`)]
|
||||
});
|
||||
} else {
|
||||
await interaction.editReply({
|
||||
embeds: [getPruneWarningEmbed("No messages found to delete.")]
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [getSuccessEmbed(result)]
|
||||
});
|
||||
}
|
||||
const finalAmount = all ? 'all' : (amount || 10);
|
||||
const confirmThreshold = config.moderation.prune.confirmThreshold;
|
||||
|
||||
} catch (error) {
|
||||
console.error("Prune command error:", error);
|
||||
// Check if confirmation is needed
|
||||
const needsConfirmation = all || (typeof finalAmount === 'number' && finalAmount > confirmThreshold);
|
||||
|
||||
let errorMessage = "An unexpected error occurred while trying to delete messages.";
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes("permission")) {
|
||||
errorMessage = "I don't have permission to delete messages in this channel.";
|
||||
} else if (error.message.includes("channel type")) {
|
||||
errorMessage = "This command cannot be used in this type of channel.";
|
||||
if (needsConfirmation) {
|
||||
// Estimate message count for confirmation
|
||||
let estimatedCount: number | undefined;
|
||||
if (all) {
|
||||
try {
|
||||
estimatedCount = await pruneService.estimateMessageCount(interaction.channel!);
|
||||
} catch {
|
||||
estimatedCount = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const { embeds, components } = getConfirmationMessage(finalAmount, estimatedCount);
|
||||
const response = await interaction.editReply({ embeds, components });
|
||||
|
||||
try {
|
||||
const confirmation = await response.awaitMessageComponent({
|
||||
filter: (i) => i.user.id === interaction.user.id,
|
||||
componentType: ComponentType.Button,
|
||||
time: 30000
|
||||
});
|
||||
|
||||
if (confirmation.customId === "cancel_prune") {
|
||||
await confirmation.update({
|
||||
embeds: [getCancelledEmbed()],
|
||||
components: []
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// User confirmed, proceed with deletion
|
||||
await confirmation.update({
|
||||
embeds: [getProgressEmbed({ current: 0, total: estimatedCount || finalAmount as number })],
|
||||
components: []
|
||||
});
|
||||
|
||||
// Execute deletion with progress callback for 'all' mode
|
||||
const result = await pruneService.deleteMessages(
|
||||
interaction.channel!,
|
||||
{
|
||||
amount: typeof finalAmount === 'number' ? finalAmount : undefined,
|
||||
userId: user?.id,
|
||||
all
|
||||
},
|
||||
all ? async (progress) => {
|
||||
await interaction.editReply({
|
||||
embeds: [getProgressEmbed(progress)]
|
||||
});
|
||||
} : undefined
|
||||
);
|
||||
|
||||
// Show success
|
||||
await interaction.editReply({
|
||||
embeds: [getSuccessEmbed(result)],
|
||||
components: []
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes("time")) {
|
||||
await interaction.editReply({
|
||||
embeds: [getPruneWarningEmbed("Confirmation timed out. Please try again.")],
|
||||
components: []
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
}
|
||||
// No confirmation needed, proceed directly
|
||||
const result = await pruneService.deleteMessages(
|
||||
interaction.channel!,
|
||||
{
|
||||
amount: finalAmount as number,
|
||||
userId: user?.id,
|
||||
all: false
|
||||
}
|
||||
);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [getPruneErrorEmbed(errorMessage)]
|
||||
});
|
||||
}
|
||||
// Check if no messages were found
|
||||
if (result.deletedCount === 0) {
|
||||
if (user) {
|
||||
await interaction.editReply({
|
||||
embeds: [getPruneWarningEmbed(`No messages found from **${user.username}** in the last ${finalAmount} messages.`)]
|
||||
});
|
||||
} else {
|
||||
await interaction.editReply({
|
||||
embeds: [getPruneWarningEmbed("No messages found to delete.")]
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [getSuccessEmbed(result)]
|
||||
});
|
||||
}
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createCommand } from "@shared/lib/utils";
|
||||
import { AuroraClient } from "@/lib/BotClient";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
|
||||
import { createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
||||
import { createSuccessEmbed } from "@lib/embeds";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const refresh = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -9,25 +10,24 @@ export const refresh = createCommand({
|
||||
.setDescription("Reloads all commands and config without restarting")
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const start = Date.now();
|
||||
await AuroraClient.loadCommands(true);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
try {
|
||||
const start = Date.now();
|
||||
await AuroraClient.loadCommands(true);
|
||||
const duration = Date.now() - start;
|
||||
// Deploy commands
|
||||
await AuroraClient.deployCommands();
|
||||
|
||||
// Deploy commands
|
||||
await AuroraClient.deployCommands();
|
||||
const embed = createSuccessEmbed(
|
||||
`Successfully reloaded ${AuroraClient.commands.size} commands in ${duration}ms.`,
|
||||
"System Refreshed"
|
||||
);
|
||||
|
||||
const embed = createSuccessEmbed(
|
||||
`Successfully reloaded ${AuroraClient.commands.size} commands in ${duration}ms.`,
|
||||
"System Refreshed"
|
||||
);
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
await interaction.editReply({ embeds: [createErrorEmbed("An error occurred while refreshing commands. Check console for details.", "Refresh Failed")] });
|
||||
}
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { SlashCommandBuilder, PermissionFlagsBits, Colors, ChatInputCommandInter
|
||||
import { guildSettingsService } from "@shared/modules/guild-settings/guild-settings.service";
|
||||
import { createBaseEmbed, createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
|
||||
import { getGuildConfig, invalidateGuildConfigCache } from "@shared/lib/config";
|
||||
import { UserError } from "@shared/lib/errors";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const settings = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -84,33 +84,29 @@ export const settings = createCommand({
|
||||
.setRequired(false))),
|
||||
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
const guildId = interaction.guildId!;
|
||||
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
const guildId = interaction.guildId!;
|
||||
|
||||
try {
|
||||
switch (subcommand) {
|
||||
case "show":
|
||||
await handleShow(interaction, guildId);
|
||||
break;
|
||||
case "set":
|
||||
await handleSet(interaction, guildId);
|
||||
break;
|
||||
case "reset":
|
||||
await handleReset(interaction, guildId);
|
||||
break;
|
||||
case "colors":
|
||||
await handleColors(interaction, guildId);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof UserError) {
|
||||
await interaction.editReply({ embeds: [createErrorEmbed(error.message)] });
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
switch (subcommand) {
|
||||
case "show":
|
||||
await handleShow(interaction, guildId);
|
||||
break;
|
||||
case "set":
|
||||
await handleSet(interaction, guildId);
|
||||
break;
|
||||
case "reset":
|
||||
await handleReset(interaction, guildId);
|
||||
break;
|
||||
case "colors":
|
||||
await handleColors(interaction, guildId);
|
||||
break;
|
||||
}
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
import { createCommand } from "@shared/lib/utils";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, TextChannel } from "discord.js";
|
||||
import { terminalService } from "@shared/modules/terminal/terminal.service";
|
||||
import { createBaseEmbed, createErrorEmbed } from "@/lib/embeds";
|
||||
import { createErrorEmbed } from "@/lib/embeds";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const terminal = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -23,15 +24,14 @@ export const terminal = createCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.reply({ ephemeral: true, content: "Initializing terminal..." });
|
||||
|
||||
try {
|
||||
await terminalService.init(channel as TextChannel);
|
||||
await interaction.editReply({ content: "✅ Terminal initialized!" });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
await interaction.editReply({ content: "❌ Failed to initialize terminal." });
|
||||
}
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
await terminalService.init(channel as TextChannel);
|
||||
await interaction.editReply({ content: "✅ Terminal initialized!" });
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,9 +4,9 @@ import { moderationService } from "@shared/modules/moderation/moderation.service
|
||||
import {
|
||||
getWarnSuccessEmbed,
|
||||
getModerationErrorEmbed,
|
||||
getUserWarningEmbed
|
||||
} from "@/modules/moderation/moderation.view";
|
||||
import { getGuildConfig } from "@shared/lib/config";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const warn = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -28,67 +28,63 @@ export const warn = createCommand({
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const targetUser = interaction.options.getUser("user", true);
|
||||
const reason = interaction.options.getString("reason", true);
|
||||
|
||||
try {
|
||||
const targetUser = interaction.options.getUser("user", true);
|
||||
const reason = interaction.options.getString("reason", true);
|
||||
// Don't allow warning bots
|
||||
if (targetUser.bot) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("You cannot warn bots.")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't allow warning bots
|
||||
if (targetUser.bot) {
|
||||
// Don't allow self-warnings
|
||||
if (targetUser.id === interaction.user.id) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("You cannot warn yourself.")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch guild config for moderation settings
|
||||
const guildConfig = await getGuildConfig(interaction.guildId!);
|
||||
|
||||
// Issue the warning via service
|
||||
const { moderationCase, warningCount, autoTimeoutIssued } = await moderationService.issueWarning({
|
||||
userId: targetUser.id,
|
||||
username: targetUser.username,
|
||||
moderatorId: interaction.user.id,
|
||||
moderatorName: interaction.user.username,
|
||||
reason,
|
||||
guildName: interaction.guild?.name || undefined,
|
||||
dmTarget: targetUser,
|
||||
timeoutTarget: await interaction.guild?.members.fetch(targetUser.id),
|
||||
config: {
|
||||
dmOnWarn: guildConfig.moderation?.cases?.dmOnWarn,
|
||||
autoTimeoutThreshold: guildConfig.moderation?.cases?.autoTimeoutThreshold,
|
||||
},
|
||||
});
|
||||
|
||||
// Send success message to moderator
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("You cannot warn bots.")]
|
||||
embeds: [getWarnSuccessEmbed(moderationCase.caseId, targetUser.username, reason)]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't allow self-warnings
|
||||
if (targetUser.id === interaction.user.id) {
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("You cannot warn yourself.")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch guild config for moderation settings
|
||||
const guildConfig = await getGuildConfig(interaction.guildId!);
|
||||
|
||||
// Issue the warning via service
|
||||
const { moderationCase, warningCount, autoTimeoutIssued } = await moderationService.issueWarning({
|
||||
userId: targetUser.id,
|
||||
username: targetUser.username,
|
||||
moderatorId: interaction.user.id,
|
||||
moderatorName: interaction.user.username,
|
||||
reason,
|
||||
guildName: interaction.guild?.name || undefined,
|
||||
dmTarget: targetUser,
|
||||
timeoutTarget: await interaction.guild?.members.fetch(targetUser.id),
|
||||
config: {
|
||||
dmOnWarn: guildConfig.moderation?.cases?.dmOnWarn,
|
||||
autoTimeoutThreshold: guildConfig.moderation?.cases?.autoTimeoutThreshold,
|
||||
},
|
||||
});
|
||||
|
||||
// Send success message to moderator
|
||||
await interaction.editReply({
|
||||
embeds: [getWarnSuccessEmbed(moderationCase.caseId, targetUser.username, reason)]
|
||||
});
|
||||
|
||||
// Follow up if auto-timeout was issued
|
||||
if (autoTimeoutIssued) {
|
||||
await interaction.followUp({
|
||||
embeds: [getModerationErrorEmbed(
|
||||
`⚠️ User has reached ${warningCount} warnings and has been automatically timed out for 24 hours.`
|
||||
)],
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Warn command error:", error);
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("An error occurred while issuing the warning.")]
|
||||
});
|
||||
}
|
||||
// Follow up if auto-timeout was issued
|
||||
if (autoTimeoutIssued) {
|
||||
await interaction.followUp({
|
||||
embeds: [getModerationErrorEmbed(
|
||||
`⚠️ User has reached ${warningCount} warnings and has been automatically timed out for 24 hours.`
|
||||
)],
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createCommand } from "@shared/lib/utils";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
||||
import { moderationService } from "@shared/modules/moderation/moderation.service";
|
||||
import { getWarningsEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
|
||||
import { getWarningsEmbed } from "@/modules/moderation/moderation.view";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const warnings = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -16,24 +17,20 @@ export const warnings = createCommand({
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const targetUser = interaction.options.getUser("user", true);
|
||||
|
||||
try {
|
||||
const targetUser = interaction.options.getUser("user", true);
|
||||
// Get active warnings for the user
|
||||
const activeWarnings = await moderationService.getUserWarnings(targetUser.id);
|
||||
|
||||
// Get active warnings for the user
|
||||
const activeWarnings = await moderationService.getUserWarnings(targetUser.id);
|
||||
|
||||
// Display the warnings
|
||||
await interaction.editReply({
|
||||
embeds: [getWarningsEmbed(activeWarnings, targetUser.username)]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Warnings command error:", error);
|
||||
await interaction.editReply({
|
||||
embeds: [getModerationErrorEmbed("An error occurred while fetching warnings.")]
|
||||
});
|
||||
}
|
||||
// Display the warnings
|
||||
await interaction.editReply({
|
||||
embeds: [getWarningsEmbed(activeWarnings, targetUser.username)]
|
||||
});
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createCommand } from "@shared/lib/utils";
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
|
||||
import { createErrorEmbed } from "@/lib/embeds";
|
||||
import { sendWebhookMessage } from "@/lib/webhookUtils";
|
||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||
|
||||
export const webhook = createCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
@@ -14,43 +15,40 @@ export const webhook = createCommand({
|
||||
.setRequired(true)
|
||||
),
|
||||
execute: async (interaction) => {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await withCommandErrorHandling(
|
||||
interaction,
|
||||
async () => {
|
||||
const payloadString = interaction.options.getString("payload", true);
|
||||
let payload;
|
||||
|
||||
const payloadString = interaction.options.getString("payload", true);
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(payloadString);
|
||||
} catch (error) {
|
||||
await interaction.editReply({
|
||||
embeds: [createErrorEmbed("The provided payload is not valid JSON.", "Invalid JSON")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
payload = JSON.parse(payloadString);
|
||||
} catch (error) {
|
||||
await interaction.editReply({
|
||||
embeds: [createErrorEmbed("The provided payload is not valid JSON.", "Invalid JSON")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
const channel = interaction.channel;
|
||||
|
||||
const channel = interaction.channel;
|
||||
if (!channel || !('createWebhook' in channel)) {
|
||||
await interaction.editReply({
|
||||
embeds: [createErrorEmbed("This channel does not support webhooks.", "Unsupported Channel")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channel || !('createWebhook' in channel)) {
|
||||
await interaction.editReply({
|
||||
embeds: [createErrorEmbed("This channel does not support webhooks.", "Unsupported Channel")]
|
||||
});
|
||||
return;
|
||||
}
|
||||
await sendWebhookMessage(
|
||||
channel,
|
||||
payload,
|
||||
interaction.client.user,
|
||||
`Proxy message requested by ${interaction.user.tag}`
|
||||
);
|
||||
|
||||
try {
|
||||
await sendWebhookMessage(
|
||||
channel,
|
||||
payload,
|
||||
interaction.client.user,
|
||||
`Proxy message requested by ${interaction.user.tag}`
|
||||
);
|
||||
|
||||
await interaction.editReply({ content: "Message sent successfully!" });
|
||||
} catch (error) {
|
||||
console.error("Webhook error:", error);
|
||||
await interaction.editReply({
|
||||
embeds: [createErrorEmbed("Failed to send message via webhook. Ensure the bot has 'Manage Webhooks' permission and the payload is valid.", "Delivery Failed")]
|
||||
});
|
||||
}
|
||||
await interaction.editReply({ content: "Message sent successfully!" });
|
||||
},
|
||||
{ ephemeral: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user