Integrates Supabase for persistent storage of federation mappings and active tickets, along with adding new commands for announcements, audit logs, and polls. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 110a0afc-77c3-48ac-afca-8e969438dafc Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/hHBt1No Replit-Helium-Checkpoint-Created: true
91 lines
2.9 KiB
JavaScript
91 lines
2.9 KiB
JavaScript
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
|
||
|
||
const POLL_EMOJIS = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟'];
|
||
|
||
module.exports = {
|
||
data: new SlashCommandBuilder()
|
||
.setName('poll')
|
||
.setDescription('Create a community poll')
|
||
.addStringOption(option =>
|
||
option.setName('question')
|
||
.setDescription('The poll question')
|
||
.setRequired(true)
|
||
.setMaxLength(256)
|
||
)
|
||
.addStringOption(option =>
|
||
option.setName('options')
|
||
.setDescription('Poll options separated by | (e.g., "Yes | No | Maybe")')
|
||
.setRequired(true)
|
||
.setMaxLength(1000)
|
||
)
|
||
.addIntegerOption(option =>
|
||
option.setName('duration')
|
||
.setDescription('Poll duration in hours (default: 24)')
|
||
.setRequired(false)
|
||
.setMinValue(1)
|
||
.setMaxValue(168)
|
||
),
|
||
|
||
async execute(interaction, supabase, client) {
|
||
const question = interaction.options.getString('question');
|
||
const optionsRaw = interaction.options.getString('options');
|
||
const duration = interaction.options.getInteger('duration') || 24;
|
||
|
||
const options = optionsRaw.split('|').map(o => o.trim()).filter(o => o.length > 0);
|
||
|
||
if (options.length < 2) {
|
||
return interaction.reply({
|
||
content: 'Please provide at least 2 options separated by |',
|
||
ephemeral: true
|
||
});
|
||
}
|
||
|
||
if (options.length > 10) {
|
||
return interaction.reply({
|
||
content: 'Maximum 10 options allowed',
|
||
ephemeral: true
|
||
});
|
||
}
|
||
|
||
const endsAt = new Date(Date.now() + duration * 60 * 60 * 1000);
|
||
const optionsText = options.map((opt, i) => `${POLL_EMOJIS[i]} ${opt}`).join('\n');
|
||
|
||
const embed = new EmbedBuilder()
|
||
.setColor(0x7c3aed)
|
||
.setTitle(`📊 ${question}`)
|
||
.setDescription(optionsText)
|
||
.addFields(
|
||
{ name: 'Created by', value: interaction.user.tag, inline: true },
|
||
{ name: 'Ends', value: `<t:${Math.floor(endsAt.getTime() / 1000)}:R>`, inline: true },
|
||
{ name: 'Options', value: `${options.length}`, inline: true }
|
||
)
|
||
.setFooter({ text: 'React to vote!' })
|
||
.setTimestamp();
|
||
|
||
const message = await interaction.reply({ embeds: [embed], fetchReply: true });
|
||
|
||
for (let i = 0; i < options.length; i++) {
|
||
try {
|
||
await message.react(POLL_EMOJIS[i]);
|
||
} catch (e) {
|
||
console.error('Failed to add reaction:', e.message);
|
||
}
|
||
}
|
||
|
||
if (supabase) {
|
||
try {
|
||
await supabase.from('polls').insert({
|
||
message_id: message.id,
|
||
channel_id: interaction.channelId,
|
||
guild_id: interaction.guildId,
|
||
question: question,
|
||
options: JSON.stringify(options),
|
||
created_by: interaction.user.id,
|
||
ends_at: endsAt.toISOString(),
|
||
});
|
||
} catch (e) {
|
||
console.warn('Failed to save poll:', e.message);
|
||
}
|
||
}
|
||
},
|
||
};
|