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
|
id String @id
|
||||||
name String
|
name String
|
||||||
prefix String @default("!")
|
prefix String @default("!")
|
||||||
|
staff Json?
|
||||||
|
|
||||||
// Relaciones
|
// Relaciones
|
||||||
alliances Alliance[]
|
alliances Alliance[]
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {listVariables} from "../../../core/lib/vars";
|
|||||||
import type Amayo from "../../../core/client";
|
import type Amayo from "../../../core/client";
|
||||||
import {BlockState, DisplayComponentUtils, EditorActionRow} from "../../../core/types/displayComponentEditor";
|
import {BlockState, DisplayComponentUtils, EditorActionRow} from "../../../core/types/displayComponentEditor";
|
||||||
import type {DisplayComponentContainer} from "../../../core/types/displayComponents";
|
import type {DisplayComponentContainer} from "../../../core/types/displayComponents";
|
||||||
|
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||||
|
|
||||||
interface EditorData {
|
interface EditorData {
|
||||||
content?: string;
|
content?: string;
|
||||||
@@ -51,8 +52,9 @@ export const command: CommandMessage = {
|
|||||||
category: "Alianzas",
|
category: "Alianzas",
|
||||||
usage: "crear-embed <nombre>",
|
usage: "crear-embed <nombre>",
|
||||||
run: async (message, args, client) => {
|
run: async (message, args, client) => {
|
||||||
if (!message.member?.permissions.has("Administrator")) {
|
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma);
|
||||||
await message.reply("❌ No tienes permisos de Administrador.");
|
if (!allowed) {
|
||||||
|
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,7 +721,7 @@ async function handleSaveBlock(
|
|||||||
});
|
});
|
||||||
|
|
||||||
await interaction.reply({
|
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
|
flags: MessageFlags.Ephemeral
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { CommandMessage } from "../../../core/types/commands";
|
import { CommandMessage } from "../../../core/types/commands";
|
||||||
import type Amayo from "../../../core/client";
|
import type Amayo from "../../../core/client";
|
||||||
import type { JsonValue } from "@prisma/client/runtime/library";
|
import type { JsonValue } from "@prisma/client/runtime/library";
|
||||||
|
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||||
|
|
||||||
interface BlockItem {
|
interface BlockItem {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -22,16 +23,17 @@ interface ActionRowBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const command: CommandMessage = {
|
export const command: CommandMessage = {
|
||||||
name: "eliminar-embed",
|
name: "eliminar-bloque",
|
||||||
type: "message",
|
type: "message",
|
||||||
aliases: ["embed-eliminar", "borrar-embed", "embeddelete"],
|
aliases: ["bloque-eliminar", "bloque-embed", "blockdelete"],
|
||||||
cooldown: 10,
|
cooldown: 10,
|
||||||
description: "Elimina bloques DisplayComponents del servidor",
|
description: "Elimina bloques DisplayComponents del servidor",
|
||||||
category: "Alianzas",
|
category: "Creacion",
|
||||||
usage: "eliminar-embed [nombre_bloque]",
|
usage: "eliminar-bloque [nombre_bloque]",
|
||||||
run: async (message: Message, args: string[], client: Amayo): Promise<void> => {
|
run: async (message: Message, args: string[], client: Amayo): Promise<void> => {
|
||||||
if (!message.member?.permissions.has("Administrator")) {
|
const allowed = await hasManageGuildOrStaff(message.member, message.guildId!, client.prisma);
|
||||||
await message.reply("❌ No tienes permisos de Administrador.");
|
if (!allowed) {
|
||||||
|
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import type {
|
|||||||
} from "../../../core/types/displayComponents";
|
} from "../../../core/types/displayComponents";
|
||||||
import type Amayo from "../../../core/client";
|
import type Amayo from "../../../core/client";
|
||||||
import type { JsonValue } from "@prisma/client/runtime/library";
|
import type { JsonValue } from "@prisma/client/runtime/library";
|
||||||
|
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||||
|
|
||||||
interface BlockListItem {
|
interface BlockListItem {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -29,17 +30,17 @@ interface ActionRowBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const command: CommandMessage = {
|
export const command: CommandMessage = {
|
||||||
name: "lista-embeds",
|
name: "lista-bloques",
|
||||||
type: "message",
|
type: "message",
|
||||||
aliases: ["embeds", "ver-embeds", "embedlist"],
|
aliases: ["bloques", "ver-bloques", "blocks"],
|
||||||
cooldown: 10,
|
cooldown: 10,
|
||||||
description: "Muestra todos los bloques DisplayComponents configurados en el servidor",
|
description: "Muestra todos los bloques DisplayComponents configurados en el servidor",
|
||||||
category: "Alianzas",
|
category: "Alianzas",
|
||||||
usage: "lista-embeds",
|
usage: "lista-bloques",
|
||||||
run: async (message: Message, args: string[], client: Amayo): Promise<void> => {
|
run: async (message: Message, args: string[], client: Amayo): Promise<void> => {
|
||||||
// Permission check
|
const allowed = await hasManageGuildOrStaff(message.member, message.guildId!, client.prisma);
|
||||||
if (!message.member?.permissions.has("Administrator")) {
|
if (!allowed) {
|
||||||
await message.reply("❌ No tienes permisos de Administrador.");
|
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { CommandMessage } from "../../../core/types/commands";
|
|||||||
import { MessageFlags } from "discord.js";
|
import { MessageFlags } from "discord.js";
|
||||||
import { ComponentType, ButtonStyle, TextInputStyle } from "discord-api-types/v10";
|
import { ComponentType, ButtonStyle, TextInputStyle } from "discord-api-types/v10";
|
||||||
import { replaceVars, isValidUrlOrVariable, listVariables } from "../../../core/lib/vars";
|
import { replaceVars, isValidUrlOrVariable, listVariables } from "../../../core/lib/vars";
|
||||||
|
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||||
|
|
||||||
// Botones de edición (máx 5 por fila)
|
// Botones de edición (máx 5 por fila)
|
||||||
const btns = (disabled = false) => ([
|
const btns = (disabled = false) => ([
|
||||||
@@ -134,8 +135,9 @@ export const command: CommandMessage = {
|
|||||||
category: "Alianzas",
|
category: "Alianzas",
|
||||||
usage: "editar-embed <nombre>",
|
usage: "editar-embed <nombre>",
|
||||||
run: async (message, args, client) => {
|
run: async (message, args, client) => {
|
||||||
if (!message.member?.permissions.has("Administrator")) {
|
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma);
|
||||||
await message.reply("❌ No tienes permisos de Administrador.");
|
if (!allowed) {
|
||||||
|
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +151,7 @@ export const command: CommandMessage = {
|
|||||||
where: { guildId: message.guild!.id, name: blockName }
|
where: { guildId: message.guild!.id, name: blockName }
|
||||||
});
|
});
|
||||||
if (!existingBlock) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// Comando para mostrar el leaderboard de alianzas con botón de refresco
|
// Comando para mostrar el leaderboard de alianzas con botón de refresco
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { CommandMessage } from "../../../core/types/commands";
|
import { CommandMessage } from "../../../core/types/commands";
|
||||||
|
import { PermissionFlagsBits } from "discord.js";
|
||||||
|
import { hasManageGuildOrStaff } from '../../../core/lib/permissions';
|
||||||
import { prisma } from "../../../core/database/prisma";
|
import { prisma } from "../../../core/database/prisma";
|
||||||
import type { Message } from "discord.js";
|
import type { Message } from "discord.js";
|
||||||
import { PermissionFlagsBits } from "discord.js";
|
|
||||||
|
|
||||||
const MAX_ENTRIES = 10;
|
const MAX_ENTRIES = 10;
|
||||||
|
|
||||||
@@ -236,7 +237,7 @@ export const command: CommandMessage = {
|
|||||||
aliases: ['ld'],
|
aliases: ['ld'],
|
||||||
cooldown: 5,
|
cooldown: 5,
|
||||||
description: 'Muestra el leaderboard de alianzas (semanal, mensual y total) con botón de refresco.',
|
description: 'Muestra el leaderboard de alianzas (semanal, mensual y total) con botón de refresco.',
|
||||||
category: 'Utilidad',
|
category: 'Alianzas',
|
||||||
usage: 'leaderboard',
|
usage: 'leaderboard',
|
||||||
run: async (message) => {
|
run: async (message) => {
|
||||||
if (!message.guild) {
|
if (!message.guild) {
|
||||||
@@ -244,9 +245,9 @@ export const command: CommandMessage = {
|
|||||||
return;
|
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 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);
|
const panel = await buildLeaderboardPanel(message, isAdmin);
|
||||||
await message.reply({
|
await message.reply({
|
||||||
|
|||||||
@@ -263,13 +263,13 @@ async function buildChannelListPanel(message: Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const command: CommandMessage = {
|
export const command: CommandMessage = {
|
||||||
name: "listar-canales-alianza-v2",
|
name: "lista-canales",
|
||||||
type: "message",
|
type: "message",
|
||||||
aliases: ["listchannels-alliance-v2", "listalchannel-v2", "channelsally-v2", "alliancechannels-v2"],
|
aliases: ["lca", "channelist", "alliacechannels"],
|
||||||
cooldown: 5,
|
cooldown: 5,
|
||||||
description: "Lista todos los canales configurados para alianzas (versión V2 con components)",
|
description: "Lista todos los canales configurados para alianzas (versión V2 con components)",
|
||||||
category: "Alianzas",
|
category: "Alianzas",
|
||||||
usage: "listar-canales-alianza-v2",
|
usage: "lista-canales",
|
||||||
run: async (message) => {
|
run: async (message) => {
|
||||||
if (!message.guild) {
|
if (!message.guild) {
|
||||||
await message.reply({ content: '❌ Este comando solo puede usarse en servidores.' });
|
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";
|
import { CommandMessage } from "../../../core/types/commands";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { EmbedBuilder, ButtonStyle, MessageFlags, ChannelType } from "discord.js";
|
import { EmbedBuilder, ButtonStyle, MessageFlags, ChannelType } from "discord.js";
|
||||||
|
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||||
|
|
||||||
export const command: CommandMessage = {
|
export const command: CommandMessage = {
|
||||||
name: "eliminar-canal-alianza",
|
name: "eliminar-canal",
|
||||||
type: "message",
|
type: "message",
|
||||||
aliases: ["removechannel-alliance", "removealchannel", "delalchannel"],
|
aliases: ["removechannel-alliance", "removealchannel", "delalchannel"],
|
||||||
cooldown: 10,
|
cooldown: 10,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
run: async (message, args, client) => {
|
run: async (message, args, client) => {
|
||||||
if (!message.member?.permissions.has("Administrator")) {
|
const allowed = await hasManageGuildOrStaff(message.member, message.guildId!, client.prisma);
|
||||||
return message.reply("❌ No tienes permisos de Administrador.");
|
if (!allowed) {
|
||||||
|
return message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtener canales configurados existentes
|
// Obtener canales configurados existentes
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { MessageFlags } from "discord.js";
|
|||||||
import { DisplayComponentUtils } from "../../../core/types/displayComponentEditor";
|
import { DisplayComponentUtils } from "../../../core/types/displayComponentEditor";
|
||||||
import { sendComponentsV2Message } from "../../../core/api/discordAPI";
|
import { sendComponentsV2Message } from "../../../core/api/discordAPI";
|
||||||
import logger from "../../../core/lib/logger";
|
import logger from "../../../core/lib/logger";
|
||||||
|
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||||
|
|
||||||
export const command: CommandMessage = {
|
export const command: CommandMessage = {
|
||||||
name: "send-embed",
|
name: "send-embed",
|
||||||
@@ -13,9 +14,9 @@ export const command: CommandMessage = {
|
|||||||
category: "Alianzas",
|
category: "Alianzas",
|
||||||
usage: "send-embed <nombre>",
|
usage: "send-embed <nombre>",
|
||||||
run: async (message, args, client) => {
|
run: async (message, args, client) => {
|
||||||
// Requiere administrador para evitar abuso mientras se prueba
|
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma);
|
||||||
if (!message.member?.permissions.has("Administrator")) {
|
if (!allowed) {
|
||||||
await message.reply("❌ No tienes permisos de Administrador.");
|
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +36,6 @@ export const command: CommandMessage = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderizamos usando la misma utilidad del editor (fuente: node_modules/discord.js APIs via repo util DisplayComponentUtils)
|
|
||||||
const container = await DisplayComponentUtils.renderPreview(
|
const container = await DisplayComponentUtils.renderPreview(
|
||||||
// @ts-ignore - guardamos BlockState como config
|
// @ts-ignore - guardamos BlockState como config
|
||||||
existingBlock.config,
|
existingBlock.config,
|
||||||
@@ -43,7 +43,6 @@ export const command: CommandMessage = {
|
|||||||
message.guild!
|
message.guild!
|
||||||
);
|
);
|
||||||
|
|
||||||
// Enviamos como Components v2 (fuente: repositorio local sendComponentsV2Message)
|
|
||||||
await sendComponentsV2Message(message.channel.id, {
|
await sendComponentsV2Message(message.channel.id, {
|
||||||
components: [container as any],
|
components: [container as any],
|
||||||
replyToMessageId: message.id
|
replyToMessageId: message.id
|
||||||
@@ -59,4 +58,3 @@ export const command: CommandMessage = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import { CommandMessage } from "../../../core/types/commands";
|
import { CommandMessage } from "../../../core/types/commands";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { ComponentType, ButtonStyle, MessageFlags, ChannelType } from "discord.js";
|
import { ComponentType, ButtonStyle, MessageFlags, ChannelType } from "discord.js";
|
||||||
|
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||||
|
|
||||||
export const command: CommandMessage = {
|
export const command: CommandMessage = {
|
||||||
name: "canal-alianza",
|
name: "canal-alianza",
|
||||||
type: "message",
|
type: "message",
|
||||||
aliases: ["alchannel", "channelally"],
|
aliases: ["alchannel", "channelally"],
|
||||||
|
description: "Configura canales para el sistema de alianzas con bloques DisplayComponents.",
|
||||||
|
usage: "canal-alianza",
|
||||||
|
category: "Alianzas",
|
||||||
cooldown: 10,
|
cooldown: 10,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
run: async (message, args, client) => {
|
run: async (message, args, client) => {
|
||||||
if (!message.member?.permissions.has("Administrator")) {
|
const allowed = await hasManageGuildOrStaff(message.member, message.guildId!, client.prisma);
|
||||||
return message.reply("❌ No tienes permisos de Administrador.");
|
if (!allowed) {
|
||||||
|
return message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtener canales configurados existentes
|
// Obtener canales configurados existentes
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import logger from "../../../core/lib/logger";
|
import logger from "../../../core/lib/logger";
|
||||||
import { CommandMessage } from "../../../core/types/commands";
|
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 = {
|
export const command: CommandMessage = {
|
||||||
name: 'configuracion',
|
name: 'configuracion',
|
||||||
type: "message",
|
type: "message",
|
||||||
aliases: ['config', 'ajustes', 'settings'],
|
aliases: ['config', 'ajustes', 'settings'],
|
||||||
cooldown: 5,
|
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',
|
category: 'Configuración',
|
||||||
usage: 'configuracion',
|
usage: 'configuracion',
|
||||||
run: async (message, args, client) => {
|
run: async (message, args, client) => {
|
||||||
if (!message.member?.permissions.has("Administrator")) {
|
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma);
|
||||||
await message.reply("❌ No tienes permisos de Administrador.");
|
if (!allowed) {
|
||||||
|
await message.reply("❌ No tienes permisos de ManageGuild ni rol de staff.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,52 +28,48 @@ export const command: CommandMessage = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const currentPrefix = server?.prefix || "!";
|
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
|
// Panel de configuración usando DisplayComponents
|
||||||
const settingsPanel = {
|
const settingsPanel = {
|
||||||
type: 17,
|
type: 17,
|
||||||
accent_color: 6178018, // Color del ejemplo
|
accent_color: 6178018, // Color del ejemplo
|
||||||
components: [
|
components: [
|
||||||
|
{ type: 10, content: "### <:invisible:1418684224441028608> 梅,panel admin,📢\n" },
|
||||||
|
{ type: 14, spacing: 1, divider: false },
|
||||||
|
{ type: 10, content: "Configuracion del Servidor:" },
|
||||||
{
|
{
|
||||||
type: 10,
|
type: 9,
|
||||||
content: "### <:invisible:1418684224441028608> 梅,panel admin,📢\n"
|
components: [ { type: 10, content: `**Prefix:**<:invisible:1418684224441028608>\`${currentPrefix}\`` } ],
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 14,
|
|
||||||
spacing: 1,
|
|
||||||
divider: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 10,
|
|
||||||
content: "Configuracion del Servidor:"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 9, // Section
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
type: 10,
|
|
||||||
content: `**Prefix:**<:invisible:1418684224441028608>\`${currentPrefix}\``
|
|
||||||
}
|
|
||||||
],
|
|
||||||
accessory: {
|
accessory: {
|
||||||
type: 2, // Button
|
type: 2,
|
||||||
style: 2, // Secondary
|
style: 2,
|
||||||
emoji: {
|
emoji: { name: "⚙️" },
|
||||||
name: "⚙️"
|
|
||||||
},
|
|
||||||
custom_id: "open_prefix_modal",
|
custom_id: "open_prefix_modal",
|
||||||
label: "Cambiar"
|
label: "Cambiar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ type: 14, divider: false },
|
||||||
{
|
{
|
||||||
type: 14,
|
type: 9,
|
||||||
divider: false
|
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({
|
const panelMessage = await message.reply({
|
||||||
flags: 32768, // SuppressEmbeds
|
flags: 32768, // Components v2
|
||||||
components: [settingsPanel]
|
components: [settingsPanel]
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,44 +85,15 @@ export const command: CommandMessage = {
|
|||||||
title: "⚙️ Configurar Prefix del Servidor",
|
title: "⚙️ Configurar Prefix del Servidor",
|
||||||
custom_id: "prefix_settings_modal",
|
custom_id: "prefix_settings_modal",
|
||||||
components: [
|
components: [
|
||||||
{
|
{ 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, // ActionRow
|
{ 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 } ] }
|
||||||
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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await interaction.showModal(prefixModal);
|
await interaction.showModal(prefixModal);
|
||||||
|
|
||||||
// Crear un collector específico para este modal
|
|
||||||
const modalCollector = interaction.awaitModalSubmit({
|
const modalCollector = interaction.awaitModalSubmit({
|
||||||
time: 300000, // 5 minutos
|
time: 300000,
|
||||||
filter: (modalInt: any) => modalInt.customId === "prefix_settings_modal" && modalInt.user.id === message.author.id
|
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 newPrefix = modalInteraction.fields.getTextInputValue("new_prefix_input");
|
||||||
const description = modalInteraction.fields.getTextInputValue("prefix_description") || "Sin descripción";
|
const description = modalInteraction.fields.getTextInputValue("prefix_description") || "Sin descripción";
|
||||||
|
|
||||||
// Validar prefix
|
|
||||||
if (!newPrefix || newPrefix.length > 10) {
|
if (!newPrefix || newPrefix.length > 10) {
|
||||||
await modalInteraction.reply({
|
await modalInteraction.reply({ content: "❌ **Error:** El prefix debe tener entre 1 y 10 caracteres.", flags: 64 });
|
||||||
content: "❌ **Error:** El prefix debe tener entre 1 y 10 caracteres.",
|
|
||||||
flags: 64 // Ephemeral
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Actualizar prefix en la base de datos
|
|
||||||
await client.prisma.guild.upsert({
|
await client.prisma.guild.upsert({
|
||||||
where: { id: message.guild!.id },
|
where: { id: message.guild!.id },
|
||||||
create: {
|
create: { id: message.guild!.id, name: message.guild!.name, prefix: newPrefix },
|
||||||
id: message.guild!.id,
|
update: { prefix: newPrefix, name: message.guild!.name }
|
||||||
name: message.guild!.name,
|
|
||||||
prefix: newPrefix
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
prefix: newPrefix,
|
|
||||||
name: message.guild!.name
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Panel de confirmación
|
|
||||||
const successPanel = {
|
const successPanel = {
|
||||||
type: 17,
|
type: 17,
|
||||||
accent_color: 3066993, // Verde
|
accent_color: 3066993,
|
||||||
components: [
|
components: [
|
||||||
{
|
{ type: 10, content: "### ✅ **Prefix Actualizado Exitosamente**" },
|
||||||
type: 10,
|
{ type: 14, spacing: 2, divider: true },
|
||||||
content: "### ✅ **Prefix Actualizado Exitosamente**"
|
{ 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`" }
|
||||||
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`"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Botón para volver al panel principal
|
const backToSettingsRow = { type: 1, components: [ { type: 2, style: 2, label: "↩️ Volver a Configuración", custom_id: "back_to_settings" } ] };
|
||||||
const backToSettingsRow = {
|
|
||||||
type: 1,
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
type: 2,
|
|
||||||
style: 2, // Secondary
|
|
||||||
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) {
|
} catch (error) {
|
||||||
const errorPanel = {
|
const errorPanel = {
|
||||||
type: 17,
|
type: 17,
|
||||||
accent_color: 15548997, // Rojo
|
accent_color: 15548997,
|
||||||
components: [
|
components: [
|
||||||
{
|
{ type: 10, content: "### ❌ **Error al Actualizar Prefix**" },
|
||||||
type: 10,
|
{ type: 14, spacing: 2, divider: true },
|
||||||
content: "### ❌ **Error al Actualizar Prefix**"
|
{ 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: 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 = {
|
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" } ] };
|
||||||
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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
await modalInteraction.update({
|
await modalInteraction.update({ components: [errorPanel, retryRow] });
|
||||||
components: [errorPanel, retryRow]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}).catch(async (error: any) => {
|
}).catch(async (error: any) => {
|
||||||
// Modal timeout o cancelado
|
|
||||||
logger.info("Modal timeout o error:", error.message);
|
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
|
// Manejar botones adicionales
|
||||||
if (interaction.customId === "back_to_settings") {
|
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 newCurrentPrefix = updatedServer?.prefix || "!";
|
||||||
|
const staffRoles2: string[] = toStringArray(updatedServer?.staff);
|
||||||
|
const staffDisplay2 = staffRoles2.length ? staffRoles2.map((id) => `<@&${id}>`).join(', ') : 'Sin staff configurado';
|
||||||
|
|
||||||
const updatedSettingsPanel = {
|
const updatedSettingsPanel = {
|
||||||
type: 17,
|
type: 17,
|
||||||
accent_color: 6178018,
|
accent_color: 6178018,
|
||||||
components: [
|
components: [
|
||||||
{
|
{ type: 10, content: "### <:invisible:1418684224441028608> 梅,panel admin,📢\n" },
|
||||||
type: 10,
|
{ type: 14, spacing: 1, divider: false },
|
||||||
content: "### <:invisible:1418684224441028608> 梅,panel admin,📢\n"
|
{ 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: 14,
|
{ type: 9, components: [ { type: 10, content: `**Staff (roles):** ${staffDisplay2}` } ], accessory: { type: 2, style: 2, emoji: { name: "🛡️" }, custom_id: "open_staff_modal", label: "Configurar" } },
|
||||||
spacing: 1,
|
{ type: 14, divider: false }
|
||||||
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
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await interaction.update({
|
await interaction.update({ components: [updatedSettingsPanel] });
|
||||||
components: [updatedSettingsPanel]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interaction.customId === "cancel_prefix_change") {
|
if (interaction.customId === "cancel_prefix_change") {
|
||||||
// Volver al panel original sin cambios
|
// Volver al panel original
|
||||||
await interaction.update({
|
const updatedServer = await client.prisma.guild.findFirst({ where: { id: message.guild!.id } });
|
||||||
components: [settingsPanel]
|
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") {
|
if (reason === "time") {
|
||||||
const timeoutPanel = {
|
const timeoutPanel = {
|
||||||
type: 17,
|
type: 17,
|
||||||
accent_color: 6178018,
|
accent_color: 6178018,
|
||||||
components: [
|
components: [
|
||||||
{
|
{ type: 10, content: "### ⏰ **Panel Expirado**" },
|
||||||
type: 10,
|
{ type: 14, spacing: 1, divider: true },
|
||||||
content: "### ⏰ **Panel Expirado**"
|
{ type: 10, content: "El panel de configuración ha expirado por inactividad.\n\nUsa `!settings` para abrir un nuevo panel." }
|
||||||
},
|
|
||||||
{
|
|
||||||
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 {
|
try {
|
||||||
await panelMessage.edit({
|
await panelMessage.edit({ components: [timeoutPanel] });
|
||||||
components: [timeoutPanel]
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Mensaje eliminado o error de edición
|
// Mensaje eliminado o error de edición
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import logger from "../../core/lib/logger";
|
import logger from "../../core/lib/logger";
|
||||||
import {
|
import {
|
||||||
ButtonInteraction,
|
ButtonInteraction,
|
||||||
MessageFlags,
|
MessageFlags
|
||||||
PermissionFlagsBits
|
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import { prisma } from '../../core/database/prisma';
|
import { prisma } from '../../core/database/prisma';
|
||||||
import { ComponentType, TextInputStyle } from 'discord-api-types/v10';
|
import { ComponentType, TextInputStyle } from 'discord-api-types/v10';
|
||||||
|
import { hasManageGuildOrStaff } from "../../core/lib/permissions";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
customId: 'ld_manage_points',
|
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);
|
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({
|
return interaction.reply({
|
||||||
content: '❌ Solo los administradores pueden gestionar puntos.',
|
content: '❌ Solo admins o staff pueden gestionar puntos.',
|
||||||
flags: MessageFlags.Ephemeral
|
flags: MessageFlags.Ephemeral
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import logger from "../../core/lib/logger";
|
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 { buildLeaderboardPanel } from '../../commands/messages/alliaces/leaderboard';
|
||||||
|
import { hasManageGuildOrStaff } from "../../core/lib/permissions";
|
||||||
|
import { prisma } from "../../core/database/prisma";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
customId: 'ld_refresh',
|
customId: 'ld_refresh',
|
||||||
@@ -11,9 +13,9 @@ export default {
|
|||||||
try {
|
try {
|
||||||
await interaction.deferUpdate();
|
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 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
|
// Reusar el builder esperando un objeto con guild y author
|
||||||
const fakeMessage: any = { guild: interaction.guild, author: interaction.user };
|
const fakeMessage: any = { guild: interaction.guild, author: interaction.user };
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import logger from "../../core/lib/logger";
|
|||||||
import {
|
import {
|
||||||
ModalSubmitInteraction,
|
ModalSubmitInteraction,
|
||||||
MessageFlags,
|
MessageFlags,
|
||||||
PermissionFlagsBits,
|
|
||||||
EmbedBuilder,
|
EmbedBuilder,
|
||||||
User,
|
User,
|
||||||
Collection,
|
Collection,
|
||||||
Snowflake
|
Snowflake
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import { prisma } from '../../core/database/prisma';
|
import { prisma } from '../../core/database/prisma';
|
||||||
|
import { hasManageGuildOrStaff } from "../../core/lib/permissions";
|
||||||
|
|
||||||
interface UserSelectComponent {
|
interface UserSelectComponent {
|
||||||
custom_id: string;
|
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);
|
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({
|
return interaction.reply({
|
||||||
content: '❌ Solo los administradores pueden gestionar puntos.',
|
content: '❌ Solo admins o staff pueden gestionar puntos.',
|
||||||
flags: MessageFlags.Ephemeral
|
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