Introduces new commands like `/automod`, `/giveaway`, `/rolepanel`, and `/schedule`. Enhances existing commands such as `/announce`, `/help`, `/leaderboard`, `/profile`, and `/serverinfo` with new features and improved embed designs. Updates welcome and goodbye listeners with rich embeds. Fixes a critical issue in the `/rolepanel` command regarding channel fetching. Adds interaction handling for role buttons and giveaway entries. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: eefee140-1301-4b6f-9439-2b0b883aa40a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/qAaysIh Replit-Helium-Checkpoint-Created: true
155 lines
5.4 KiB
JavaScript
155 lines
5.4 KiB
JavaScript
const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js');
|
|
|
|
module.exports = {
|
|
data: new SlashCommandBuilder()
|
|
.setName('announce')
|
|
.setDescription('Send an announcement to all realms')
|
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
|
.addStringOption(option =>
|
|
option.setName('title')
|
|
.setDescription('Announcement title')
|
|
.setRequired(true)
|
|
.setMaxLength(256)
|
|
)
|
|
.addStringOption(option =>
|
|
option.setName('message')
|
|
.setDescription('Announcement message')
|
|
.setRequired(true)
|
|
.setMaxLength(2000)
|
|
)
|
|
.addStringOption(option =>
|
|
option.setName('type')
|
|
.setDescription('Announcement type (changes color and style)')
|
|
.setRequired(false)
|
|
.addChoices(
|
|
{ name: '📢 General (Purple)', value: 'general' },
|
|
{ name: '🆕 Update (Green)', value: 'update' },
|
|
{ name: '🎉 Event (Orange)', value: 'event' },
|
|
{ name: '⚠️ Important (Red)', value: 'important' },
|
|
{ name: '⭐ Highlight (Gold)', value: 'highlight' },
|
|
{ name: '💡 Info (Blue)', value: 'info' }
|
|
)
|
|
)
|
|
.addStringOption(option =>
|
|
option.setName('image')
|
|
.setDescription('Image URL to include')
|
|
.setRequired(false)
|
|
)
|
|
.addBooleanOption(option =>
|
|
option.setName('ping')
|
|
.setDescription('Ping @everyone with this announcement')
|
|
.setRequired(false)
|
|
),
|
|
|
|
async execute(interaction, supabase, client) {
|
|
await interaction.deferReply({ ephemeral: true });
|
|
|
|
const title = interaction.options.getString('title');
|
|
const message = interaction.options.getString('message');
|
|
const type = interaction.options.getString('type') || 'general';
|
|
const imageUrl = interaction.options.getString('image');
|
|
const ping = interaction.options.getBoolean('ping') || false;
|
|
|
|
const typeConfig = {
|
|
general: { color: 0x7c3aed, emoji: '📢', label: 'Announcement' },
|
|
update: { color: 0x22c55e, emoji: '🆕', label: 'Update' },
|
|
event: { color: 0xf97316, emoji: '🎉', label: 'Event' },
|
|
important: { color: 0xef4444, emoji: '⚠️', label: 'Important' },
|
|
highlight: { color: 0xfbbf24, emoji: '⭐', label: 'Highlight' },
|
|
info: { color: 0x3b82f6, emoji: '💡', label: 'Info' }
|
|
};
|
|
|
|
const config = typeConfig[type];
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setColor(config.color)
|
|
.setAuthor({
|
|
name: `${config.emoji} ${config.label}`,
|
|
iconURL: interaction.guild.iconURL({ size: 64 })
|
|
})
|
|
.setTitle(title)
|
|
.setDescription(message)
|
|
.setFooter({
|
|
text: `Announced by ${interaction.user.tag} • ${interaction.guild.name}`,
|
|
iconURL: interaction.user.displayAvatarURL({ size: 32 })
|
|
})
|
|
.setTimestamp();
|
|
|
|
if (imageUrl) {
|
|
embed.setImage(imageUrl);
|
|
}
|
|
|
|
const results = [];
|
|
const REALM_GUILDS = client.REALM_GUILDS;
|
|
|
|
for (const [realm, guildId] of Object.entries(REALM_GUILDS)) {
|
|
if (!guildId) continue;
|
|
|
|
const guild = client.guilds.cache.get(guildId);
|
|
if (!guild) {
|
|
results.push({ realm, status: 'offline', emoji: '⚫' });
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const announcementChannel = guild.channels.cache.find(
|
|
c => c.isTextBased() && !c.isThread() && !c.isVoiceBased() &&
|
|
(c.name.includes('announcement') || c.name.includes('general'))
|
|
);
|
|
|
|
if (announcementChannel && announcementChannel.isTextBased()) {
|
|
const content = ping ? '@everyone' : null;
|
|
await announcementChannel.send({ content, embeds: [embed] });
|
|
results.push({ realm, status: 'sent', channel: announcementChannel.name, emoji: '✅' });
|
|
} else {
|
|
results.push({ realm, status: 'no_channel', emoji: '⚠️' });
|
|
}
|
|
} catch (error) {
|
|
console.error(`Announce error for ${realm}:`, error);
|
|
results.push({ realm, status: 'error', error: error.message, emoji: '❌' });
|
|
}
|
|
}
|
|
|
|
if (supabase) {
|
|
try {
|
|
await supabase.from('audit_logs').insert({
|
|
action: 'announce',
|
|
user_id: interaction.user.id,
|
|
username: interaction.user.tag,
|
|
guild_id: interaction.guildId,
|
|
details: { title, message, type, results },
|
|
});
|
|
} catch (e) {
|
|
console.warn('Failed to log announcement:', e.message);
|
|
}
|
|
}
|
|
|
|
const successCount = results.filter(r => r.status === 'sent').length;
|
|
const failCount = results.filter(r => r.status !== 'sent' && r.status !== 'offline').length;
|
|
|
|
const resultEmbed = new EmbedBuilder()
|
|
.setColor(successCount > 0 ? 0x22c55e : 0xef4444)
|
|
.setTitle(`${config.emoji} Announcement Results`)
|
|
.setDescription(
|
|
results.length > 0
|
|
? results.map(r =>
|
|
`${r.emoji} **${capitalizeFirst(r.realm)}**: ${r.status}${r.channel ? ` (#${r.channel})` : ''}`
|
|
).join('\n')
|
|
: 'No realms configured'
|
|
)
|
|
.addFields({
|
|
name: '📊 Summary',
|
|
value: `✅ Sent: ${successCount} | ⚠️ Failed: ${failCount} | ⚫ Offline: ${results.filter(r => r.status === 'offline').length}`,
|
|
inline: false
|
|
})
|
|
.setFooter({ text: `Type: ${config.label}` })
|
|
.setTimestamp();
|
|
|
|
await interaction.editReply({ embeds: [resultEmbed] });
|
|
},
|
|
};
|
|
|
|
function capitalizeFirst(str) {
|
|
if (!str) return str;
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|