feat: implement permission checks for ManageGuild and staff roles across commands
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."Guild" ADD COLUMN "staff" JSONB;
|
||||
@@ -22,6 +22,7 @@ model Guild {
|
||||
id String @id
|
||||
name String
|
||||
prefix String @default("!")
|
||||
staff Json?
|
||||
|
||||
// Relaciones
|
||||
alliances Alliance[]
|
||||
|
||||
@@ -12,6 +12,7 @@ import {listVariables} from "../../../core/lib/vars";
|
||||
import type Amayo from "../../../core/client";
|
||||
import {BlockState, DisplayComponentUtils, EditorActionRow} from "../../../core/types/displayComponentEditor";
|
||||
import type {DisplayComponentContainer} from "../../../core/types/displayComponents";
|
||||
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||
|
||||
interface EditorData {
|
||||
content?: string;
|
||||
@@ -51,8 +52,9 @@ export const command: CommandMessage = {
|
||||
category: "Alianzas",
|
||||
usage: "crear-embed <nombre>",
|
||||
run: async (message, args, client) => {
|
||||
if (!message.member?.permissions.has("Administrator")) {
|
||||
await message.reply("❌ No tienes permisos de Administrador.");
|
||||
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma);
|
||||
if (!allowed) {
|
||||
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -719,7 +721,7 @@ async function handleSaveBlock(
|
||||
});
|
||||
|
||||
await interaction.reply({
|
||||
content: `✅ **Bloque guardado exitosamente!**\n\n📄 **Nombre:** \`${blockName}\`\n🎨 **Componentes:** ${blockState.components.length}\n\n🎯 **Uso:** \`!send ${blockName}\``,
|
||||
content: `✅ **Bloque guardado exitosamente!**\n\n📄 **Nombre:** \`${blockName}\`\n🎨 **Componentes:** ${blockState.components.length}\n\n🎯 **Uso:** \`!send-embed ${blockName}\``,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { CommandMessage } from "../../../core/types/commands";
|
||||
import type Amayo from "../../../core/client";
|
||||
import type { JsonValue } from "@prisma/client/runtime/library";
|
||||
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||
|
||||
interface BlockItem {
|
||||
name: string;
|
||||
@@ -22,16 +23,17 @@ interface ActionRowBuilder {
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: "eliminar-embed",
|
||||
name: "eliminar-bloque",
|
||||
type: "message",
|
||||
aliases: ["embed-eliminar", "borrar-embed", "embeddelete"],
|
||||
aliases: ["bloque-eliminar", "bloque-embed", "blockdelete"],
|
||||
cooldown: 10,
|
||||
description: "Elimina bloques DisplayComponents del servidor",
|
||||
category: "Alianzas",
|
||||
usage: "eliminar-embed [nombre_bloque]",
|
||||
category: "Creacion",
|
||||
usage: "eliminar-bloque [nombre_bloque]",
|
||||
run: async (message: Message, args: string[], client: Amayo): Promise<void> => {
|
||||
if (!message.member?.permissions.has("Administrator")) {
|
||||
await message.reply("❌ No tienes permisos de Administrador.");
|
||||
const allowed = await hasManageGuildOrStaff(message.member, message.guildId!, client.prisma);
|
||||
if (!allowed) {
|
||||
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
} from "../../../core/types/displayComponents";
|
||||
import type Amayo from "../../../core/client";
|
||||
import type { JsonValue } from "@prisma/client/runtime/library";
|
||||
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||
|
||||
interface BlockListItem {
|
||||
name: string;
|
||||
@@ -29,17 +30,17 @@ interface ActionRowBuilder {
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: "lista-embeds",
|
||||
name: "lista-bloques",
|
||||
type: "message",
|
||||
aliases: ["embeds", "ver-embeds", "embedlist"],
|
||||
aliases: ["bloques", "ver-bloques", "blocks"],
|
||||
cooldown: 10,
|
||||
description: "Muestra todos los bloques DisplayComponents configurados en el servidor",
|
||||
category: "Alianzas",
|
||||
usage: "lista-embeds",
|
||||
usage: "lista-bloques",
|
||||
run: async (message: Message, args: string[], client: Amayo): Promise<void> => {
|
||||
// Permission check
|
||||
if (!message.member?.permissions.has("Administrator")) {
|
||||
await message.reply("❌ No tienes permisos de Administrador.");
|
||||
const allowed = await hasManageGuildOrStaff(message.member, message.guildId!, client.prisma);
|
||||
if (!allowed) {
|
||||
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { CommandMessage } from "../../../core/types/commands";
|
||||
import { MessageFlags } from "discord.js";
|
||||
import { ComponentType, ButtonStyle, TextInputStyle } from "discord-api-types/v10";
|
||||
import { replaceVars, isValidUrlOrVariable, listVariables } from "../../../core/lib/vars";
|
||||
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||
|
||||
// Botones de edición (máx 5 por fila)
|
||||
const btns = (disabled = false) => ([
|
||||
@@ -134,8 +135,9 @@ export const command: CommandMessage = {
|
||||
category: "Alianzas",
|
||||
usage: "editar-embed <nombre>",
|
||||
run: async (message, args, client) => {
|
||||
if (!message.member?.permissions.has("Administrator")) {
|
||||
await message.reply("❌ No tienes permisos de Administrador.");
|
||||
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma);
|
||||
if (!allowed) {
|
||||
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,7 +151,7 @@ export const command: CommandMessage = {
|
||||
where: { guildId: message.guild!.id, name: blockName }
|
||||
});
|
||||
if (!existingBlock) {
|
||||
await message.reply("❌ Block no encontrado. Usa `!blockcreatev2 <nombre>` para crear uno nuevo.");
|
||||
await message.reply("❌ Block no encontrado. Usa `!editar-bloque <nombre>` para crear uno nuevo.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Comando para mostrar el leaderboard de alianzas con botón de refresco
|
||||
// @ts-ignore
|
||||
import { CommandMessage } from "../../../core/types/commands";
|
||||
import { PermissionFlagsBits } from "discord.js";
|
||||
import { hasManageGuildOrStaff } from '../../../core/lib/permissions';
|
||||
import { prisma } from "../../../core/database/prisma";
|
||||
import type { Message } from "discord.js";
|
||||
import { PermissionFlagsBits } from "discord.js";
|
||||
|
||||
const MAX_ENTRIES = 10;
|
||||
|
||||
@@ -236,7 +237,7 @@ export const command: CommandMessage = {
|
||||
aliases: ['ld'],
|
||||
cooldown: 5,
|
||||
description: 'Muestra el leaderboard de alianzas (semanal, mensual y total) con botón de refresco.',
|
||||
category: 'Utilidad',
|
||||
category: 'Alianzas',
|
||||
usage: 'leaderboard',
|
||||
run: async (message) => {
|
||||
if (!message.guild) {
|
||||
@@ -244,9 +245,9 @@ export const command: CommandMessage = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar si el usuario es administrador
|
||||
// Verificar si el usuario es admin o staff
|
||||
const member = await message.guild.members.fetch(message.author.id);
|
||||
const isAdmin = member.permissions.has(PermissionFlagsBits.ManageGuild);
|
||||
const isAdmin = await hasManageGuildOrStaff(member, message.guild.id, prisma);
|
||||
|
||||
const panel = await buildLeaderboardPanel(message, isAdmin);
|
||||
await message.reply({
|
||||
|
||||
@@ -263,13 +263,13 @@ async function buildChannelListPanel(message: Message) {
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: "listar-canales-alianza-v2",
|
||||
name: "lista-canales",
|
||||
type: "message",
|
||||
aliases: ["listchannels-alliance-v2", "listalchannel-v2", "channelsally-v2", "alliancechannels-v2"],
|
||||
aliases: ["lca", "channelist", "alliacechannels"],
|
||||
cooldown: 5,
|
||||
description: "Lista todos los canales configurados para alianzas (versión V2 con components)",
|
||||
category: "Alianzas",
|
||||
usage: "listar-canales-alianza-v2",
|
||||
usage: "lista-canales",
|
||||
run: async (message) => {
|
||||
if (!message.guild) {
|
||||
await message.reply({ content: '❌ Este comando solo puede usarse en servidores.' });
|
||||
|
||||
@@ -2,16 +2,18 @@ import logger from "../../../core/lib/logger";
|
||||
import { CommandMessage } from "../../../core/types/commands";
|
||||
// @ts-ignore
|
||||
import { EmbedBuilder, ButtonStyle, MessageFlags, ChannelType } from "discord.js";
|
||||
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: "eliminar-canal-alianza",
|
||||
name: "eliminar-canal",
|
||||
type: "message",
|
||||
aliases: ["removechannel-alliance", "removealchannel", "delalchannel"],
|
||||
cooldown: 10,
|
||||
// @ts-ignore
|
||||
run: async (message, args, client) => {
|
||||
if (!message.member?.permissions.has("Administrator")) {
|
||||
return message.reply("❌ No tienes permisos de Administrador.");
|
||||
const allowed = await hasManageGuildOrStaff(message.member, message.guildId!, client.prisma);
|
||||
if (!allowed) {
|
||||
return message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||
}
|
||||
|
||||
// Obtener canales configurados existentes
|
||||
|
||||
@@ -3,6 +3,7 @@ import { MessageFlags } from "discord.js";
|
||||
import { DisplayComponentUtils } from "../../../core/types/displayComponentEditor";
|
||||
import { sendComponentsV2Message } from "../../../core/api/discordAPI";
|
||||
import logger from "../../../core/lib/logger";
|
||||
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: "send-embed",
|
||||
@@ -13,9 +14,9 @@ export const command: CommandMessage = {
|
||||
category: "Alianzas",
|
||||
usage: "send-embed <nombre>",
|
||||
run: async (message, args, client) => {
|
||||
// Requiere administrador para evitar abuso mientras se prueba
|
||||
if (!message.member?.permissions.has("Administrator")) {
|
||||
await message.reply("❌ No tienes permisos de Administrador.");
|
||||
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma);
|
||||
if (!allowed) {
|
||||
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -35,7 +36,6 @@ export const command: CommandMessage = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Renderizamos usando la misma utilidad del editor (fuente: node_modules/discord.js APIs via repo util DisplayComponentUtils)
|
||||
const container = await DisplayComponentUtils.renderPreview(
|
||||
// @ts-ignore - guardamos BlockState como config
|
||||
existingBlock.config,
|
||||
@@ -43,7 +43,6 @@ export const command: CommandMessage = {
|
||||
message.guild!
|
||||
);
|
||||
|
||||
// Enviamos como Components v2 (fuente: repositorio local sendComponentsV2Message)
|
||||
await sendComponentsV2Message(message.channel.id, {
|
||||
components: [container as any],
|
||||
replyToMessageId: message.id
|
||||
@@ -59,4 +58,3 @@ export const command: CommandMessage = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { CommandMessage } from "../../../core/types/commands";
|
||||
// @ts-ignore
|
||||
import { ComponentType, ButtonStyle, MessageFlags, ChannelType } from "discord.js";
|
||||
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: "canal-alianza",
|
||||
type: "message",
|
||||
aliases: ["alchannel", "channelally"],
|
||||
description: "Configura canales para el sistema de alianzas con bloques DisplayComponents.",
|
||||
usage: "canal-alianza",
|
||||
category: "Alianzas",
|
||||
cooldown: 10,
|
||||
// @ts-ignore
|
||||
run: async (message, args, client) => {
|
||||
if (!message.member?.permissions.has("Administrator")) {
|
||||
return message.reply("❌ No tienes permisos de Administrador.");
|
||||
const allowed = await hasManageGuildOrStaff(message.member, message.guildId!, client.prisma);
|
||||
if (!allowed) {
|
||||
return message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||
}
|
||||
|
||||
// Obtener canales configurados existentes
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import logger from "../../../core/lib/logger";
|
||||
import { CommandMessage } from "../../../core/types/commands";
|
||||
import { ComponentType } from "discord-api-types/v10";
|
||||
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||
|
||||
function toStringArray(input: unknown): string[] {
|
||||
if (!Array.isArray(input)) return [];
|
||||
return (input as unknown[]).filter((v): v is string => typeof v === 'string');
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'configuracion',
|
||||
type: "message",
|
||||
aliases: ['config', 'ajustes', 'settings'],
|
||||
cooldown: 5,
|
||||
description: 'Abre el panel de configuración del servidor (prefix y más).',
|
||||
description: 'Abre el panel de configuración del servidor (prefix, staff y más).',
|
||||
category: 'Configuración',
|
||||
usage: 'configuracion',
|
||||
run: async (message, args, client) => {
|
||||
if (!message.member?.permissions.has("Administrator")) {
|
||||
await message.reply("❌ No tienes permisos de Administrador.");
|
||||
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma);
|
||||
if (!allowed) {
|
||||
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,52 +28,48 @@ export const command: CommandMessage = {
|
||||
});
|
||||
|
||||
const currentPrefix = server?.prefix || "!";
|
||||
const staffRoles: string[] = toStringArray(server?.staff);
|
||||
const staffDisplay = staffRoles.length
|
||||
? staffRoles.map((id) => `<@&${id}>`).join(', ')
|
||||
: 'Sin staff configurado';
|
||||
|
||||
// Panel de configuración usando DisplayComponents
|
||||
const settingsPanel = {
|
||||
type: 17,
|
||||
accent_color: 6178018, // Color del ejemplo
|
||||
components: [
|
||||
{ type: 10, content: "### <:invisible:1418684224441028608> 梅,panel admin,📢\n" },
|
||||
{ type: 14, spacing: 1, divider: false },
|
||||
{ type: 10, content: "Configuracion del Servidor:" },
|
||||
{
|
||||
type: 10,
|
||||
content: "### <:invisible:1418684224441028608> 梅,panel admin,📢\n"
|
||||
},
|
||||
{
|
||||
type: 14,
|
||||
spacing: 1,
|
||||
divider: false
|
||||
},
|
||||
{
|
||||
type: 10,
|
||||
content: "Configuracion del Servidor:"
|
||||
},
|
||||
{
|
||||
type: 9, // Section
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: `**Prefix:**<:invisible:1418684224441028608>\`${currentPrefix}\``
|
||||
}
|
||||
],
|
||||
type: 9,
|
||||
components: [ { type: 10, content: `**Prefix:**<:invisible:1418684224441028608>\`${currentPrefix}\`` } ],
|
||||
accessory: {
|
||||
type: 2, // Button
|
||||
style: 2, // Secondary
|
||||
emoji: {
|
||||
name: "⚙️"
|
||||
},
|
||||
type: 2,
|
||||
style: 2,
|
||||
emoji: { name: "⚙️" },
|
||||
custom_id: "open_prefix_modal",
|
||||
label: "Cambiar"
|
||||
}
|
||||
},
|
||||
{ type: 14, divider: false },
|
||||
{
|
||||
type: 14,
|
||||
divider: false
|
||||
type: 9,
|
||||
components: [ { type: 10, content: `**Staff (roles):** ${staffDisplay}` } ],
|
||||
accessory: {
|
||||
type: 2,
|
||||
style: 2, // Secondary
|
||||
emoji: { name: "🛡️" },
|
||||
custom_id: "open_staff_modal",
|
||||
label: "Configurar"
|
||||
}
|
||||
},
|
||||
{ type: 14, divider: false }
|
||||
]
|
||||
};
|
||||
|
||||
const panelMessage = await message.reply({
|
||||
flags: 32768, // SuppressEmbeds
|
||||
flags: 32768, // Components v2
|
||||
components: [settingsPanel]
|
||||
});
|
||||
|
||||
@@ -81,44 +85,15 @@ export const command: CommandMessage = {
|
||||
title: "⚙️ Configurar Prefix del Servidor",
|
||||
custom_id: "prefix_settings_modal",
|
||||
components: [
|
||||
{
|
||||
type: 1, // ActionRow
|
||||
components: [
|
||||
{
|
||||
type: 4, // TextInput
|
||||
custom_id: "new_prefix_input",
|
||||
label: "Nuevo Prefix",
|
||||
style: 1, // Short
|
||||
placeholder: `Prefix actual: ${currentPrefix}`,
|
||||
required: true,
|
||||
max_length: 10,
|
||||
min_length: 1,
|
||||
value: currentPrefix
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 1,
|
||||
components: [
|
||||
{
|
||||
type: 4,
|
||||
custom_id: "prefix_description",
|
||||
label: "¿Por qué cambiar el prefix? (Opcional)",
|
||||
style: 2, // Paragraph
|
||||
placeholder: "Ej: Evitar conflictos con otros bots...",
|
||||
required: false,
|
||||
max_length: 200
|
||||
}
|
||||
]
|
||||
}
|
||||
{ type: 1, components: [ { type: 4, custom_id: "new_prefix_input", label: "Nuevo Prefix", style: 1, placeholder: `Prefix actual: ${currentPrefix}`, required: true, max_length: 10, min_length: 1, value: currentPrefix } ] },
|
||||
{ type: 1, components: [ { type: 4, custom_id: "prefix_description", label: "¿Por qué cambiar el prefix? (Opcional)", style: 2, placeholder: "Ej: Evitar conflictos con otros bots...", required: false, max_length: 200 } ] }
|
||||
]
|
||||
};
|
||||
|
||||
await interaction.showModal(prefixModal);
|
||||
|
||||
// Crear un collector específico para este modal
|
||||
const modalCollector = interaction.awaitModalSubmit({
|
||||
time: 300000, // 5 minutos
|
||||
time: 300000,
|
||||
filter: (modalInt: any) => modalInt.customId === "prefix_settings_modal" && modalInt.user.id === message.author.id
|
||||
});
|
||||
|
||||
@@ -126,226 +101,158 @@ export const command: CommandMessage = {
|
||||
const newPrefix = modalInteraction.fields.getTextInputValue("new_prefix_input");
|
||||
const description = modalInteraction.fields.getTextInputValue("prefix_description") || "Sin descripción";
|
||||
|
||||
// Validar prefix
|
||||
if (!newPrefix || newPrefix.length > 10) {
|
||||
await modalInteraction.reply({
|
||||
content: "❌ **Error:** El prefix debe tener entre 1 y 10 caracteres.",
|
||||
flags: 64 // Ephemeral
|
||||
});
|
||||
await modalInteraction.reply({ content: "❌ **Error:** El prefix debe tener entre 1 y 10 caracteres.", flags: 64 });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Actualizar prefix en la base de datos
|
||||
await client.prisma.guild.upsert({
|
||||
where: { id: message.guild!.id },
|
||||
create: {
|
||||
id: message.guild!.id,
|
||||
name: message.guild!.name,
|
||||
prefix: newPrefix
|
||||
},
|
||||
update: {
|
||||
prefix: newPrefix,
|
||||
name: message.guild!.name
|
||||
}
|
||||
create: { id: message.guild!.id, name: message.guild!.name, prefix: newPrefix },
|
||||
update: { prefix: newPrefix, name: message.guild!.name }
|
||||
});
|
||||
|
||||
// Panel de confirmación
|
||||
const successPanel = {
|
||||
type: 17,
|
||||
accent_color: 3066993, // Verde
|
||||
accent_color: 3066993,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: "### ✅ **Prefix Actualizado Exitosamente**"
|
||||
},
|
||||
{
|
||||
type: 14,
|
||||
spacing: 2,
|
||||
divider: true
|
||||
},
|
||||
{
|
||||
type: 9,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: `**Prefix anterior:** \`${currentPrefix}\`\n**Prefix nuevo:** \`${newPrefix}\`\n\n**Motivo:** ${description}`
|
||||
}
|
||||
],
|
||||
accessory: {
|
||||
type: 2,
|
||||
style: 3, // Success
|
||||
label: "✓ Listo",
|
||||
custom_id: "prefix_confirmed",
|
||||
emoji: { name: "✅" }
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 14,
|
||||
spacing: 1,
|
||||
divider: false
|
||||
},
|
||||
{
|
||||
type: 10,
|
||||
content: "🚀 **¡Listo!** Ahora puedes usar los comandos con el nuevo prefix.\n\n💡 **Ejemplo:** `" + newPrefix + "help`, `" + newPrefix + "embedlist`"
|
||||
}
|
||||
{ type: 10, content: "### ✅ **Prefix Actualizado Exitosamente**" },
|
||||
{ type: 14, spacing: 2, divider: true },
|
||||
{ type: 9, components: [ { type: 10, content: `**Prefix anterior:** \`${currentPrefix}\`\n**Prefix nuevo:** \`${newPrefix}\`\n\n**Motivo:** ${description}` } ], accessory: { type: 2, style: 3, label: "✓ Listo", custom_id: "prefix_confirmed", emoji: { name: "✅" } } },
|
||||
{ type: 14, spacing: 1, divider: false },
|
||||
{ type: 10, content: "🚀 **¡Listo!** Ahora puedes usar los comandos con el nuevo prefix.\n\n💡 **Ejemplo:** `" + newPrefix + "help`, `" + newPrefix + "embedlist`" }
|
||||
]
|
||||
};
|
||||
|
||||
// Botón para volver al panel principal
|
||||
const backToSettingsRow = {
|
||||
type: 1,
|
||||
components: [
|
||||
{
|
||||
type: 2,
|
||||
style: 2, // Secondary
|
||||
label: "↩️ Volver a Configuración",
|
||||
custom_id: "back_to_settings"
|
||||
}
|
||||
]
|
||||
};
|
||||
const backToSettingsRow = { type: 1, components: [ { type: 2, style: 2, label: "↩️ Volver a Configuración", custom_id: "back_to_settings" } ] };
|
||||
|
||||
// Actualizar el panel original
|
||||
await modalInteraction.update({
|
||||
components: [successPanel, backToSettingsRow]
|
||||
});
|
||||
await modalInteraction.update({ components: [successPanel, backToSettingsRow] });
|
||||
|
||||
} catch (error) {
|
||||
const errorPanel = {
|
||||
type: 17,
|
||||
accent_color: 15548997, // Rojo
|
||||
accent_color: 15548997,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: "### ❌ **Error al Actualizar Prefix**"
|
||||
},
|
||||
{
|
||||
type: 14,
|
||||
spacing: 2,
|
||||
divider: true
|
||||
},
|
||||
{
|
||||
type: 10,
|
||||
content: `**Error:** No se pudo actualizar el prefix a \`${newPrefix}\`\n\n**Posibles causas:**\n• Error de conexión con la base de datos\n• Prefix contiene caracteres no válidos\n• Permisos insuficientes\n\n🔄 **Solución:** Intenta nuevamente con un prefix diferente.`
|
||||
}
|
||||
{ type: 10, content: "### ❌ **Error al Actualizar Prefix**" },
|
||||
{ type: 14, spacing: 2, divider: true },
|
||||
{ type: 10, content: `**Error:** No se pudo actualizar el prefix a \`${newPrefix}\`\n\n**Posibles causas:**\n• Error de conexión con la base de datos\n• Prefix contiene caracteres no válidos\n• Permisos insuficientes\n\n🔄 **Solución:** Intenta nuevamente con un prefix diferente.` }
|
||||
]
|
||||
};
|
||||
|
||||
const retryRow = {
|
||||
type: 1,
|
||||
components: [
|
||||
{
|
||||
type: 2,
|
||||
style: 2,
|
||||
label: "🔄 Reintentar",
|
||||
custom_id: "open_prefix_modal"
|
||||
},
|
||||
{
|
||||
type: 2,
|
||||
style: 4, // Danger
|
||||
label: "❌ Cancelar",
|
||||
custom_id: "cancel_prefix_change"
|
||||
}
|
||||
]
|
||||
};
|
||||
const retryRow = { type: 1, components: [ { type: 2, style: 2, label: "🔄 Reintentar", custom_id: "open_prefix_modal" }, { type: 2, style: 4, label: "❌ Cancelar", custom_id: "cancel_prefix_change" } ] };
|
||||
|
||||
await modalInteraction.update({
|
||||
components: [errorPanel, retryRow]
|
||||
});
|
||||
await modalInteraction.update({ components: [errorPanel, retryRow] });
|
||||
}
|
||||
}).catch(async (error: any) => {
|
||||
// Modal timeout o cancelado
|
||||
logger.info("Modal timeout o error:", error.message);
|
||||
});
|
||||
}
|
||||
|
||||
if (interaction.customId === "open_staff_modal") {
|
||||
// Modal para seleccionar hasta 3 roles de staff
|
||||
const staffModal = {
|
||||
title: "🛡️ Configurar Roles de Staff",
|
||||
customId: "staff_roles_modal",
|
||||
components: [
|
||||
{ type: ComponentType.Label, label: "Selecciona hasta 3 roles de staff", component: { type: ComponentType.RoleSelect, customId: "staff_roles", required: false, minValues: 0, maxValues: 3, placeholder: "Roles de staff..." } }
|
||||
]
|
||||
} as const;
|
||||
|
||||
await interaction.showModal(staffModal);
|
||||
|
||||
try {
|
||||
const modalInteraction = await interaction.awaitModalSubmit({ time: 300000 });
|
||||
const selected = modalInteraction.components.getSelectedRoles('staff_roles');
|
||||
const roleIds: string[] = selected ? Array.from(selected.keys()).slice(0, 3) : [];
|
||||
|
||||
await client.prisma.guild.upsert({
|
||||
where: { id: message.guild!.id },
|
||||
create: { id: message.guild!.id, name: message.guild!.name, staff: roleIds },
|
||||
update: { staff: roleIds, name: message.guild!.name }
|
||||
});
|
||||
|
||||
const updatedDisplay = roleIds.length ? roleIds.map((id) => `<@&${id}>`).join(', ') : 'Sin staff configurado';
|
||||
|
||||
const successPanel = {
|
||||
type: 17,
|
||||
accent_color: 3066993,
|
||||
components: [
|
||||
{ type: 10, content: "### ✅ **Staff Actualizado**" },
|
||||
{ type: 14, spacing: 2, divider: true },
|
||||
{ type: 10, content: `**Nuevos roles de staff:** ${updatedDisplay}` }
|
||||
]
|
||||
};
|
||||
|
||||
const backRow = { type: 1, components: [ { type: 2, style: 2, label: '↩️ Volver a Configuración', custom_id: 'back_to_settings' } ] };
|
||||
await modalInteraction.update({ components: [successPanel, backRow] });
|
||||
} catch (error) {
|
||||
// timeout o error
|
||||
}
|
||||
}
|
||||
|
||||
// Manejar botones adicionales
|
||||
if (interaction.customId === "back_to_settings") {
|
||||
// Volver al panel principal
|
||||
const updatedServer = await client.prisma.guild.findFirst({
|
||||
where: { id: message.guild!.id }
|
||||
});
|
||||
const updatedServer = await client.prisma.guild.findFirst({ where: { id: message.guild!.id } });
|
||||
const newCurrentPrefix = updatedServer?.prefix || "!";
|
||||
const staffRoles2: string[] = toStringArray(updatedServer?.staff);
|
||||
const staffDisplay2 = staffRoles2.length ? staffRoles2.map((id) => `<@&${id}>`).join(', ') : 'Sin staff configurado';
|
||||
|
||||
const updatedSettingsPanel = {
|
||||
type: 17,
|
||||
accent_color: 6178018,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: "### <:invisible:1418684224441028608> 梅,panel admin,📢\n"
|
||||
},
|
||||
{
|
||||
type: 14,
|
||||
spacing: 1,
|
||||
divider: false
|
||||
},
|
||||
{
|
||||
type: 10,
|
||||
content: "Configuracion del Servidor:"
|
||||
},
|
||||
{
|
||||
type: 9,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: `**Prefix:** \`${newCurrentPrefix}\``
|
||||
}
|
||||
],
|
||||
accessory: {
|
||||
type: 2,
|
||||
style: 2,
|
||||
emoji: { name: "⚙️" },
|
||||
custom_id: "open_prefix_modal",
|
||||
label: "Cambiar"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 14,
|
||||
divider: false
|
||||
}
|
||||
{ type: 10, content: "### <:invisible:1418684224441028608> 梅,panel admin,📢\n" },
|
||||
{ type: 14, spacing: 1, divider: false },
|
||||
{ type: 10, content: "Configuracion del Servidor:" },
|
||||
{ type: 9, components: [ { type: 10, content: `**Prefix:** \`${newCurrentPrefix}\`` } ], accessory: { type: 2, style: 2, emoji: { name: "⚙️" }, custom_id: "open_prefix_modal", label: "Cambiar" } },
|
||||
{ type: 14, divider: false },
|
||||
{ type: 9, components: [ { type: 10, content: `**Staff (roles):** ${staffDisplay2}` } ], accessory: { type: 2, style: 2, emoji: { name: "🛡️" }, custom_id: "open_staff_modal", label: "Configurar" } },
|
||||
{ type: 14, divider: false }
|
||||
]
|
||||
};
|
||||
|
||||
await interaction.update({
|
||||
components: [updatedSettingsPanel]
|
||||
});
|
||||
await interaction.update({ components: [updatedSettingsPanel] });
|
||||
}
|
||||
|
||||
if (interaction.customId === "cancel_prefix_change") {
|
||||
// Volver al panel original sin cambios
|
||||
await interaction.update({
|
||||
components: [settingsPanel]
|
||||
});
|
||||
// Volver al panel original
|
||||
const updatedServer = await client.prisma.guild.findFirst({ where: { id: message.guild!.id } });
|
||||
const staffRoles3: string[] = toStringArray(updatedServer?.staff);
|
||||
const staffDisplay3 = staffRoles3.length ? staffRoles3.map((id) => `<@&${id}>`).join(', ') : 'Sin staff configurado';
|
||||
|
||||
const originalPanel = {
|
||||
type: 17,
|
||||
accent_color: 6178018,
|
||||
components: [
|
||||
{ type: 10, content: "### <:invisible:1418684224441028608> 梅,panel admin,📢\n" },
|
||||
{ type: 14, spacing: 1, divider: false },
|
||||
{ type: 10, content: "Configuracion del Servidor:" },
|
||||
{ type: 9, components: [ { type: 10, content: `**Prefix:** \`${currentPrefix}\`` } ], accessory: { type: 2, style: 2, emoji: { name: "⚙️" }, custom_id: "open_prefix_modal", label: "Cambiar" } },
|
||||
{ type: 14, divider: false },
|
||||
{ type: 9, components: [ { type: 10, content: `**Staff (roles):** ${staffDisplay3}` } ], accessory: { type: 2, style: 2, emoji: { name: "🛡️" }, custom_id: "open_staff_modal", label: "Configurar" } },
|
||||
{ type: 14, divider: false }
|
||||
]
|
||||
};
|
||||
|
||||
await interaction.update({ components: [originalPanel] });
|
||||
}
|
||||
});
|
||||
|
||||
collector.on("end", async (collected: any, reason: string) => {
|
||||
collector.on("end", async (_: any, reason: string) => {
|
||||
if (reason === "time") {
|
||||
const timeoutPanel = {
|
||||
type: 17,
|
||||
accent_color: 6178018,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: "### ⏰ **Panel Expirado**"
|
||||
},
|
||||
{
|
||||
type: 14,
|
||||
spacing: 1,
|
||||
divider: true
|
||||
},
|
||||
{
|
||||
type: 10,
|
||||
content: "El panel de configuración ha expirado por inactividad.\n\nUsa `!settings` para abrir un nuevo panel."
|
||||
}
|
||||
{ type: 10, content: "### ⏰ **Panel Expirado**" },
|
||||
{ type: 14, spacing: 1, divider: true },
|
||||
{ type: 10, content: "El panel de configuración ha expirado por inactividad.\n\nUsa `!settings` para abrir un nuevo panel." }
|
||||
]
|
||||
};
|
||||
|
||||
try {
|
||||
await panelMessage.edit({
|
||||
components: [timeoutPanel]
|
||||
});
|
||||
await panelMessage.edit({ components: [timeoutPanel] });
|
||||
} catch (error) {
|
||||
// Mensaje eliminado o error de edición
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import logger from "../../core/lib/logger";
|
||||
import {
|
||||
ButtonInteraction,
|
||||
MessageFlags,
|
||||
PermissionFlagsBits
|
||||
MessageFlags
|
||||
} from 'discord.js';
|
||||
import { prisma } from '../../core/database/prisma';
|
||||
import { ComponentType, TextInputStyle } from 'discord-api-types/v10';
|
||||
import { hasManageGuildOrStaff } from "../../core/lib/permissions";
|
||||
|
||||
export default {
|
||||
customId: 'ld_manage_points',
|
||||
@@ -17,11 +17,12 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
// Verificar permisos de administrador
|
||||
// Verificar permisos (ManageGuild o rol de staff)
|
||||
const member = await interaction.guild.members.fetch(interaction.user.id);
|
||||
if (!member.permissions.has(PermissionFlagsBits.ManageGuild)) {
|
||||
const allowed = await hasManageGuildOrStaff(member, interaction.guild.id, prisma);
|
||||
if (!allowed) {
|
||||
return interaction.reply({
|
||||
content: '❌ Solo los administradores pueden gestionar puntos.',
|
||||
content: '❌ Solo admins o staff pueden gestionar puntos.',
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import logger from "../../core/lib/logger";
|
||||
import { ButtonInteraction, MessageFlags, PermissionFlagsBits } from 'discord.js';
|
||||
import { ButtonInteraction, MessageFlags } from 'discord.js';
|
||||
import { buildLeaderboardPanel } from '../../commands/messages/alliaces/leaderboard';
|
||||
import { hasManageGuildOrStaff } from "../../core/lib/permissions";
|
||||
import { prisma } from "../../core/database/prisma";
|
||||
|
||||
export default {
|
||||
customId: 'ld_refresh',
|
||||
@@ -11,9 +13,9 @@ export default {
|
||||
try {
|
||||
await interaction.deferUpdate();
|
||||
|
||||
// Verificar si el usuario es administrador
|
||||
// Verificar si el usuario es admin o staff
|
||||
const member = await interaction.guild.members.fetch(interaction.user.id);
|
||||
const isAdmin = member.permissions.has(PermissionFlagsBits.ManageGuild);
|
||||
const isAdmin = await hasManageGuildOrStaff(member, interaction.guild.id, prisma);
|
||||
|
||||
// Reusar el builder esperando un objeto con guild y author
|
||||
const fakeMessage: any = { guild: interaction.guild, author: interaction.user };
|
||||
|
||||
@@ -2,13 +2,13 @@ import logger from "../../core/lib/logger";
|
||||
import {
|
||||
ModalSubmitInteraction,
|
||||
MessageFlags,
|
||||
PermissionFlagsBits,
|
||||
EmbedBuilder,
|
||||
User,
|
||||
Collection,
|
||||
Snowflake
|
||||
} from 'discord.js';
|
||||
import { prisma } from '../../core/database/prisma';
|
||||
import { hasManageGuildOrStaff } from "../../core/lib/permissions";
|
||||
|
||||
interface UserSelectComponent {
|
||||
custom_id: string;
|
||||
@@ -36,11 +36,12 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
// Verificar permisos
|
||||
// Verificar permisos (ManageGuild o rol staff)
|
||||
const member = await interaction.guild.members.fetch(interaction.user.id);
|
||||
if (!member.permissions.has(PermissionFlagsBits.ManageGuild)) {
|
||||
const allowed = await hasManageGuildOrStaff(member, interaction.guild.id, prisma);
|
||||
if (!allowed) {
|
||||
return interaction.reply({
|
||||
content: '❌ Solo los administradores pueden gestionar puntos.',
|
||||
content: '❌ Solo admins o staff pueden gestionar puntos.',
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
35
src/core/lib/permissions.ts
Normal file
35
src/core/lib/permissions.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { GuildMember } from 'discord.js';
|
||||
import type { PrismaClient } from '@prisma/client';
|
||||
|
||||
function toStringArray(input: unknown): string[] {
|
||||
if (!Array.isArray(input)) return [];
|
||||
return (input as unknown[]).filter((v): v is string => typeof v === 'string');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the member has ManageGuild permission or has a role included
|
||||
* in the Guild.staff JSON (expected: string[] of role IDs).
|
||||
*/
|
||||
export async function hasManageGuildOrStaff(
|
||||
member: GuildMember | null | undefined,
|
||||
guildId: string,
|
||||
prisma: PrismaClient
|
||||
): Promise<boolean> {
|
||||
if (!member) return false;
|
||||
|
||||
try {
|
||||
// Native permission first
|
||||
if (member.permissions.has('ManageGuild')) return true;
|
||||
|
||||
// Load guild staff config and coerce safely to string[]
|
||||
const guild = await prisma.guild.findFirst({ where: { id: guildId } });
|
||||
const staff = toStringArray(guild?.staff ?? []);
|
||||
if (!staff.length) return false;
|
||||
|
||||
// Check role intersection
|
||||
const memberRoles = member.roles?.cache ? Array.from(member.roles.cache.keys()) : [];
|
||||
return staff.some((roleId) => memberRoles.includes(roleId));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user