Add new commands and improve existing ones for better user experience
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
This commit is contained in:
parent
cbfc697853
commit
6f5c37959f
15 changed files with 2590 additions and 122 deletions
2
.replit
2
.replit
|
|
@ -23,7 +23,7 @@ localPort = 8080
|
|||
externalPort = 8080
|
||||
|
||||
[[ports]]
|
||||
localPort = 46469
|
||||
localPort = 45017
|
||||
externalPort = 3000
|
||||
|
||||
[workflows]
|
||||
|
|
|
|||
|
|
@ -867,6 +867,142 @@ client.on("interactionCreate", async (interaction) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'role') {
|
||||
const roleId = params.join('_');
|
||||
try {
|
||||
const role = interaction.guild.roles.cache.get(roleId);
|
||||
if (!role) {
|
||||
return interaction.reply({ content: 'This role no longer exists.', ephemeral: true });
|
||||
}
|
||||
|
||||
const member = interaction.member;
|
||||
if (member.roles.cache.has(roleId)) {
|
||||
await member.roles.remove(roleId);
|
||||
await interaction.reply({ content: `Removed ${role} from you!`, ephemeral: true });
|
||||
} else {
|
||||
await member.roles.add(roleId);
|
||||
await interaction.reply({ content: `Added ${role} to you!`, ephemeral: true });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Role button error:', err);
|
||||
await interaction.reply({ content: 'Failed to toggle role. Check bot permissions.', ephemeral: true }).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'giveaway') {
|
||||
const giveawayAction = params[0];
|
||||
if (giveawayAction === 'enter') {
|
||||
try {
|
||||
const messageId = interaction.message.id;
|
||||
|
||||
let giveawayData = client.giveaways?.get(messageId);
|
||||
let entries = giveawayData?.entries || [];
|
||||
|
||||
if (supabase) {
|
||||
const { data } = await supabase
|
||||
.from('giveaways')
|
||||
.select('*')
|
||||
.eq('message_id', messageId)
|
||||
.single();
|
||||
|
||||
if (data) {
|
||||
entries = data.entries || [];
|
||||
giveawayData = data;
|
||||
}
|
||||
}
|
||||
|
||||
if (!giveawayData) {
|
||||
return interaction.reply({ content: 'This giveaway is no longer active.', ephemeral: true });
|
||||
}
|
||||
|
||||
if (giveawayData.required_role) {
|
||||
const hasRole = interaction.member.roles.cache.has(giveawayData.required_role);
|
||||
if (!hasRole) {
|
||||
return interaction.reply({ content: `You need the <@&${giveawayData.required_role}> role to enter!`, ephemeral: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.includes(interaction.user.id)) {
|
||||
return interaction.reply({ content: 'You have already entered this giveaway!', ephemeral: true });
|
||||
}
|
||||
|
||||
entries.push(interaction.user.id);
|
||||
|
||||
if (client.giveaways?.has(messageId)) {
|
||||
client.giveaways.get(messageId).entries = entries;
|
||||
}
|
||||
|
||||
if (supabase) {
|
||||
await supabase
|
||||
.from('giveaways')
|
||||
.update({ entries: entries })
|
||||
.eq('message_id', messageId);
|
||||
}
|
||||
|
||||
const embed = EmbedBuilder.from(interaction.message.embeds[0]);
|
||||
const entriesField = embed.data.fields?.find(f => f.name.includes('Entries'));
|
||||
if (entriesField) {
|
||||
entriesField.value = `${entries.length}`;
|
||||
}
|
||||
await interaction.message.edit({ embeds: [embed] });
|
||||
|
||||
await interaction.reply({ content: `You have entered the giveaway! Total entries: ${entries.length}`, ephemeral: true });
|
||||
|
||||
} catch (err) {
|
||||
console.error('Giveaway entry error:', err);
|
||||
await interaction.reply({ content: 'Failed to enter giveaway.', ephemeral: true }).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (interaction.isModalSubmit()) {
|
||||
if (interaction.customId.startsWith('embed_modal_')) {
|
||||
try {
|
||||
const parts = interaction.customId.split('_');
|
||||
const channelId = parts[2];
|
||||
const color = parseInt(parts[3], 16);
|
||||
|
||||
const title = interaction.fields.getTextInputValue('embed_title');
|
||||
const description = interaction.fields.getTextInputValue('embed_description');
|
||||
const imageUrl = interaction.fields.getTextInputValue('embed_image') || null;
|
||||
const thumbnailUrl = interaction.fields.getTextInputValue('embed_thumbnail') || null;
|
||||
const footerText = interaction.fields.getTextInputValue('embed_footer') || null;
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(color)
|
||||
.setTitle(title)
|
||||
.setDescription(description)
|
||||
.setTimestamp();
|
||||
|
||||
if (imageUrl) embed.setImage(imageUrl);
|
||||
if (thumbnailUrl) embed.setThumbnail(thumbnailUrl);
|
||||
if (footerText) embed.setFooter({ text: footerText });
|
||||
|
||||
const channel = await client.channels.fetch(channelId);
|
||||
await channel.send({ embeds: [embed] });
|
||||
|
||||
await interaction.reply({ content: `Embed sent to <#${channelId}>!`, ephemeral: true });
|
||||
|
||||
} catch (err) {
|
||||
console.error('Embed modal error:', err);
|
||||
await interaction.reply({ content: 'Failed to send embed. Check permissions and URLs.', ephemeral: true }).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (interaction.isStringSelectMenu()) {
|
||||
if (interaction.customId === 'help_category') {
|
||||
try {
|
||||
const category = interaction.values[0];
|
||||
const { getCategoryEmbed } = require('./commands/help');
|
||||
const embed = getCategoryEmbed(category);
|
||||
await interaction.update({ embeds: [embed] });
|
||||
} catch (err) {
|
||||
console.error('Help select error:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,17 +18,23 @@ module.exports = {
|
|||
.setMaxLength(2000)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('color')
|
||||
.setDescription('Embed color')
|
||||
option.setName('type')
|
||||
.setDescription('Announcement type (changes color and style)')
|
||||
.setRequired(false)
|
||||
.addChoices(
|
||||
{ name: 'Purple (Default)', value: '7c3aed' },
|
||||
{ name: 'Green (Success)', value: '00ff00' },
|
||||
{ name: 'Red (Alert)', value: 'ff0000' },
|
||||
{ name: 'Blue (Info)', value: '3b82f6' },
|
||||
{ name: 'Yellow (Warning)', value: 'fbbf24' }
|
||||
{ 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')
|
||||
|
|
@ -40,16 +46,39 @@ module.exports = {
|
|||
|
||||
const title = interaction.options.getString('title');
|
||||
const message = interaction.options.getString('message');
|
||||
const color = parseInt(interaction.options.getString('color') || '7c3aed', 16);
|
||||
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(color)
|
||||
.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}` })
|
||||
.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;
|
||||
|
||||
|
|
@ -58,7 +87,7 @@ module.exports = {
|
|||
|
||||
const guild = client.guilds.cache.get(guildId);
|
||||
if (!guild) {
|
||||
results.push({ realm, status: 'offline' });
|
||||
results.push({ realm, status: 'offline', emoji: '⚫' });
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -71,13 +100,13 @@ module.exports = {
|
|||
if (announcementChannel && announcementChannel.isTextBased()) {
|
||||
const content = ping ? '@everyone' : null;
|
||||
await announcementChannel.send({ content, embeds: [embed] });
|
||||
results.push({ realm, status: 'sent', channel: announcementChannel.name });
|
||||
results.push({ realm, status: 'sent', channel: announcementChannel.name, emoji: '✅' });
|
||||
} else {
|
||||
results.push({ realm, status: 'no_channel' });
|
||||
results.push({ realm, status: 'no_channel', emoji: '⚠️' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Announce error for ${realm}:`, error);
|
||||
results.push({ realm, status: 'error', error: error.message });
|
||||
results.push({ realm, status: 'error', error: error.message, emoji: '❌' });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,24 +117,39 @@ module.exports = {
|
|||
user_id: interaction.user.id,
|
||||
username: interaction.user.tag,
|
||||
guild_id: interaction.guildId,
|
||||
details: { title, message, results },
|
||||
details: { title, message, type, results },
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Failed to log announcement:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
const resultText = results.map(r => {
|
||||
const emoji = r.status === 'sent' ? '✅' : r.status === 'offline' ? '⚫' : '❌';
|
||||
return `${emoji} **${r.realm}**: ${r.status}${r.channel ? ` (#${r.channel})` : ''}`;
|
||||
}).join('\n');
|
||||
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(0x7c3aed)
|
||||
.setTitle('Announcement Results')
|
||||
.setDescription(resultText || 'No realms configured')
|
||||
.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);
|
||||
}
|
||||
|
|
|
|||
424
aethex-bot/commands/automod.js
Normal file
424
aethex-bot/commands/automod.js
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
const {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
PermissionFlagsBits
|
||||
} = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('automod')
|
||||
.setDescription('Configure auto-moderation settings')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('status')
|
||||
.setDescription('View current auto-mod settings')
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('links')
|
||||
.setDescription('Toggle link filtering')
|
||||
.addBooleanOption(option =>
|
||||
option.setName('enabled')
|
||||
.setDescription('Enable or disable link filtering')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('action')
|
||||
.setDescription('Action to take')
|
||||
.setRequired(false)
|
||||
.addChoices(
|
||||
{ name: 'Delete only', value: 'delete' },
|
||||
{ name: 'Delete + Warn', value: 'warn' },
|
||||
{ name: 'Delete + Timeout (5min)', value: 'timeout' }
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('spam')
|
||||
.setDescription('Toggle spam detection')
|
||||
.addBooleanOption(option =>
|
||||
option.setName('enabled')
|
||||
.setDescription('Enable or disable spam detection')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addIntegerOption(option =>
|
||||
option.setName('threshold')
|
||||
.setDescription('Messages per 5 seconds to trigger')
|
||||
.setRequired(false)
|
||||
.setMinValue(3)
|
||||
.setMaxValue(20)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('badwords')
|
||||
.setDescription('Manage banned words')
|
||||
.addStringOption(option =>
|
||||
option.setName('action')
|
||||
.setDescription('Add or remove words')
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
{ name: 'Add word', value: 'add' },
|
||||
{ name: 'Remove word', value: 'remove' },
|
||||
{ name: 'List words', value: 'list' },
|
||||
{ name: 'Clear all', value: 'clear' }
|
||||
)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('word')
|
||||
.setDescription('Word to add/remove')
|
||||
.setRequired(false)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('invites')
|
||||
.setDescription('Toggle Discord invite filtering')
|
||||
.addBooleanOption(option =>
|
||||
option.setName('enabled')
|
||||
.setDescription('Enable or disable invite filtering')
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('mentions')
|
||||
.setDescription('Toggle mass mention detection')
|
||||
.addBooleanOption(option =>
|
||||
option.setName('enabled')
|
||||
.setDescription('Enable or disable mention spam detection')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addIntegerOption(option =>
|
||||
option.setName('limit')
|
||||
.setDescription('Maximum mentions per message')
|
||||
.setRequired(false)
|
||||
.setMinValue(3)
|
||||
.setMaxValue(50)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('exempt')
|
||||
.setDescription('Exempt a role from auto-mod')
|
||||
.addRoleOption(option =>
|
||||
option.setName('role')
|
||||
.setDescription('Role to exempt')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addBooleanOption(option =>
|
||||
option.setName('exempt')
|
||||
.setDescription('Exempt or un-exempt')
|
||||
.setRequired(true)
|
||||
)
|
||||
),
|
||||
|
||||
async execute(interaction, supabase, client) {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
switch (subcommand) {
|
||||
case 'status':
|
||||
await handleStatus(interaction, supabase);
|
||||
break;
|
||||
case 'links':
|
||||
await handleLinks(interaction, supabase, client);
|
||||
break;
|
||||
case 'spam':
|
||||
await handleSpam(interaction, supabase, client);
|
||||
break;
|
||||
case 'badwords':
|
||||
await handleBadwords(interaction, supabase, client);
|
||||
break;
|
||||
case 'invites':
|
||||
await handleInvites(interaction, supabase, client);
|
||||
break;
|
||||
case 'mentions':
|
||||
await handleMentions(interaction, supabase, client);
|
||||
break;
|
||||
case 'exempt':
|
||||
await handleExempt(interaction, supabase, client);
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async function getAutomodConfig(guildId, supabase) {
|
||||
if (!supabase) return getDefaultConfig();
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('automod_config')
|
||||
.select('*')
|
||||
.eq('guild_id', guildId)
|
||||
.single();
|
||||
|
||||
if (error || !data) return getDefaultConfig();
|
||||
return data;
|
||||
} catch {
|
||||
return getDefaultConfig();
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultConfig() {
|
||||
return {
|
||||
links_enabled: false,
|
||||
links_action: 'delete',
|
||||
spam_enabled: false,
|
||||
spam_threshold: 5,
|
||||
badwords: [],
|
||||
invites_enabled: false,
|
||||
mentions_enabled: false,
|
||||
mentions_limit: 5,
|
||||
exempt_roles: []
|
||||
};
|
||||
}
|
||||
|
||||
async function saveAutomodConfig(guildId, config, supabase) {
|
||||
if (!supabase) return;
|
||||
|
||||
try {
|
||||
await supabase.from('automod_config').upsert({
|
||||
guild_id: guildId,
|
||||
...config,
|
||||
updated_at: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Save automod config error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStatus(interaction, supabase) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const config = await getAutomodConfig(interaction.guildId, supabase);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x7c3aed)
|
||||
.setTitle('🛡️ Auto-Moderation Settings')
|
||||
.addFields(
|
||||
{
|
||||
name: '🔗 Link Filter',
|
||||
value: config.links_enabled ? `✅ Enabled (${config.links_action})` : '❌ Disabled',
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '📨 Spam Detection',
|
||||
value: config.spam_enabled ? `✅ Enabled (${config.spam_threshold} msg/5s)` : '❌ Disabled',
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '🚫 Bad Words',
|
||||
value: config.badwords?.length > 0 ? `✅ ${config.badwords.length} words` : '❌ None set',
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '📩 Invite Filter',
|
||||
value: config.invites_enabled ? '✅ Enabled' : '❌ Disabled',
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '📢 Mass Mentions',
|
||||
value: config.mentions_enabled ? `✅ Enabled (max ${config.mentions_limit})` : '❌ Disabled',
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '🎭 Exempt Roles',
|
||||
value: config.exempt_roles?.length > 0
|
||||
? config.exempt_roles.map(r => `<@&${r}>`).join(', ')
|
||||
: 'None',
|
||||
inline: true
|
||||
}
|
||||
)
|
||||
.setFooter({ text: 'Use /automod [setting] to configure' })
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
async function handleLinks(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const enabled = interaction.options.getBoolean('enabled');
|
||||
const action = interaction.options.getString('action') || 'delete';
|
||||
|
||||
const config = await getAutomodConfig(interaction.guildId, supabase);
|
||||
config.links_enabled = enabled;
|
||||
config.links_action = action;
|
||||
|
||||
await saveAutomodConfig(interaction.guildId, config, supabase);
|
||||
|
||||
client.automodConfig = client.automodConfig || new Map();
|
||||
client.automodConfig.set(interaction.guildId, config);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(enabled ? 0x22c55e : 0xef4444)
|
||||
.setTitle(enabled ? '✅ Link Filter Enabled' : '❌ Link Filter Disabled')
|
||||
.setDescription(enabled ? `Links will be ${action === 'delete' ? 'deleted' : action === 'warn' ? 'deleted and user warned' : 'deleted and user timed out'}` : 'Links will no longer be filtered')
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
async function handleSpam(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const enabled = interaction.options.getBoolean('enabled');
|
||||
const threshold = interaction.options.getInteger('threshold') || 5;
|
||||
|
||||
const config = await getAutomodConfig(interaction.guildId, supabase);
|
||||
config.spam_enabled = enabled;
|
||||
config.spam_threshold = threshold;
|
||||
|
||||
await saveAutomodConfig(interaction.guildId, config, supabase);
|
||||
|
||||
client.automodConfig = client.automodConfig || new Map();
|
||||
client.automodConfig.set(interaction.guildId, config);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(enabled ? 0x22c55e : 0xef4444)
|
||||
.setTitle(enabled ? '✅ Spam Detection Enabled' : '❌ Spam Detection Disabled')
|
||||
.setDescription(enabled ? `Spam threshold set to ${threshold} messages per 5 seconds` : 'Spam will no longer be detected')
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
async function handleBadwords(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const action = interaction.options.getString('action');
|
||||
const word = interaction.options.getString('word')?.toLowerCase();
|
||||
|
||||
const config = await getAutomodConfig(interaction.guildId, supabase);
|
||||
config.badwords = config.badwords || [];
|
||||
|
||||
let embed;
|
||||
|
||||
switch (action) {
|
||||
case 'add':
|
||||
if (!word) {
|
||||
return interaction.editReply({ content: 'Please provide a word to add.' });
|
||||
}
|
||||
if (!config.badwords.includes(word)) {
|
||||
config.badwords.push(word);
|
||||
}
|
||||
embed = new EmbedBuilder()
|
||||
.setColor(0x22c55e)
|
||||
.setTitle('✅ Word Added')
|
||||
.setDescription(`Added "${word}" to the filter list.`)
|
||||
.setTimestamp();
|
||||
break;
|
||||
|
||||
case 'remove':
|
||||
if (!word) {
|
||||
return interaction.editReply({ content: 'Please provide a word to remove.' });
|
||||
}
|
||||
config.badwords = config.badwords.filter(w => w !== word);
|
||||
embed = new EmbedBuilder()
|
||||
.setColor(0x22c55e)
|
||||
.setTitle('✅ Word Removed')
|
||||
.setDescription(`Removed "${word}" from the filter list.`)
|
||||
.setTimestamp();
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
embed = new EmbedBuilder()
|
||||
.setColor(0x7c3aed)
|
||||
.setTitle('🚫 Banned Words')
|
||||
.setDescription(config.badwords.length > 0
|
||||
? config.badwords.map(w => `\`${w}\``).join(', ')
|
||||
: 'No words in the filter list.')
|
||||
.setTimestamp();
|
||||
break;
|
||||
|
||||
case 'clear':
|
||||
config.badwords = [];
|
||||
embed = new EmbedBuilder()
|
||||
.setColor(0x22c55e)
|
||||
.setTitle('✅ List Cleared')
|
||||
.setDescription('All banned words have been removed.')
|
||||
.setTimestamp();
|
||||
break;
|
||||
}
|
||||
|
||||
await saveAutomodConfig(interaction.guildId, config, supabase);
|
||||
|
||||
client.automodConfig = client.automodConfig || new Map();
|
||||
client.automodConfig.set(interaction.guildId, config);
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
async function handleInvites(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const enabled = interaction.options.getBoolean('enabled');
|
||||
|
||||
const config = await getAutomodConfig(interaction.guildId, supabase);
|
||||
config.invites_enabled = enabled;
|
||||
|
||||
await saveAutomodConfig(interaction.guildId, config, supabase);
|
||||
|
||||
client.automodConfig = client.automodConfig || new Map();
|
||||
client.automodConfig.set(interaction.guildId, config);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(enabled ? 0x22c55e : 0xef4444)
|
||||
.setTitle(enabled ? '✅ Invite Filter Enabled' : '❌ Invite Filter Disabled')
|
||||
.setDescription(enabled ? 'Discord invites will be automatically deleted' : 'Discord invites will no longer be filtered')
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
async function handleMentions(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const enabled = interaction.options.getBoolean('enabled');
|
||||
const limit = interaction.options.getInteger('limit') || 5;
|
||||
|
||||
const config = await getAutomodConfig(interaction.guildId, supabase);
|
||||
config.mentions_enabled = enabled;
|
||||
config.mentions_limit = limit;
|
||||
|
||||
await saveAutomodConfig(interaction.guildId, config, supabase);
|
||||
|
||||
client.automodConfig = client.automodConfig || new Map();
|
||||
client.automodConfig.set(interaction.guildId, config);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(enabled ? 0x22c55e : 0xef4444)
|
||||
.setTitle(enabled ? '✅ Mass Mention Detection Enabled' : '❌ Mass Mention Detection Disabled')
|
||||
.setDescription(enabled ? `Messages with more than ${limit} mentions will be deleted` : 'Mass mentions will no longer be filtered')
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
async function handleExempt(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const role = interaction.options.getRole('role');
|
||||
const exempt = interaction.options.getBoolean('exempt');
|
||||
|
||||
const config = await getAutomodConfig(interaction.guildId, supabase);
|
||||
config.exempt_roles = config.exempt_roles || [];
|
||||
|
||||
if (exempt) {
|
||||
if (!config.exempt_roles.includes(role.id)) {
|
||||
config.exempt_roles.push(role.id);
|
||||
}
|
||||
} else {
|
||||
config.exempt_roles = config.exempt_roles.filter(r => r !== role.id);
|
||||
}
|
||||
|
||||
await saveAutomodConfig(interaction.guildId, config, supabase);
|
||||
|
||||
client.automodConfig = client.automodConfig || new Map();
|
||||
client.automodConfig.set(interaction.guildId, config);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x22c55e)
|
||||
.setTitle(exempt ? '✅ Role Exempted' : '✅ Role Un-exempted')
|
||||
.setDescription(exempt
|
||||
? `${role} is now exempt from auto-moderation`
|
||||
: `${role} is no longer exempt from auto-moderation`)
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
97
aethex-bot/commands/embed.js
Normal file
97
aethex-bot/commands/embed.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
const {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
PermissionFlagsBits,
|
||||
ModalBuilder,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
ActionRowBuilder,
|
||||
ChannelType
|
||||
} = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('embed')
|
||||
.setDescription('Create a custom embed message')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
|
||||
.addChannelOption(option =>
|
||||
option.setName('channel')
|
||||
.setDescription('Channel to send the embed to')
|
||||
.setRequired(true)
|
||||
.addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('color')
|
||||
.setDescription('Embed color')
|
||||
.setRequired(false)
|
||||
.addChoices(
|
||||
{ name: '🟣 Purple (Default)', value: '7c3aed' },
|
||||
{ name: '🟢 Green (Success)', value: '22c55e' },
|
||||
{ name: '🔴 Red (Alert)', value: 'ef4444' },
|
||||
{ name: '🔵 Blue (Info)', value: '3b82f6' },
|
||||
{ name: '🟡 Yellow (Warning)', value: 'eab308' },
|
||||
{ name: '🟠 Orange (Highlight)', value: 'f97316' },
|
||||
{ name: '⚪ White', value: 'ffffff' },
|
||||
{ name: '⚫ Black', value: '1f2937' },
|
||||
{ name: '🩷 Pink', value: 'ec4899' },
|
||||
{ name: '🩵 Cyan', value: '06b6d4' }
|
||||
)
|
||||
),
|
||||
|
||||
async execute(interaction, supabase, client) {
|
||||
const channel = interaction.options.getChannel('channel');
|
||||
const color = interaction.options.getString('color') || '7c3aed';
|
||||
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId(`embed_modal_${channel.id}_${color}`)
|
||||
.setTitle('Create Custom Embed');
|
||||
|
||||
const titleInput = new TextInputBuilder()
|
||||
.setCustomId('embed_title')
|
||||
.setLabel('Title')
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('Enter embed title')
|
||||
.setMaxLength(256)
|
||||
.setRequired(true);
|
||||
|
||||
const descriptionInput = new TextInputBuilder()
|
||||
.setCustomId('embed_description')
|
||||
.setLabel('Description')
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setPlaceholder('Enter embed description (supports markdown)')
|
||||
.setMaxLength(4000)
|
||||
.setRequired(true);
|
||||
|
||||
const imageInput = new TextInputBuilder()
|
||||
.setCustomId('embed_image')
|
||||
.setLabel('Image URL (optional)')
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('https://example.com/image.png')
|
||||
.setRequired(false);
|
||||
|
||||
const thumbnailInput = new TextInputBuilder()
|
||||
.setCustomId('embed_thumbnail')
|
||||
.setLabel('Thumbnail URL (optional)')
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('https://example.com/thumbnail.png')
|
||||
.setRequired(false);
|
||||
|
||||
const footerInput = new TextInputBuilder()
|
||||
.setCustomId('embed_footer')
|
||||
.setLabel('Footer text (optional)')
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('Enter footer text')
|
||||
.setMaxLength(2048)
|
||||
.setRequired(false);
|
||||
|
||||
const row1 = new ActionRowBuilder().addComponents(titleInput);
|
||||
const row2 = new ActionRowBuilder().addComponents(descriptionInput);
|
||||
const row3 = new ActionRowBuilder().addComponents(imageInput);
|
||||
const row4 = new ActionRowBuilder().addComponents(thumbnailInput);
|
||||
const row5 = new ActionRowBuilder().addComponents(footerInput);
|
||||
|
||||
modal.addComponents(row1, row2, row3, row4, row5);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
},
|
||||
};
|
||||
326
aethex-bot/commands/giveaway.js
Normal file
326
aethex-bot/commands/giveaway.js
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
const {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
PermissionFlagsBits,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle
|
||||
} = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('giveaway')
|
||||
.setDescription('Create and manage giveaways')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('start')
|
||||
.setDescription('Start a new giveaway')
|
||||
.addStringOption(option =>
|
||||
option.setName('prize')
|
||||
.setDescription('What are you giving away?')
|
||||
.setRequired(true)
|
||||
.setMaxLength(256)
|
||||
)
|
||||
.addIntegerOption(option =>
|
||||
option.setName('duration')
|
||||
.setDescription('Duration in minutes')
|
||||
.setRequired(true)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(10080)
|
||||
)
|
||||
.addIntegerOption(option =>
|
||||
option.setName('winners')
|
||||
.setDescription('Number of winners')
|
||||
.setRequired(false)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(20)
|
||||
)
|
||||
.addRoleOption(option =>
|
||||
option.setName('required_role')
|
||||
.setDescription('Role required to enter (optional)')
|
||||
.setRequired(false)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('end')
|
||||
.setDescription('End a giveaway early')
|
||||
.addStringOption(option =>
|
||||
option.setName('message_id')
|
||||
.setDescription('Message ID of the giveaway')
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('reroll')
|
||||
.setDescription('Reroll a giveaway winner')
|
||||
.addStringOption(option =>
|
||||
option.setName('message_id')
|
||||
.setDescription('Message ID of the giveaway')
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('list')
|
||||
.setDescription('List active giveaways')
|
||||
),
|
||||
|
||||
async execute(interaction, supabase, client) {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
if (subcommand === 'start') {
|
||||
await handleStart(interaction, supabase, client);
|
||||
} else if (subcommand === 'end') {
|
||||
await handleEnd(interaction, supabase, client);
|
||||
} else if (subcommand === 'reroll') {
|
||||
await handleReroll(interaction, supabase, client);
|
||||
} else if (subcommand === 'list') {
|
||||
await handleList(interaction, supabase);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async function handleStart(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const prize = interaction.options.getString('prize');
|
||||
const duration = interaction.options.getInteger('duration');
|
||||
const winnersCount = interaction.options.getInteger('winners') || 1;
|
||||
const requiredRole = interaction.options.getRole('required_role');
|
||||
|
||||
const endTime = Date.now() + (duration * 60 * 1000);
|
||||
const endTimestamp = Math.floor(endTime / 1000);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xfbbf24)
|
||||
.setTitle('🎉 GIVEAWAY 🎉')
|
||||
.setDescription(`**${prize}**\n\nClick the button below to enter!`)
|
||||
.addFields(
|
||||
{ name: '⏰ Ends', value: `<t:${endTimestamp}:R>`, inline: true },
|
||||
{ name: '🏆 Winners', value: `${winnersCount}`, inline: true },
|
||||
{ name: '👥 Entries', value: '0', inline: true }
|
||||
)
|
||||
.setFooter({ text: `Hosted by ${interaction.user.tag}` })
|
||||
.setTimestamp(new Date(endTime));
|
||||
|
||||
if (requiredRole) {
|
||||
embed.addFields({ name: '🔒 Required Role', value: `${requiredRole}`, inline: false });
|
||||
}
|
||||
|
||||
const button = new ButtonBuilder()
|
||||
.setCustomId('giveaway_enter')
|
||||
.setLabel('🎁 Enter Giveaway')
|
||||
.setStyle(ButtonStyle.Success);
|
||||
|
||||
const row = new ActionRowBuilder().addComponents(button);
|
||||
|
||||
try {
|
||||
const message = await interaction.channel.send({ embeds: [embed], components: [row] });
|
||||
|
||||
if (supabase) {
|
||||
await supabase.from('giveaways').insert({
|
||||
message_id: message.id,
|
||||
channel_id: interaction.channelId,
|
||||
guild_id: interaction.guildId,
|
||||
prize: prize,
|
||||
winners_count: winnersCount,
|
||||
required_role: requiredRole?.id || null,
|
||||
host_id: interaction.user.id,
|
||||
end_time: new Date(endTime).toISOString(),
|
||||
entries: [],
|
||||
status: 'active'
|
||||
});
|
||||
}
|
||||
|
||||
client.giveaways = client.giveaways || new Map();
|
||||
client.giveaways.set(message.id, {
|
||||
channelId: interaction.channelId,
|
||||
endTime: endTime,
|
||||
prize: prize,
|
||||
winnersCount: winnersCount,
|
||||
requiredRole: requiredRole?.id,
|
||||
entries: []
|
||||
});
|
||||
|
||||
setTimeout(() => endGiveaway(message.id, client, supabase), duration * 60 * 1000);
|
||||
|
||||
const successEmbed = new EmbedBuilder()
|
||||
.setColor(0x22c55e)
|
||||
.setTitle('✅ Giveaway Started!')
|
||||
.setDescription(`Giveaway for **${prize}** has started!\n\nEnds <t:${endTimestamp}:R>`)
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [successEmbed] });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Giveaway start error:', error);
|
||||
await interaction.editReply({ content: 'Failed to start giveaway.' });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEnd(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const messageId = interaction.options.getString('message_id');
|
||||
|
||||
try {
|
||||
await endGiveaway(messageId, client, supabase, interaction.channel);
|
||||
await interaction.editReply({ content: '✅ Giveaway ended!' });
|
||||
} catch (error) {
|
||||
console.error('Giveaway end error:', error);
|
||||
await interaction.editReply({ content: 'Failed to end giveaway. Check the message ID.' });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReroll(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const messageId = interaction.options.getString('message_id');
|
||||
|
||||
try {
|
||||
if (!supabase) {
|
||||
return interaction.editReply({ content: 'Database not available.' });
|
||||
}
|
||||
|
||||
const { data: giveaway, error } = await supabase
|
||||
.from('giveaways')
|
||||
.select('*')
|
||||
.eq('message_id', messageId)
|
||||
.single();
|
||||
|
||||
if (error || !giveaway) {
|
||||
return interaction.editReply({ content: 'Giveaway not found.' });
|
||||
}
|
||||
|
||||
const entries = giveaway.entries || [];
|
||||
if (entries.length === 0) {
|
||||
return interaction.editReply({ content: 'No entries to reroll.' });
|
||||
}
|
||||
|
||||
const winner = entries[Math.floor(Math.random() * entries.length)];
|
||||
|
||||
const channel = await client.channels.fetch(giveaway.channel_id);
|
||||
await channel.send({
|
||||
content: `🎉 New winner: <@${winner}>!\nPrize: **${giveaway.prize}**`
|
||||
});
|
||||
|
||||
await interaction.editReply({ content: '✅ Rerolled winner!' });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Giveaway reroll error:', error);
|
||||
await interaction.editReply({ content: 'Failed to reroll.' });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleList(interaction, supabase) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
if (!supabase) {
|
||||
return interaction.editReply({ content: 'Database not available.' });
|
||||
}
|
||||
|
||||
try {
|
||||
const { data: giveaways, error } = await supabase
|
||||
.from('giveaways')
|
||||
.select('*')
|
||||
.eq('guild_id', interaction.guildId)
|
||||
.eq('status', 'active')
|
||||
.order('end_time', { ascending: true });
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
if (!giveaways || giveaways.length === 0) {
|
||||
return interaction.editReply({ content: 'No active giveaways.' });
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xfbbf24)
|
||||
.setTitle('🎉 Active Giveaways')
|
||||
.setDescription(giveaways.map(g => {
|
||||
const endTimestamp = Math.floor(new Date(g.end_time).getTime() / 1000);
|
||||
return `**${g.prize}**\nEnds: <t:${endTimestamp}:R>\nEntries: ${g.entries?.length || 0}\nMessage ID: \`${g.message_id}\``;
|
||||
}).join('\n\n'))
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Giveaway list error:', error);
|
||||
await interaction.editReply({ content: 'Failed to fetch giveaways.' });
|
||||
}
|
||||
}
|
||||
|
||||
async function endGiveaway(messageId, client, supabase, channel = null) {
|
||||
try {
|
||||
let giveawayData = client.giveaways?.get(messageId);
|
||||
let entries = giveawayData?.entries || [];
|
||||
let prize = giveawayData?.prize || 'Unknown Prize';
|
||||
let winnersCount = giveawayData?.winnersCount || 1;
|
||||
let channelId = giveawayData?.channelId;
|
||||
|
||||
if (supabase) {
|
||||
const { data } = await supabase
|
||||
.from('giveaways')
|
||||
.select('*')
|
||||
.eq('message_id', messageId)
|
||||
.single();
|
||||
|
||||
if (data) {
|
||||
entries = data.entries || [];
|
||||
prize = data.prize;
|
||||
winnersCount = data.winners_count;
|
||||
channelId = data.channel_id;
|
||||
|
||||
await supabase
|
||||
.from('giveaways')
|
||||
.update({ status: 'ended' })
|
||||
.eq('message_id', messageId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!channelId) return;
|
||||
|
||||
const giveawayChannel = channel || await client.channels.fetch(channelId);
|
||||
const message = await giveawayChannel.messages.fetch(messageId).catch(() => null);
|
||||
|
||||
if (!message) return;
|
||||
|
||||
const winners = [];
|
||||
const entriesCopy = [...entries];
|
||||
|
||||
for (let i = 0; i < winnersCount && entriesCopy.length > 0; i++) {
|
||||
const index = Math.floor(Math.random() * entriesCopy.length);
|
||||
winners.push(entriesCopy.splice(index, 1)[0]);
|
||||
}
|
||||
|
||||
const endedEmbed = EmbedBuilder.from(message.embeds[0])
|
||||
.setColor(0x6b7280)
|
||||
.setTitle('🎉 GIVEAWAY ENDED 🎉')
|
||||
.setFields(
|
||||
{ name: '🏆 Winner(s)', value: winners.length > 0 ? winners.map(w => `<@${w}>`).join(', ') : 'No valid entries', inline: false },
|
||||
{ name: '👥 Total Entries', value: `${entries.length}`, inline: true }
|
||||
);
|
||||
|
||||
const disabledButton = new ButtonBuilder()
|
||||
.setCustomId('giveaway_ended')
|
||||
.setLabel('Giveaway Ended')
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setDisabled(true);
|
||||
|
||||
const row = new ActionRowBuilder().addComponents(disabledButton);
|
||||
|
||||
await message.edit({ embeds: [endedEmbed], components: [row] });
|
||||
|
||||
if (winners.length > 0) {
|
||||
await giveawayChannel.send({
|
||||
content: `🎉 Congratulations ${winners.map(w => `<@${w}>`).join(', ')}! You won **${prize}**!`
|
||||
});
|
||||
}
|
||||
|
||||
client.giveaways?.delete(messageId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('End giveaway error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.endGiveaway = endGiveaway;
|
||||
|
|
@ -1,55 +1,205 @@
|
|||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder } = require("discord.js");
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("help")
|
||||
.setDescription("View all AeThex bot commands and features"),
|
||||
.setDescription("View all AeThex bot commands and features")
|
||||
.addStringOption(option =>
|
||||
option.setName('category')
|
||||
.setDescription('Specific category to view')
|
||||
.setRequired(false)
|
||||
.addChoices(
|
||||
{ name: '🔗 Account', value: 'account' },
|
||||
{ name: '⚔️ Realms', value: 'realms' },
|
||||
{ name: '📊 Community', value: 'community' },
|
||||
{ name: '⭐ Leveling', value: 'leveling' },
|
||||
{ name: '🛡️ Moderation', value: 'moderation' },
|
||||
{ name: '🔧 Utility', value: 'utility' },
|
||||
{ name: '⚙️ Admin', value: 'admin' }
|
||||
)
|
||||
),
|
||||
|
||||
async execute(interaction) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x7289da)
|
||||
.setTitle("🤖 AeThex Bot Commands")
|
||||
.setDescription("Here are all the commands you can use with the AeThex Discord bot.")
|
||||
const category = interaction.options.getString('category');
|
||||
|
||||
if (category) {
|
||||
const embed = getCategoryEmbed(category);
|
||||
return interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
|
||||
const mainEmbed = new EmbedBuilder()
|
||||
.setColor(0x7c3aed)
|
||||
.setAuthor({
|
||||
name: 'AeThex Bot Help',
|
||||
iconURL: interaction.client.user.displayAvatarURL()
|
||||
})
|
||||
.setDescription('Welcome to AeThex Bot! Select a category below to view commands.')
|
||||
.addFields(
|
||||
{
|
||||
name: "🔗 Account Linking",
|
||||
value: [
|
||||
"`/verify` - Link your Discord account to AeThex",
|
||||
"`/unlink` - Disconnect your Discord from AeThex",
|
||||
"`/profile` - View your linked AeThex profile",
|
||||
].join("\n"),
|
||||
name: "🔗 Account",
|
||||
value: "`/verify` `/unlink` `/profile`",
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "⚔️ Realm Management",
|
||||
value: [
|
||||
"`/set-realm` - Choose your primary realm (Labs, GameForge, Corp, Foundation, Dev-Link)",
|
||||
"`/verify-role` - Check your assigned Discord roles",
|
||||
].join("\n"),
|
||||
name: "⚔️ Realms",
|
||||
value: "`/set-realm` `/verify-role` `/refresh-roles`",
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "📊 Community",
|
||||
value: [
|
||||
"`/stats` - View your AeThex statistics and activity",
|
||||
"`/leaderboard` - See the top contributors",
|
||||
"`/post` - Create a post in the AeThex community feed",
|
||||
].join("\n"),
|
||||
value: "`/stats` `/leaderboard` `/post` `/poll`",
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "ℹ️ Information",
|
||||
value: "`/help` - Show this help message",
|
||||
name: "⭐ Leveling",
|
||||
value: "`/rank` `/daily` `/badges`",
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "🛡️ Moderation",
|
||||
value: "`/warn` `/kick` `/ban` `/timeout` `/modlog`",
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "🔧 Utility",
|
||||
value: "`/userinfo` `/serverinfo` `/avatar`",
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "⚙️ Admin",
|
||||
value: "`/config` `/announce` `/embed` `/rolepanel`",
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "🎉 Fun",
|
||||
value: "`/giveaway` `/schedule`",
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "🛡️ Auto-Mod",
|
||||
value: "`/automod`",
|
||||
inline: true,
|
||||
}
|
||||
)
|
||||
.addFields({
|
||||
name: "🔗 Quick Links",
|
||||
value: [
|
||||
"[AeThex Platform](https://aethex.dev)",
|
||||
"[Creator Directory](https://aethex.dev/creators)",
|
||||
"[Community Feed](https://aethex.dev/community/feed)",
|
||||
].join(" | "),
|
||||
value: "[AeThex Platform](https://aethex.dev) • [Creator Directory](https://aethex.dev/creators) • [Community Feed](https://aethex.dev/community/feed)",
|
||||
inline: false,
|
||||
})
|
||||
.setFooter({
|
||||
text: "Use /help [category] for detailed commands • AeThex Bot",
|
||||
iconURL: interaction.client.user.displayAvatarURL()
|
||||
})
|
||||
.setFooter({ text: "AeThex | Build. Create. Connect." })
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
const selectMenu = new StringSelectMenuBuilder()
|
||||
.setCustomId('help_category')
|
||||
.setPlaceholder('Select a category for details...')
|
||||
.addOptions([
|
||||
{ label: 'Account', description: 'Link and manage your account', emoji: '🔗', value: 'account' },
|
||||
{ label: 'Realms', description: 'Realm selection and roles', emoji: '⚔️', value: 'realms' },
|
||||
{ label: 'Community', description: 'Community features', emoji: '📊', value: 'community' },
|
||||
{ label: 'Leveling', description: 'XP and leveling system', emoji: '⭐', value: 'leveling' },
|
||||
{ label: 'Moderation', description: 'Moderation tools', emoji: '🛡️', value: 'moderation' },
|
||||
{ label: 'Utility', description: 'Utility commands', emoji: '🔧', value: 'utility' },
|
||||
{ label: 'Admin', description: 'Admin and config', emoji: '⚙️', value: 'admin' },
|
||||
]);
|
||||
|
||||
const row = new ActionRowBuilder().addComponents(selectMenu);
|
||||
|
||||
await interaction.reply({ embeds: [mainEmbed], components: [row], ephemeral: true });
|
||||
},
|
||||
};
|
||||
|
||||
function getCategoryEmbed(category) {
|
||||
const categories = {
|
||||
account: {
|
||||
title: '🔗 Account Commands',
|
||||
color: 0x3b82f6,
|
||||
commands: [
|
||||
{ name: '/verify', desc: 'Link your Discord account to AeThex' },
|
||||
{ name: '/unlink', desc: 'Disconnect your Discord from AeThex' },
|
||||
{ name: '/profile [@user]', desc: 'View your or another user\'s AeThex profile' },
|
||||
]
|
||||
},
|
||||
realms: {
|
||||
title: '⚔️ Realm Commands',
|
||||
color: 0xf97316,
|
||||
commands: [
|
||||
{ name: '/set-realm', desc: 'Choose your primary realm (Labs, GameForge, Corp, Foundation, Dev-Link)' },
|
||||
{ name: '/verify-role', desc: 'Check your assigned Discord roles' },
|
||||
{ name: '/refresh-roles', desc: 'Sync your roles based on AeThex profile' },
|
||||
]
|
||||
},
|
||||
community: {
|
||||
title: '📊 Community Commands',
|
||||
color: 0x22c55e,
|
||||
commands: [
|
||||
{ name: '/stats', desc: 'View your AeThex statistics and activity' },
|
||||
{ name: '/leaderboard [category]', desc: 'See the top contributors' },
|
||||
{ name: '/post', desc: 'Create a post in the AeThex community feed' },
|
||||
{ name: '/poll', desc: 'Create a community poll' },
|
||||
{ name: '/studio [@user]', desc: 'View AeThex Studio profile' },
|
||||
{ name: '/foundation [@user]', desc: 'View Foundation contributions' },
|
||||
]
|
||||
},
|
||||
leveling: {
|
||||
title: '⭐ Leveling Commands',
|
||||
color: 0xfbbf24,
|
||||
commands: [
|
||||
{ name: '/rank [@user]', desc: 'View your level and unified XP' },
|
||||
{ name: '/daily', desc: 'Claim your daily XP bonus (+50 base + streak)' },
|
||||
{ name: '/badges', desc: 'View earned badges across platforms' },
|
||||
]
|
||||
},
|
||||
moderation: {
|
||||
title: '🛡️ Moderation Commands',
|
||||
color: 0xef4444,
|
||||
commands: [
|
||||
{ name: '/warn @user [reason]', desc: 'Warn a user' },
|
||||
{ name: '/kick @user [reason]', desc: 'Kick a user from the server' },
|
||||
{ name: '/ban @user [reason]', desc: 'Ban a user from the server' },
|
||||
{ name: '/timeout @user [minutes] [reason]', desc: 'Timeout a user' },
|
||||
{ name: '/modlog @user', desc: 'View a user\'s moderation history' },
|
||||
{ name: '/auditlog', desc: 'View admin action history' },
|
||||
]
|
||||
},
|
||||
utility: {
|
||||
title: '🔧 Utility Commands',
|
||||
color: 0x8b5cf6,
|
||||
commands: [
|
||||
{ name: '/userinfo [@user]', desc: 'View detailed user information' },
|
||||
{ name: '/serverinfo', desc: 'View server statistics and info' },
|
||||
{ name: '/avatar [@user]', desc: 'Get a user\'s avatar' },
|
||||
{ name: '/status', desc: 'View network status' },
|
||||
]
|
||||
},
|
||||
admin: {
|
||||
title: '⚙️ Admin Commands',
|
||||
color: 0x6b7280,
|
||||
commands: [
|
||||
{ name: '/config', desc: 'View and edit server configuration' },
|
||||
{ name: '/announce', desc: 'Send cross-server announcements' },
|
||||
{ name: '/embed', desc: 'Create custom embed messages' },
|
||||
{ name: '/rolepanel', desc: 'Create role button panels' },
|
||||
{ name: '/giveaway', desc: 'Create and manage giveaways' },
|
||||
{ name: '/schedule', desc: 'Schedule messages for later' },
|
||||
{ name: '/automod', desc: 'Configure auto-moderation' },
|
||||
{ name: '/admin', desc: 'Bot administration commands' },
|
||||
{ name: '/federation', desc: 'Manage cross-server role sync' },
|
||||
{ name: '/ticket', desc: 'Manage support tickets' },
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const cat = categories[category] || categories.account;
|
||||
|
||||
return new EmbedBuilder()
|
||||
.setColor(cat.color)
|
||||
.setTitle(cat.title)
|
||||
.setDescription(cat.commands.map(c => `**${c.name}**\n${c.desc}`).join('\n\n'))
|
||||
.setFooter({ text: 'Use /help to see all categories' })
|
||||
.setTimestamp();
|
||||
}
|
||||
|
||||
module.exports.getCategoryEmbed = getCategoryEmbed;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ module.exports = {
|
|||
.addChoices(
|
||||
{ name: "🔥 Most Active (Posts)", value: "posts" },
|
||||
{ name: "❤️ Most Liked", value: "likes" },
|
||||
{ name: "🎨 Top Creators", value: "creators" }
|
||||
{ name: "🎨 Top Creators", value: "creators" },
|
||||
{ name: "⭐ XP Leaders", value: "xp" }
|
||||
)
|
||||
),
|
||||
|
||||
|
|
@ -23,15 +24,38 @@ module.exports = {
|
|||
await interaction.deferReply();
|
||||
|
||||
try {
|
||||
const category = interaction.options.getString("category") || "posts";
|
||||
const category = interaction.options.getString("category") || "xp";
|
||||
|
||||
let leaderboardData = [];
|
||||
let title = "";
|
||||
let emoji = "";
|
||||
let color = 0x7c3aed;
|
||||
|
||||
if (category === "posts") {
|
||||
if (category === "xp") {
|
||||
title = "XP Leaderboard";
|
||||
emoji = "⭐";
|
||||
color = 0xfbbf24;
|
||||
|
||||
const { data: profiles } = await supabase
|
||||
.from("user_profiles")
|
||||
.select("id, username, full_name, avatar_url, xp")
|
||||
.not("xp", "is", null)
|
||||
.order("xp", { ascending: false })
|
||||
.limit(10);
|
||||
|
||||
for (const profile of profiles || []) {
|
||||
const level = Math.floor(Math.sqrt((profile.xp || 0) / 100));
|
||||
leaderboardData.push({
|
||||
name: profile.full_name || profile.username || "Anonymous",
|
||||
value: `Level ${level} • ${(profile.xp || 0).toLocaleString()} XP`,
|
||||
username: profile.username,
|
||||
xp: profile.xp || 0
|
||||
});
|
||||
}
|
||||
} else if (category === "posts") {
|
||||
title = "Most Active Posters";
|
||||
emoji = "🔥";
|
||||
color = 0xef4444;
|
||||
|
||||
const { data: posts } = await supabase
|
||||
.from("community_posts")
|
||||
|
|
@ -65,6 +89,7 @@ module.exports = {
|
|||
} else if (category === "likes") {
|
||||
title = "Most Liked Users";
|
||||
emoji = "❤️";
|
||||
color = 0xec4899;
|
||||
|
||||
const { data: posts } = await supabase
|
||||
.from("community_posts")
|
||||
|
|
@ -92,7 +117,7 @@ module.exports = {
|
|||
if (profile) {
|
||||
leaderboardData.push({
|
||||
name: profile.full_name || profile.username || "Anonymous",
|
||||
value: `${count} likes received`,
|
||||
value: `${count.toLocaleString()} likes`,
|
||||
username: profile.username,
|
||||
});
|
||||
}
|
||||
|
|
@ -100,6 +125,7 @@ module.exports = {
|
|||
} else if (category === "creators") {
|
||||
title = "Top Creators";
|
||||
emoji = "🎨";
|
||||
color = 0x8b5cf6;
|
||||
|
||||
const { data: creators } = await supabase
|
||||
.from("aethex_creators")
|
||||
|
|
@ -128,22 +154,36 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
|
||||
const medals = ['🥇', '🥈', '🥉'];
|
||||
|
||||
const description = leaderboardData.length > 0
|
||||
? leaderboardData
|
||||
.map((user, index) => {
|
||||
const medal = index < 3 ? medals[index] : `\`${index + 1}.\``;
|
||||
return `${medal} **${user.name}**\n └ ${user.value}`;
|
||||
})
|
||||
.join("\n\n")
|
||||
: "No data available yet. Be the first to contribute!";
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x7289da)
|
||||
.setColor(color)
|
||||
.setTitle(`${emoji} ${title}`)
|
||||
.setDescription(
|
||||
leaderboardData.length > 0
|
||||
? leaderboardData
|
||||
.map(
|
||||
(user, index) =>
|
||||
`**${index + 1}.** ${user.name} - ${user.value}`
|
||||
)
|
||||
.join("\n")
|
||||
: "No data available yet. Be the first to contribute!"
|
||||
)
|
||||
.setFooter({ text: "AeThex Leaderboard | Updated in real-time" })
|
||||
.setDescription(description)
|
||||
.setThumbnail(interaction.guild.iconURL({ size: 128 }))
|
||||
.setFooter({
|
||||
text: `${interaction.guild.name} • Updated in real-time`,
|
||||
iconURL: interaction.guild.iconURL({ size: 32 })
|
||||
})
|
||||
.setTimestamp();
|
||||
|
||||
if (leaderboardData.length > 0) {
|
||||
embed.addFields({
|
||||
name: '📊 Stats',
|
||||
value: `Showing top ${leaderboardData.length} contributors`,
|
||||
inline: true
|
||||
});
|
||||
}
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
} catch (error) {
|
||||
console.error("Leaderboard command error:", error);
|
||||
|
|
|
|||
|
|
@ -3,27 +3,37 @@ const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
|||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("profile")
|
||||
.setDescription("View your AeThex profile in Discord"),
|
||||
.setDescription("View your AeThex profile in Discord")
|
||||
.addUserOption(option =>
|
||||
option.setName('user')
|
||||
.setDescription('User to view profile of')
|
||||
.setRequired(false)
|
||||
),
|
||||
|
||||
async execute(interaction, supabase) {
|
||||
if (!supabase) {
|
||||
return interaction.reply({ content: "This feature requires Supabase to be configured.", ephemeral: true });
|
||||
}
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
await interaction.deferReply();
|
||||
|
||||
const targetUser = interaction.options.getUser('user') || interaction.user;
|
||||
|
||||
try {
|
||||
const { data: link } = await supabase
|
||||
.from("discord_links")
|
||||
.select("user_id, primary_arm")
|
||||
.eq("discord_id", interaction.user.id)
|
||||
.eq("discord_id", targetUser.id)
|
||||
.single();
|
||||
|
||||
if (!link) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xff6b6b)
|
||||
.setTitle("❌ Not Linked")
|
||||
.setThumbnail(targetUser.displayAvatarURL({ size: 256 }))
|
||||
.setDescription(
|
||||
"You must link your Discord account to AeThex first.\nUse `/verify` to get started.",
|
||||
targetUser.id === interaction.user.id
|
||||
? "You must link your Discord account to AeThex first.\nUse `/verify` to get started."
|
||||
: `${targetUser.tag} hasn't linked their Discord account to AeThex yet.`
|
||||
);
|
||||
|
||||
return await interaction.editReply({ embeds: [embed] });
|
||||
|
|
@ -39,7 +49,7 @@ module.exports = {
|
|||
const embed = new EmbedBuilder()
|
||||
.setColor(0xff6b6b)
|
||||
.setTitle("❌ Profile Not Found")
|
||||
.setDescription("Your AeThex profile could not be found.");
|
||||
.setDescription("The AeThex profile could not be found.");
|
||||
|
||||
return await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
|
|
@ -52,35 +62,77 @@ module.exports = {
|
|||
devlink: "💻",
|
||||
};
|
||||
|
||||
const armColors = {
|
||||
labs: 0x22c55e,
|
||||
gameforge: 0xf97316,
|
||||
corp: 0x3b82f6,
|
||||
foundation: 0xec4899,
|
||||
devlink: 0x8b5cf6,
|
||||
};
|
||||
|
||||
const xp = profile.xp || 0;
|
||||
const level = Math.floor(Math.sqrt(xp / 100));
|
||||
const currentLevelXp = level * level * 100;
|
||||
const nextLevelXp = (level + 1) * (level + 1) * 100;
|
||||
const progressXp = xp - currentLevelXp;
|
||||
const neededXp = nextLevelXp - currentLevelXp;
|
||||
const progressPercent = Math.min(100, Math.floor((progressXp / neededXp) * 100));
|
||||
|
||||
const progressBar = createProgressBar(progressPercent);
|
||||
|
||||
const badges = profile.badges || [];
|
||||
const badgeDisplay = badges.length > 0
|
||||
? badges.map(b => getBadgeEmoji(b)).join(' ')
|
||||
: 'No badges yet';
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x7289da)
|
||||
.setTitle(`${profile.full_name || "AeThex User"}'s Profile`)
|
||||
.setThumbnail(
|
||||
profile.avatar_url || "https://aethex.dev/placeholder.svg",
|
||||
)
|
||||
.setColor(armColors[link.primary_arm] || 0x7c3aed)
|
||||
.setAuthor({
|
||||
name: `${profile.full_name || profile.username || 'AeThex User'}`,
|
||||
iconURL: targetUser.displayAvatarURL({ size: 64 })
|
||||
})
|
||||
.setThumbnail(profile.avatar_url || targetUser.displayAvatarURL({ size: 256 }))
|
||||
.setDescription(profile.bio || '*No bio set*')
|
||||
.addFields(
|
||||
{
|
||||
name: "👤 Username",
|
||||
value: profile.username || "N/A",
|
||||
value: `\`${profile.username || 'N/A'}\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: `${armEmojis[link.primary_arm] || "⚔️"} Primary Realm`,
|
||||
value: link.primary_arm || "Not set",
|
||||
name: `${armEmojis[link.primary_arm] || "⚔️"} Realm`,
|
||||
value: capitalizeFirst(link.primary_arm) || "Not set",
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "📊 Role",
|
||||
value: profile.user_type || "community_member",
|
||||
value: formatRole(profile.user_type),
|
||||
inline: true,
|
||||
},
|
||||
{ name: "📝 Bio", value: profile.bio || "No bio set", inline: false },
|
||||
{
|
||||
name: `📈 Level ${level}`,
|
||||
value: `${progressBar}\n\`${xp.toLocaleString()}\` / \`${nextLevelXp.toLocaleString()}\` XP`,
|
||||
inline: false,
|
||||
},
|
||||
{
|
||||
name: "🏆 Badges",
|
||||
value: badgeDisplay,
|
||||
inline: false,
|
||||
}
|
||||
)
|
||||
.addFields({
|
||||
name: "🔗 Links",
|
||||
value: `[Visit Full Profile](https://aethex.dev/creators/${profile.username})`,
|
||||
value: `[View Full Profile](https://aethex.dev/creators/${profile.username}) • [AeThex Platform](https://aethex.dev)`,
|
||||
})
|
||||
.setFooter({ text: "AeThex | Your Web3 Creator Hub" });
|
||||
.setFooter({
|
||||
text: `AeThex | ${targetUser.tag}`,
|
||||
iconURL: 'https://aethex.dev/favicon.ico'
|
||||
})
|
||||
.setTimestamp();
|
||||
|
||||
if (profile.banner_url) {
|
||||
embed.setImage(profile.banner_url);
|
||||
}
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
} catch (error) {
|
||||
|
|
@ -94,3 +146,38 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
};
|
||||
|
||||
function createProgressBar(percent) {
|
||||
const filled = Math.floor(percent / 10);
|
||||
const empty = 10 - filled;
|
||||
return `${'▓'.repeat(filled)}${'░'.repeat(empty)} ${percent}%`;
|
||||
}
|
||||
|
||||
function capitalizeFirst(str) {
|
||||
if (!str) return str;
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
function formatRole(role) {
|
||||
if (!role) return 'Member';
|
||||
return role.split('_').map(capitalizeFirst).join(' ');
|
||||
}
|
||||
|
||||
function getBadgeEmoji(badge) {
|
||||
const badgeMap = {
|
||||
'verified': '✅',
|
||||
'founder': '👑',
|
||||
'early_adopter': '🌟',
|
||||
'contributor': '💎',
|
||||
'creator': '🎨',
|
||||
'developer': '💻',
|
||||
'moderator': '🛡️',
|
||||
'partner': '🤝',
|
||||
'premium': '💫',
|
||||
'top_poster': '📝',
|
||||
'helpful': '❤️',
|
||||
'bug_hunter': '🐛',
|
||||
'event_winner': '🏆',
|
||||
};
|
||||
return badgeMap[badge] || `[${badge}]`;
|
||||
}
|
||||
|
|
|
|||
394
aethex-bot/commands/rolepanel.js
Normal file
394
aethex-bot/commands/rolepanel.js
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
const {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
PermissionFlagsBits,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
ChannelType
|
||||
} = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('rolepanel')
|
||||
.setDescription('Create and manage role button panels')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('create')
|
||||
.setDescription('Create a new role panel')
|
||||
.addChannelOption(option =>
|
||||
option.setName('channel')
|
||||
.setDescription('Channel to send the panel to')
|
||||
.setRequired(true)
|
||||
.addChannelTypes(ChannelType.GuildText)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('title')
|
||||
.setDescription('Panel title')
|
||||
.setRequired(true)
|
||||
.setMaxLength(256)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('description')
|
||||
.setDescription('Panel description')
|
||||
.setRequired(false)
|
||||
.setMaxLength(2000)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('color')
|
||||
.setDescription('Embed color')
|
||||
.setRequired(false)
|
||||
.addChoices(
|
||||
{ name: '🟣 Purple', value: '7c3aed' },
|
||||
{ name: '🟢 Green', value: '22c55e' },
|
||||
{ name: '🔵 Blue', value: '3b82f6' },
|
||||
{ name: '🟡 Yellow', value: 'eab308' },
|
||||
{ name: '🟠 Orange', value: 'f97316' }
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('addrole')
|
||||
.setDescription('Add a role button to a panel')
|
||||
.addStringOption(option =>
|
||||
option.setName('message_id')
|
||||
.setDescription('Message ID of the role panel')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addRoleOption(option =>
|
||||
option.setName('role')
|
||||
.setDescription('Role to add')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('label')
|
||||
.setDescription('Button label (optional, uses role name if not set)')
|
||||
.setRequired(false)
|
||||
.setMaxLength(80)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('emoji')
|
||||
.setDescription('Button emoji (optional)')
|
||||
.setRequired(false)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('style')
|
||||
.setDescription('Button style')
|
||||
.setRequired(false)
|
||||
.addChoices(
|
||||
{ name: 'Blue (Primary)', value: 'primary' },
|
||||
{ name: 'Gray (Secondary)', value: 'secondary' },
|
||||
{ name: 'Green (Success)', value: 'success' },
|
||||
{ name: 'Red (Danger)', value: 'danger' }
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('removerole')
|
||||
.setDescription('Remove a role button from a panel')
|
||||
.addStringOption(option =>
|
||||
option.setName('message_id')
|
||||
.setDescription('Message ID of the role panel')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addRoleOption(option =>
|
||||
option.setName('role')
|
||||
.setDescription('Role to remove')
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('list')
|
||||
.setDescription('List all role panels in this server')
|
||||
),
|
||||
|
||||
async execute(interaction, supabase, client) {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
if (subcommand === 'create') {
|
||||
await handleCreate(interaction, supabase);
|
||||
} else if (subcommand === 'addrole') {
|
||||
await handleAddRole(interaction, supabase, client);
|
||||
} else if (subcommand === 'removerole') {
|
||||
await handleRemoveRole(interaction, supabase, client);
|
||||
} else if (subcommand === 'list') {
|
||||
await handleList(interaction, supabase);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async function handleCreate(interaction, supabase) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const channel = interaction.options.getChannel('channel');
|
||||
const title = interaction.options.getString('title');
|
||||
const description = interaction.options.getString('description') || 'Click a button below to get or remove a role!';
|
||||
const color = parseInt(interaction.options.getString('color') || '7c3aed', 16);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(color)
|
||||
.setTitle(`🎭 ${title}`)
|
||||
.setDescription(description)
|
||||
.setFooter({ text: 'Click a button to toggle the role' })
|
||||
.setTimestamp();
|
||||
|
||||
try {
|
||||
const message = await channel.send({ embeds: [embed] });
|
||||
|
||||
if (supabase) {
|
||||
await supabase.from('role_panels').insert({
|
||||
message_id: message.id,
|
||||
channel_id: channel.id,
|
||||
guild_id: interaction.guildId,
|
||||
title: title,
|
||||
description: description,
|
||||
color: color.toString(16),
|
||||
roles: [],
|
||||
created_by: interaction.user.id
|
||||
});
|
||||
}
|
||||
|
||||
const successEmbed = new EmbedBuilder()
|
||||
.setColor(0x22c55e)
|
||||
.setTitle('✅ Role Panel Created')
|
||||
.setDescription(`Panel created in ${channel}!\n\nUse \`/rolepanel addrole\` with message ID:\n\`${message.id}\``)
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [successEmbed] });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Role panel create error:', error);
|
||||
await interaction.editReply({ content: 'Failed to create role panel. Check bot permissions.', ephemeral: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddRole(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const messageId = interaction.options.getString('message_id');
|
||||
const role = interaction.options.getRole('role');
|
||||
const label = interaction.options.getString('label') || role.name;
|
||||
const emoji = interaction.options.getString('emoji');
|
||||
const styleStr = interaction.options.getString('style') || 'primary';
|
||||
|
||||
const styleMap = {
|
||||
'primary': ButtonStyle.Primary,
|
||||
'secondary': ButtonStyle.Secondary,
|
||||
'success': ButtonStyle.Success,
|
||||
'danger': ButtonStyle.Danger
|
||||
};
|
||||
const style = styleMap[styleStr];
|
||||
|
||||
try {
|
||||
let channel = null;
|
||||
let message = null;
|
||||
|
||||
if (supabase) {
|
||||
const { data: panel } = await supabase
|
||||
.from('role_panels')
|
||||
.select('channel_id')
|
||||
.eq('message_id', messageId)
|
||||
.eq('guild_id', interaction.guildId)
|
||||
.single();
|
||||
|
||||
if (panel?.channel_id) {
|
||||
channel = await interaction.guild.channels.fetch(panel.channel_id).catch(() => null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!channel) {
|
||||
channel = interaction.channel;
|
||||
}
|
||||
|
||||
message = await channel.messages.fetch(messageId).catch(() => null);
|
||||
|
||||
if (!message) {
|
||||
return interaction.editReply({ content: 'Could not find that message. Make sure the message ID is correct and the panel exists.' });
|
||||
}
|
||||
|
||||
if (message.author.id !== client.user.id) {
|
||||
return interaction.editReply({ content: 'That message was not sent by me. I can only edit my own messages.' });
|
||||
}
|
||||
|
||||
const buttonData = {
|
||||
role_id: role.id,
|
||||
role_name: role.name,
|
||||
label: label,
|
||||
emoji: emoji,
|
||||
style: styleStr
|
||||
};
|
||||
|
||||
const existingRows = message.components.map(row => ActionRowBuilder.from(row));
|
||||
|
||||
const button = new ButtonBuilder()
|
||||
.setCustomId(`role_${role.id}`)
|
||||
.setLabel(label)
|
||||
.setStyle(style);
|
||||
|
||||
if (emoji) {
|
||||
button.setEmoji(emoji);
|
||||
}
|
||||
|
||||
let added = false;
|
||||
for (const row of existingRows) {
|
||||
if (row.components.length < 5) {
|
||||
row.addComponents(button);
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
if (existingRows.length >= 5) {
|
||||
return interaction.editReply({ content: 'Maximum buttons reached (25). Remove some roles first.' });
|
||||
}
|
||||
const newRow = new ActionRowBuilder().addComponents(button);
|
||||
existingRows.push(newRow);
|
||||
}
|
||||
|
||||
await message.edit({ components: existingRows });
|
||||
|
||||
if (supabase) {
|
||||
const { data: panel } = await supabase
|
||||
.from('role_panels')
|
||||
.select('roles')
|
||||
.eq('message_id', messageId)
|
||||
.single();
|
||||
|
||||
const roles = panel?.roles || [];
|
||||
roles.push(buttonData);
|
||||
|
||||
await supabase
|
||||
.from('role_panels')
|
||||
.update({ roles: roles })
|
||||
.eq('message_id', messageId);
|
||||
}
|
||||
|
||||
const successEmbed = new EmbedBuilder()
|
||||
.setColor(0x22c55e)
|
||||
.setTitle('✅ Role Added')
|
||||
.setDescription(`Added ${role} to the panel!`)
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [successEmbed] });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Add role error:', error);
|
||||
await interaction.editReply({ content: `Failed to add role: ${error.message}` });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveRole(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const messageId = interaction.options.getString('message_id');
|
||||
const role = interaction.options.getRole('role');
|
||||
|
||||
try {
|
||||
let channel = null;
|
||||
let message = null;
|
||||
|
||||
if (supabase) {
|
||||
const { data: panel } = await supabase
|
||||
.from('role_panels')
|
||||
.select('channel_id')
|
||||
.eq('message_id', messageId)
|
||||
.eq('guild_id', interaction.guildId)
|
||||
.single();
|
||||
|
||||
if (panel?.channel_id) {
|
||||
channel = await interaction.guild.channels.fetch(panel.channel_id).catch(() => null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!channel) {
|
||||
channel = interaction.channel;
|
||||
}
|
||||
|
||||
message = await channel.messages.fetch(messageId).catch(() => null);
|
||||
|
||||
if (!message) {
|
||||
return interaction.editReply({ content: 'Could not find that message. Make sure the message ID is correct and the panel exists.' });
|
||||
}
|
||||
|
||||
const existingRows = message.components.map(row => ActionRowBuilder.from(row));
|
||||
|
||||
for (const row of existingRows) {
|
||||
const buttonIndex = row.components.findIndex(btn => btn.data.custom_id === `role_${role.id}`);
|
||||
if (buttonIndex !== -1) {
|
||||
row.components.splice(buttonIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const filteredRows = existingRows.filter(row => row.components.length > 0);
|
||||
|
||||
await message.edit({ components: filteredRows });
|
||||
|
||||
if (supabase) {
|
||||
const { data: panel } = await supabase
|
||||
.from('role_panels')
|
||||
.select('roles')
|
||||
.eq('message_id', messageId)
|
||||
.single();
|
||||
|
||||
const roles = (panel?.roles || []).filter(r => r.role_id !== role.id);
|
||||
|
||||
await supabase
|
||||
.from('role_panels')
|
||||
.update({ roles: roles })
|
||||
.eq('message_id', messageId);
|
||||
}
|
||||
|
||||
const successEmbed = new EmbedBuilder()
|
||||
.setColor(0x22c55e)
|
||||
.setTitle('✅ Role Removed')
|
||||
.setDescription(`Removed ${role} from the panel!`)
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [successEmbed] });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Remove role error:', error);
|
||||
await interaction.editReply({ content: `Failed to remove role: ${error.message}` });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleList(interaction, supabase) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
if (!supabase) {
|
||||
return interaction.editReply({ content: 'This feature requires database connection.' });
|
||||
}
|
||||
|
||||
try {
|
||||
const { data: panels, error } = await supabase
|
||||
.from('role_panels')
|
||||
.select('*')
|
||||
.eq('guild_id', interaction.guildId)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
if (!panels || panels.length === 0) {
|
||||
return interaction.editReply({ content: 'No role panels found in this server.' });
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x7c3aed)
|
||||
.setTitle('🎭 Role Panels')
|
||||
.setDescription(panels.map(p =>
|
||||
`**${p.title}**\n` +
|
||||
`Channel: <#${p.channel_id}>\n` +
|
||||
`Message ID: \`${p.message_id}\`\n` +
|
||||
`Roles: ${p.roles?.length || 0}`
|
||||
).join('\n\n'))
|
||||
.setFooter({ text: `${panels.length} panel(s)` })
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
|
||||
} catch (error) {
|
||||
console.error('List panels error:', error);
|
||||
await interaction.editReply({ content: 'Failed to fetch panels.' });
|
||||
}
|
||||
}
|
||||
331
aethex-bot/commands/schedule.js
Normal file
331
aethex-bot/commands/schedule.js
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
const {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
PermissionFlagsBits,
|
||||
ChannelType
|
||||
} = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('schedule')
|
||||
.setDescription('Schedule a message to be sent later')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('message')
|
||||
.setDescription('Schedule a text message')
|
||||
.addChannelOption(option =>
|
||||
option.setName('channel')
|
||||
.setDescription('Channel to send the message to')
|
||||
.setRequired(true)
|
||||
.addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('content')
|
||||
.setDescription('Message content')
|
||||
.setRequired(true)
|
||||
.setMaxLength(2000)
|
||||
)
|
||||
.addIntegerOption(option =>
|
||||
option.setName('minutes')
|
||||
.setDescription('Minutes from now')
|
||||
.setRequired(true)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(10080)
|
||||
)
|
||||
.addBooleanOption(option =>
|
||||
option.setName('ping_everyone')
|
||||
.setDescription('Ping @everyone')
|
||||
.setRequired(false)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('embed')
|
||||
.setDescription('Schedule an embed message')
|
||||
.addChannelOption(option =>
|
||||
option.setName('channel')
|
||||
.setDescription('Channel to send the embed to')
|
||||
.setRequired(true)
|
||||
.addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('title')
|
||||
.setDescription('Embed title')
|
||||
.setRequired(true)
|
||||
.setMaxLength(256)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('description')
|
||||
.setDescription('Embed description')
|
||||
.setRequired(true)
|
||||
.setMaxLength(2000)
|
||||
)
|
||||
.addIntegerOption(option =>
|
||||
option.setName('minutes')
|
||||
.setDescription('Minutes from now')
|
||||
.setRequired(true)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(10080)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option.setName('color')
|
||||
.setDescription('Embed color')
|
||||
.setRequired(false)
|
||||
.addChoices(
|
||||
{ name: '🟣 Purple', value: '7c3aed' },
|
||||
{ name: '🟢 Green', value: '22c55e' },
|
||||
{ name: '🔴 Red', value: 'ef4444' },
|
||||
{ name: '🔵 Blue', value: '3b82f6' },
|
||||
{ name: '🟡 Yellow', value: 'eab308' }
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('list')
|
||||
.setDescription('List scheduled messages')
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('cancel')
|
||||
.setDescription('Cancel a scheduled message')
|
||||
.addStringOption(option =>
|
||||
option.setName('id')
|
||||
.setDescription('Scheduled message ID')
|
||||
.setRequired(true)
|
||||
)
|
||||
),
|
||||
|
||||
async execute(interaction, supabase, client) {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
if (subcommand === 'message') {
|
||||
await handleMessage(interaction, supabase, client);
|
||||
} else if (subcommand === 'embed') {
|
||||
await handleEmbed(interaction, supabase, client);
|
||||
} else if (subcommand === 'list') {
|
||||
await handleList(interaction, supabase);
|
||||
} else if (subcommand === 'cancel') {
|
||||
await handleCancel(interaction, supabase, client);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async function handleMessage(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const channel = interaction.options.getChannel('channel');
|
||||
const content = interaction.options.getString('content');
|
||||
const minutes = interaction.options.getInteger('minutes');
|
||||
const pingEveryone = interaction.options.getBoolean('ping_everyone') || false;
|
||||
|
||||
const sendTime = Date.now() + (minutes * 60 * 1000);
|
||||
const sendTimestamp = Math.floor(sendTime / 1000);
|
||||
|
||||
const scheduleId = `sched_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
try {
|
||||
const scheduleData = {
|
||||
id: scheduleId,
|
||||
type: 'message',
|
||||
channelId: channel.id,
|
||||
content: pingEveryone ? `@everyone ${content}` : content,
|
||||
sendTime: sendTime
|
||||
};
|
||||
|
||||
client.scheduledMessages = client.scheduledMessages || new Map();
|
||||
const timeout = setTimeout(() => sendScheduledMessage(scheduleId, client, supabase), minutes * 60 * 1000);
|
||||
client.scheduledMessages.set(scheduleId, { ...scheduleData, timeout });
|
||||
|
||||
if (supabase) {
|
||||
await supabase.from('scheduled_messages').insert({
|
||||
id: scheduleId,
|
||||
guild_id: interaction.guildId,
|
||||
channel_id: channel.id,
|
||||
type: 'message',
|
||||
content: scheduleData.content,
|
||||
send_time: new Date(sendTime).toISOString(),
|
||||
created_by: interaction.user.id,
|
||||
status: 'pending'
|
||||
});
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x22c55e)
|
||||
.setTitle('✅ Message Scheduled')
|
||||
.setDescription(`Message will be sent to ${channel} <t:${sendTimestamp}:R>`)
|
||||
.addFields(
|
||||
{ name: 'ID', value: `\`${scheduleId}\``, inline: true },
|
||||
{ name: 'Send Time', value: `<t:${sendTimestamp}:f>`, inline: true }
|
||||
)
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Schedule message error:', error);
|
||||
await interaction.editReply({ content: 'Failed to schedule message.' });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEmbed(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const channel = interaction.options.getChannel('channel');
|
||||
const title = interaction.options.getString('title');
|
||||
const description = interaction.options.getString('description');
|
||||
const minutes = interaction.options.getInteger('minutes');
|
||||
const color = interaction.options.getString('color') || '7c3aed';
|
||||
|
||||
const sendTime = Date.now() + (minutes * 60 * 1000);
|
||||
const sendTimestamp = Math.floor(sendTime / 1000);
|
||||
|
||||
const scheduleId = `sched_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
try {
|
||||
const embedData = {
|
||||
title: title,
|
||||
description: description,
|
||||
color: parseInt(color, 16)
|
||||
};
|
||||
|
||||
const scheduleData = {
|
||||
id: scheduleId,
|
||||
type: 'embed',
|
||||
channelId: channel.id,
|
||||
embedData: embedData,
|
||||
sendTime: sendTime
|
||||
};
|
||||
|
||||
client.scheduledMessages = client.scheduledMessages || new Map();
|
||||
const timeout = setTimeout(() => sendScheduledMessage(scheduleId, client, supabase), minutes * 60 * 1000);
|
||||
client.scheduledMessages.set(scheduleId, { ...scheduleData, timeout });
|
||||
|
||||
if (supabase) {
|
||||
await supabase.from('scheduled_messages').insert({
|
||||
id: scheduleId,
|
||||
guild_id: interaction.guildId,
|
||||
channel_id: channel.id,
|
||||
type: 'embed',
|
||||
embed_data: embedData,
|
||||
send_time: new Date(sendTime).toISOString(),
|
||||
created_by: interaction.user.id,
|
||||
status: 'pending'
|
||||
});
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x22c55e)
|
||||
.setTitle('✅ Embed Scheduled')
|
||||
.setDescription(`Embed will be sent to ${channel} <t:${sendTimestamp}:R>`)
|
||||
.addFields(
|
||||
{ name: 'ID', value: `\`${scheduleId}\``, inline: true },
|
||||
{ name: 'Send Time', value: `<t:${sendTimestamp}:f>`, inline: true },
|
||||
{ name: 'Embed Title', value: title, inline: false }
|
||||
)
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Schedule embed error:', error);
|
||||
await interaction.editReply({ content: 'Failed to schedule embed.' });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleList(interaction, supabase) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
if (!supabase) {
|
||||
return interaction.editReply({ content: 'Database not available.' });
|
||||
}
|
||||
|
||||
try {
|
||||
const { data: scheduled, error } = await supabase
|
||||
.from('scheduled_messages')
|
||||
.select('*')
|
||||
.eq('guild_id', interaction.guildId)
|
||||
.eq('status', 'pending')
|
||||
.order('send_time', { ascending: true });
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
if (!scheduled || scheduled.length === 0) {
|
||||
return interaction.editReply({ content: 'No scheduled messages.' });
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x7c3aed)
|
||||
.setTitle('📅 Scheduled Messages')
|
||||
.setDescription(scheduled.map(s => {
|
||||
const sendTimestamp = Math.floor(new Date(s.send_time).getTime() / 1000);
|
||||
return `**ID:** \`${s.id}\`\nType: ${s.type}\nChannel: <#${s.channel_id}>\nSends: <t:${sendTimestamp}:R>`;
|
||||
}).join('\n\n'))
|
||||
.setFooter({ text: `${scheduled.length} scheduled` })
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Schedule list error:', error);
|
||||
await interaction.editReply({ content: 'Failed to fetch scheduled messages.' });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancel(interaction, supabase, client) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const scheduleId = interaction.options.getString('id');
|
||||
|
||||
try {
|
||||
const scheduled = client.scheduledMessages?.get(scheduleId);
|
||||
if (scheduled?.timeout) {
|
||||
clearTimeout(scheduled.timeout);
|
||||
client.scheduledMessages.delete(scheduleId);
|
||||
}
|
||||
|
||||
if (supabase) {
|
||||
await supabase
|
||||
.from('scheduled_messages')
|
||||
.update({ status: 'cancelled' })
|
||||
.eq('id', scheduleId);
|
||||
}
|
||||
|
||||
await interaction.editReply({ content: '✅ Scheduled message cancelled.' });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Schedule cancel error:', error);
|
||||
await interaction.editReply({ content: 'Failed to cancel.' });
|
||||
}
|
||||
}
|
||||
|
||||
async function sendScheduledMessage(scheduleId, client, supabase) {
|
||||
try {
|
||||
const scheduled = client.scheduledMessages?.get(scheduleId);
|
||||
if (!scheduled) return;
|
||||
|
||||
const channel = await client.channels.fetch(scheduled.channelId);
|
||||
if (!channel) return;
|
||||
|
||||
if (scheduled.type === 'message') {
|
||||
await channel.send(scheduled.content);
|
||||
} else if (scheduled.type === 'embed') {
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(scheduled.embedData.color)
|
||||
.setTitle(scheduled.embedData.title)
|
||||
.setDescription(scheduled.embedData.description)
|
||||
.setTimestamp();
|
||||
|
||||
await channel.send({ embeds: [embed] });
|
||||
}
|
||||
|
||||
client.scheduledMessages.delete(scheduleId);
|
||||
|
||||
if (supabase) {
|
||||
await supabase
|
||||
.from('scheduled_messages')
|
||||
.update({ status: 'sent' })
|
||||
.eq('id', scheduleId);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Send scheduled message error:', error);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,51 +3,158 @@ const { SlashCommandBuilder, EmbedBuilder, ChannelType } = require('discord.js')
|
|||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('serverinfo')
|
||||
.setDescription('View information about this server'),
|
||||
.setDescription('View detailed information about this server'),
|
||||
|
||||
async execute(interaction, supabase, client) {
|
||||
await interaction.deferReply();
|
||||
|
||||
const guild = interaction.guild;
|
||||
|
||||
await guild.members.fetch().catch(() => {});
|
||||
|
||||
const textChannels = guild.channels.cache.filter(c => c.type === ChannelType.GuildText).size;
|
||||
const voiceChannels = guild.channels.cache.filter(c => c.type === ChannelType.GuildVoice).size;
|
||||
const categories = guild.channels.cache.filter(c => c.type === ChannelType.GuildCategory).size;
|
||||
const stageChannels = guild.channels.cache.filter(c => c.type === ChannelType.GuildStageVoice).size;
|
||||
const forumChannels = guild.channels.cache.filter(c => c.type === ChannelType.GuildForum).size;
|
||||
|
||||
const roles = guild.roles.cache.size - 1;
|
||||
const emojis = guild.emojis.cache.size;
|
||||
const stickers = guild.stickers.cache.size;
|
||||
|
||||
const boostLevel = guild.premiumTier;
|
||||
const boostCount = guild.premiumSubscriptionCount || 0;
|
||||
const boostEmojis = ['', '🌙', '🌟', '✨'];
|
||||
|
||||
const totalMembers = guild.memberCount;
|
||||
const onlineMembers = guild.members.cache.filter(m => m.presence?.status !== 'offline').size;
|
||||
const botMembers = guild.members.cache.filter(m => m.user.bot).size;
|
||||
const humanMembers = totalMembers - botMembers;
|
||||
|
||||
const owner = await guild.fetchOwner().catch(() => null);
|
||||
|
||||
const verificationLevels = {
|
||||
0: 'None',
|
||||
1: 'Low',
|
||||
2: 'Medium',
|
||||
3: 'High',
|
||||
4: 'Highest'
|
||||
};
|
||||
|
||||
const features = guild.features.slice(0, 5).map(f =>
|
||||
f.split('_').map(w => w.charAt(0) + w.slice(1).toLowerCase()).join(' ')
|
||||
);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x7c3aed)
|
||||
.setTitle(guild.name)
|
||||
.setThumbnail(guild.iconURL({ size: 256 }))
|
||||
.setAuthor({
|
||||
name: guild.name,
|
||||
iconURL: guild.iconURL({ size: 64 })
|
||||
})
|
||||
.setThumbnail(guild.iconURL({ size: 256, dynamic: true }))
|
||||
.setDescription(guild.description || '*No server description*')
|
||||
.addFields(
|
||||
{ name: 'ID', value: guild.id, inline: true },
|
||||
{ name: 'Owner', value: owner ? owner.user.tag : 'Unknown', inline: true },
|
||||
{ name: 'Created', value: `<t:${Math.floor(guild.createdTimestamp / 1000)}:R>`, inline: true },
|
||||
{ name: 'Members', value: `${guild.memberCount}`, inline: true },
|
||||
{ name: 'Roles', value: `${roles}`, inline: true },
|
||||
{ name: 'Emojis', value: `${emojis}`, inline: true },
|
||||
{ name: 'Text Channels', value: `${textChannels}`, inline: true },
|
||||
{ name: 'Voice Channels', value: `${voiceChannels}`, inline: true },
|
||||
{ name: 'Categories', value: `${categories}`, inline: true },
|
||||
{ name: 'Boost Level', value: `Tier ${boostLevel}`, inline: true },
|
||||
{ name: 'Boosts', value: `${boostCount}`, inline: true },
|
||||
{ name: 'Verification', value: guild.verificationLevel.toString(), inline: true }
|
||||
{
|
||||
name: '📋 General',
|
||||
value: [
|
||||
`**ID:** \`${guild.id}\``,
|
||||
`**Owner:** ${owner ? owner.user.tag : 'Unknown'}`,
|
||||
`**Created:** <t:${Math.floor(guild.createdTimestamp / 1000)}:R>`
|
||||
].join('\n'),
|
||||
inline: false
|
||||
},
|
||||
{
|
||||
name: '👥 Members',
|
||||
value: [
|
||||
`**Total:** ${totalMembers.toLocaleString()}`,
|
||||
`**Humans:** ${humanMembers.toLocaleString()}`,
|
||||
`**Bots:** ${botMembers.toLocaleString()}`
|
||||
].join('\n'),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '💬 Channels',
|
||||
value: [
|
||||
`**Text:** ${textChannels}`,
|
||||
`**Voice:** ${voiceChannels}`,
|
||||
`**Categories:** ${categories}`,
|
||||
forumChannels > 0 ? `**Forums:** ${forumChannels}` : null,
|
||||
stageChannels > 0 ? `**Stages:** ${stageChannels}` : null
|
||||
].filter(Boolean).join('\n'),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '🎨 Assets',
|
||||
value: [
|
||||
`**Roles:** ${roles}`,
|
||||
`**Emojis:** ${emojis}`,
|
||||
`**Stickers:** ${stickers}`
|
||||
].join('\n'),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: `${boostEmojis[boostLevel] || '💎'} Boost Status`,
|
||||
value: [
|
||||
`**Level:** ${boostLevel}/3`,
|
||||
`**Boosts:** ${boostCount}`,
|
||||
`**Progress:** ${getBoostProgress(boostLevel, boostCount)}`
|
||||
].join('\n'),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '🔒 Security',
|
||||
value: [
|
||||
`**Verification:** ${verificationLevels[guild.verificationLevel] || 'Unknown'}`,
|
||||
`**2FA Required:** ${guild.mfaLevel === 1 ? 'Yes' : 'No'}`,
|
||||
`**NSFW Level:** ${getNsfwLevel(guild.nsfwLevel)}`
|
||||
].join('\n'),
|
||||
inline: true
|
||||
}
|
||||
)
|
||||
.setFooter({
|
||||
text: `Requested by ${interaction.user.tag}`,
|
||||
iconURL: interaction.user.displayAvatarURL({ size: 32 })
|
||||
})
|
||||
.setTimestamp();
|
||||
|
||||
if (guild.description) {
|
||||
embed.setDescription(guild.description);
|
||||
if (features.length > 0) {
|
||||
embed.addFields({
|
||||
name: '✨ Features',
|
||||
value: features.map(f => `\`${f}\``).join(' • '),
|
||||
inline: false
|
||||
});
|
||||
}
|
||||
|
||||
if (guild.bannerURL()) {
|
||||
embed.setImage(guild.bannerURL({ size: 512 }));
|
||||
}
|
||||
|
||||
await interaction.reply({ embeds: [embed] });
|
||||
if (guild.splash) {
|
||||
embed.addFields({
|
||||
name: '🖼️ Splash',
|
||||
value: `[View Invite Splash](${guild.splashURL({ size: 512 })})`,
|
||||
inline: true
|
||||
});
|
||||
}
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
},
|
||||
};
|
||||
|
||||
function getBoostProgress(level, count) {
|
||||
const thresholds = [2, 7, 14];
|
||||
if (level >= 3) return '✅ Max Level';
|
||||
const needed = thresholds[level];
|
||||
const progress = Math.min(100, Math.floor((count / needed) * 100));
|
||||
return `${count}/${needed} (${progress}%)`;
|
||||
}
|
||||
|
||||
function getNsfwLevel(level) {
|
||||
const levels = {
|
||||
0: 'Default',
|
||||
1: 'Explicit',
|
||||
2: 'Safe',
|
||||
3: 'Age-Restricted'
|
||||
};
|
||||
return levels[level] || 'Unknown';
|
||||
}
|
||||
|
|
|
|||
185
aethex-bot/listeners/automod.js
Normal file
185
aethex-bot/listeners/automod.js
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
const { EmbedBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'messageCreate',
|
||||
|
||||
async execute(message, client, supabase) {
|
||||
if (message.author.bot) return;
|
||||
if (!message.guild) return;
|
||||
|
||||
const config = await getAutomodConfig(message.guild.id, client, supabase);
|
||||
if (!config) return;
|
||||
|
||||
if (isExempt(message.member, config.exempt_roles)) return;
|
||||
|
||||
let violation = null;
|
||||
|
||||
if (config.links_enabled && containsLink(message.content)) {
|
||||
violation = { type: 'link', action: config.links_action };
|
||||
}
|
||||
|
||||
if (!violation && config.invites_enabled && containsInvite(message.content)) {
|
||||
violation = { type: 'invite', action: 'delete' };
|
||||
}
|
||||
|
||||
if (!violation && config.badwords?.length > 0 && containsBadWord(message.content, config.badwords)) {
|
||||
violation = { type: 'badword', action: 'delete' };
|
||||
}
|
||||
|
||||
if (!violation && config.mentions_enabled && exceedsMentionLimit(message, config.mentions_limit)) {
|
||||
violation = { type: 'mentions', action: 'delete' };
|
||||
}
|
||||
|
||||
if (!violation && config.spam_enabled) {
|
||||
const isSpam = await checkSpam(message, client, config.spam_threshold);
|
||||
if (isSpam) {
|
||||
violation = { type: 'spam', action: 'timeout' };
|
||||
}
|
||||
}
|
||||
|
||||
if (violation) {
|
||||
await handleViolation(message, violation, client, supabase);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function getAutomodConfig(guildId, client, supabase) {
|
||||
if (client.automodConfig?.has(guildId)) {
|
||||
return client.automodConfig.get(guildId);
|
||||
}
|
||||
|
||||
if (!supabase) return null;
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('automod_config')
|
||||
.select('*')
|
||||
.eq('guild_id', guildId)
|
||||
.single();
|
||||
|
||||
if (error || !data) return null;
|
||||
|
||||
client.automodConfig = client.automodConfig || new Map();
|
||||
client.automodConfig.set(guildId, data);
|
||||
|
||||
return data;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isExempt(member, exemptRoles) {
|
||||
if (!member || !exemptRoles || exemptRoles.length === 0) return false;
|
||||
|
||||
if (member.permissions.has('Administrator')) return true;
|
||||
|
||||
return member.roles.cache.some(role => exemptRoles.includes(role.id));
|
||||
}
|
||||
|
||||
function containsLink(content) {
|
||||
const linkRegex = /https?:\/\/[^\s]+/gi;
|
||||
return linkRegex.test(content);
|
||||
}
|
||||
|
||||
function containsInvite(content) {
|
||||
const inviteRegex = /(discord\.(gg|io|me|li)|discordapp\.com\/invite)\/[a-zA-Z0-9]+/gi;
|
||||
return inviteRegex.test(content);
|
||||
}
|
||||
|
||||
function containsBadWord(content, badwords) {
|
||||
const lowerContent = content.toLowerCase();
|
||||
return badwords.some(word => {
|
||||
const regex = new RegExp(`\\b${escapeRegex(word)}\\b`, 'i');
|
||||
return regex.test(lowerContent);
|
||||
});
|
||||
}
|
||||
|
||||
function escapeRegex(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
function exceedsMentionLimit(message, limit) {
|
||||
const mentions = message.mentions.users.size + message.mentions.roles.size;
|
||||
return mentions > (limit || 5);
|
||||
}
|
||||
|
||||
async function checkSpam(message, client, threshold) {
|
||||
client.spamTracker = client.spamTracker || new Map();
|
||||
|
||||
const key = `${message.guild.id}-${message.author.id}`;
|
||||
const now = Date.now();
|
||||
const window = 5000;
|
||||
|
||||
let userMessages = client.spamTracker.get(key) || [];
|
||||
userMessages = userMessages.filter(t => now - t < window);
|
||||
userMessages.push(now);
|
||||
client.spamTracker.set(key, userMessages);
|
||||
|
||||
return userMessages.length >= (threshold || 5);
|
||||
}
|
||||
|
||||
async function handleViolation(message, violation, client, supabase) {
|
||||
try {
|
||||
await message.delete().catch(() => {});
|
||||
|
||||
const violationMessages = {
|
||||
link: '🔗 Links are not allowed in this server.',
|
||||
invite: '📨 Discord invites are not allowed.',
|
||||
badword: '🚫 Your message contained a banned word.',
|
||||
mentions: '📢 Too many mentions in your message.',
|
||||
spam: '🔄 Slow down! You\'re sending messages too fast.'
|
||||
};
|
||||
|
||||
const dmEmbed = new EmbedBuilder()
|
||||
.setColor(0xef4444)
|
||||
.setTitle('⚠️ Message Removed')
|
||||
.setDescription(violationMessages[violation.type] || 'Your message violated server rules.')
|
||||
.setFooter({ text: message.guild.name })
|
||||
.setTimestamp();
|
||||
|
||||
await message.author.send({ embeds: [dmEmbed] }).catch(() => {});
|
||||
|
||||
if (violation.action === 'warn' && supabase) {
|
||||
await supabase.from('warnings').insert({
|
||||
guild_id: message.guild.id,
|
||||
user_id: message.author.id,
|
||||
user_tag: message.author.tag,
|
||||
moderator_id: client.user.id,
|
||||
moderator_tag: client.user.tag,
|
||||
reason: `Auto-mod: ${violation.type} violation`
|
||||
});
|
||||
}
|
||||
|
||||
if (violation.action === 'timeout') {
|
||||
await message.member.timeout(5 * 60 * 1000, `Auto-mod: ${violation.type} violation`).catch(() => {});
|
||||
}
|
||||
|
||||
const { data: config } = await supabase
|
||||
?.from('server_config')
|
||||
.select('modlog_channel')
|
||||
.eq('guild_id', message.guild.id)
|
||||
.single() || { data: null };
|
||||
|
||||
if (config?.modlog_channel) {
|
||||
const logChannel = await client.channels.fetch(config.modlog_channel).catch(() => null);
|
||||
if (logChannel) {
|
||||
const logEmbed = new EmbedBuilder()
|
||||
.setColor(0xf97316)
|
||||
.setTitle('🛡️ Auto-Mod Action')
|
||||
.addFields(
|
||||
{ name: 'User', value: `${message.author.tag} (${message.author.id})`, inline: true },
|
||||
{ name: 'Violation', value: violation.type, inline: true },
|
||||
{ name: 'Action', value: violation.action, inline: true },
|
||||
{ name: 'Channel', value: `<#${message.channel.id}>`, inline: true }
|
||||
)
|
||||
.setFooter({ text: 'Auto-Moderation' })
|
||||
.setTimestamp();
|
||||
|
||||
await logChannel.send({ embeds: [logEmbed] });
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Auto-mod violation handling error:', error);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,20 +18,62 @@ module.exports = {
|
|||
const channel = await client.channels.fetch(config.goodbye_channel).catch(() => null);
|
||||
if (!channel) return;
|
||||
|
||||
const membershipDuration = getMembershipDuration(member.joinedTimestamp);
|
||||
const currentMemberCount = member.guild.memberCount;
|
||||
|
||||
const roles = member.roles.cache
|
||||
.filter(r => r.id !== member.guild.id)
|
||||
.sort((a, b) => b.position - a.position)
|
||||
.map(r => r.name)
|
||||
.slice(0, 5);
|
||||
|
||||
const goodbyeMessages = [
|
||||
`**${member.user.tag}** has left the server.`,
|
||||
`Goodbye, **${member.user.tag}**! We'll miss you!`,
|
||||
`**${member.user.tag}** just left. Hope to see you again!`,
|
||||
`**${member.user.tag}** has departed from the server.`,
|
||||
];
|
||||
const randomMessage = goodbyeMessages[Math.floor(Math.random() * goodbyeMessages.length)];
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xff6b6b)
|
||||
.setTitle('Goodbye!')
|
||||
.setDescription(`${member.user.tag} has left the server.`)
|
||||
.setThumbnail(member.displayAvatarURL({ size: 256 }))
|
||||
.setColor(0xef4444)
|
||||
.setAuthor({
|
||||
name: 'Member Left',
|
||||
iconURL: member.guild.iconURL({ size: 64 })
|
||||
})
|
||||
.setDescription(randomMessage)
|
||||
.setThumbnail(member.displayAvatarURL({ size: 512, dynamic: true }))
|
||||
.addFields(
|
||||
{ name: 'Members Now', value: `${member.guild.memberCount}`, inline: true },
|
||||
{ name: 'Was Member For', value: member.joinedTimestamp
|
||||
? `<t:${Math.floor(member.joinedTimestamp / 1000)}:R>`
|
||||
: 'Unknown', inline: true }
|
||||
{
|
||||
name: '👤 User',
|
||||
value: member.user.tag,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '👥 Members Now',
|
||||
value: currentMemberCount.toLocaleString(),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '⏰ Was Member For',
|
||||
value: membershipDuration,
|
||||
inline: true
|
||||
}
|
||||
)
|
||||
.setFooter({ text: `ID: ${member.id}` })
|
||||
.setFooter({
|
||||
text: `ID: ${member.id}`,
|
||||
iconURL: member.displayAvatarURL({ size: 32 })
|
||||
})
|
||||
.setTimestamp();
|
||||
|
||||
if (roles.length > 0) {
|
||||
embed.addFields({
|
||||
name: '🎭 Had Roles',
|
||||
value: roles.map(r => `\`${r}\``).join(', ') + (member.roles.cache.size > 6 ? ` +${member.roles.cache.size - 6} more` : ''),
|
||||
inline: false
|
||||
});
|
||||
}
|
||||
|
||||
await channel.send({ embeds: [embed] });
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -39,3 +81,37 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getMembershipDuration(joinedTimestamp) {
|
||||
if (!joinedTimestamp) return 'Unknown';
|
||||
|
||||
const now = Date.now();
|
||||
const diff = now - joinedTimestamp;
|
||||
|
||||
const seconds = Math.floor(diff / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
const months = Math.floor(days / 30);
|
||||
const years = Math.floor(days / 365);
|
||||
|
||||
if (years > 0) {
|
||||
const remainingMonths = months % 12;
|
||||
return remainingMonths > 0
|
||||
? `${years}y ${remainingMonths}mo`
|
||||
: `${years} year${years > 1 ? 's' : ''}`;
|
||||
} else if (months > 0) {
|
||||
const remainingDays = days % 30;
|
||||
return remainingDays > 0
|
||||
? `${months}mo ${remainingDays}d`
|
||||
: `${months} month${months > 1 ? 's' : ''}`;
|
||||
} else if (days > 0) {
|
||||
return `${days} day${days > 1 ? 's' : ''}`;
|
||||
} else if (hours > 0) {
|
||||
return `${hours} hour${hours > 1 ? 's' : ''}`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes} minute${minutes > 1 ? 's' : ''}`;
|
||||
} else {
|
||||
return 'Just now';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
const { EmbedBuilder } = require('discord.js');
|
||||
const { EmbedBuilder, AttachmentBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'guildMemberAdd',
|
||||
|
||||
async execute(member, client, supabase) {
|
||||
if (client.trackNewMember) {
|
||||
client.trackNewMember();
|
||||
}
|
||||
|
||||
if (!supabase) return;
|
||||
|
||||
try {
|
||||
|
|
@ -25,18 +29,66 @@ module.exports = {
|
|||
const channel = await client.channels.fetch(config.welcome_channel).catch(() => null);
|
||||
if (!channel) return;
|
||||
|
||||
const memberNumber = member.guild.memberCount;
|
||||
const accountAge = getAccountAge(member.user.createdTimestamp);
|
||||
const isNewAccount = (Date.now() - member.user.createdTimestamp) < 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const welcomeMessages = [
|
||||
`Welcome to **${member.guild.name}**, ${member}! We're glad you're here!`,
|
||||
`Hey ${member}, welcome to **${member.guild.name}**! Make yourself at home!`,
|
||||
`${member} just joined **${member.guild.name}**! Welcome aboard!`,
|
||||
`A wild ${member} appeared in **${member.guild.name}**! Welcome!`,
|
||||
`${member} has arrived! Welcome to **${member.guild.name}**!`,
|
||||
];
|
||||
const randomMessage = welcomeMessages[Math.floor(Math.random() * welcomeMessages.length)];
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x7c3aed)
|
||||
.setTitle('Welcome!')
|
||||
.setDescription(`Welcome to **${member.guild.name}**, ${member}!`)
|
||||
.setThumbnail(member.displayAvatarURL({ size: 256 }))
|
||||
.setColor(0x22c55e)
|
||||
.setAuthor({
|
||||
name: 'Welcome to the server!',
|
||||
iconURL: member.guild.iconURL({ size: 64 })
|
||||
})
|
||||
.setDescription(randomMessage)
|
||||
.setThumbnail(member.displayAvatarURL({ size: 512, dynamic: true }))
|
||||
.addFields(
|
||||
{ name: 'Member #', value: `${member.guild.memberCount}`, inline: true },
|
||||
{ name: 'Account Created', value: `<t:${Math.floor(member.user.createdTimestamp / 1000)}:R>`, inline: true }
|
||||
{
|
||||
name: '👤 Member',
|
||||
value: `${member.user.tag}`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '🔢 Member #',
|
||||
value: `${memberNumber.toLocaleString()}`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '📅 Account Age',
|
||||
value: accountAge,
|
||||
inline: true
|
||||
}
|
||||
)
|
||||
.setFooter({ text: `ID: ${member.id}` })
|
||||
.setFooter({
|
||||
text: `ID: ${member.id}`,
|
||||
iconURL: member.displayAvatarURL({ size: 32 })
|
||||
})
|
||||
.setTimestamp();
|
||||
|
||||
if (isNewAccount) {
|
||||
embed.addFields({
|
||||
name: '⚠️ Notice',
|
||||
value: 'This is a new account (created within the last 7 days)',
|
||||
inline: false
|
||||
});
|
||||
}
|
||||
|
||||
if (member.guild.rulesChannel) {
|
||||
embed.addFields({
|
||||
name: '📜 Get Started',
|
||||
value: `Check out the rules in ${member.guild.rulesChannel}`,
|
||||
inline: false
|
||||
});
|
||||
}
|
||||
|
||||
await channel.send({ embeds: [embed] });
|
||||
}
|
||||
|
||||
|
|
@ -45,3 +97,22 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getAccountAge(createdTimestamp) {
|
||||
const now = Date.now();
|
||||
const diff = now - createdTimestamp;
|
||||
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
const months = Math.floor(days / 30);
|
||||
const years = Math.floor(days / 365);
|
||||
|
||||
if (years > 0) {
|
||||
return `${years} year${years > 1 ? 's' : ''} ago`;
|
||||
} else if (months > 0) {
|
||||
return `${months} month${months > 1 ? 's' : ''} ago`;
|
||||
} else if (days > 0) {
|
||||
return `${days} day${days > 1 ? 's' : ''} ago`;
|
||||
} else {
|
||||
return 'Today';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue