const { Client, GatewayIntentBits, ButtonBuilder, ButtonStyle, ActionRowBuilder, EmbedBuilder, } = require("discord.js"); const cron = require("node-cron"); // ─── Config ──────────────────────────────────────────────────────────────── const TOKEN = process.env.DISCORD_TOKEN; // your bot token const CHANNEL_ID = process.env.CHANNEL_ID; // TG planning channel ID const TG_HOUR = 20; // TG starts at 20:00 const POST_HOUR = 10; // bot posts at 19:00 (1h before) const TIMEZONE = "Etc/GMT-2"; // GMT+2 (change to e.g. "Europe/Lisbon" if needed) // ──────────────────────────────────────────────────────────────────────────── const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers] }); // vote entry: { username, votedAt, previousYesAt? } let votes = { yes: new Map(), no: new Map() }; let currentPollMessageId = null; function nowFormatted() { return new Date().toLocaleTimeString("en-GB", { timeZone: TIMEZONE, hour: "2-digit", minute: "2-digit", }); } function formatYesVoters() { if (votes.yes.size === 0) return "—"; return [...votes.yes.values()] .map((v) => `${v.username} · ${v.votedAt}`) .join("\n"); } function formatNoVoters() { if (votes.no.size === 0) return "—"; return [...votes.no.values()] .map((v) => { const switched = v.previousYesAt ? ` (was Yes at ${v.previousYesAt})` : ""; return `${v.username} · ${v.votedAt}${switched}`; }) .join("\n"); } function buildEmbed() { return new EmbedBuilder() .setTitle("⚔️ TG — Tonight?") .setDescription( `Is **TG happening tonight at ${TG_HOUR}:00**?\n` ) .setColor(0xe8a317) .addFields( { name: `✅ Yes (${votes.yes.size})`, value: formatYesVoters(), inline: true, }, { name: `❌ No (${votes.no.size})`, value: formatNoVoters(), inline: true, } ) .setFooter({ text: "Vote updates live • Anyone can vote • You can switch your vote" }) .setTimestamp(); } function buildButtons() { const yesBtn = new ButtonBuilder() .setCustomId("tg_yes") .setLabel("✅ Yes") .setStyle(ButtonStyle.Success); const noBtn = new ButtonBuilder() .setCustomId("tg_no") .setLabel("❌ No") .setStyle(ButtonStyle.Danger); return new ActionRowBuilder().addComponents(yesBtn, noBtn); } async function updatePollMessage(channel) { if (!currentPollMessageId) return; try { const msg = await channel.messages.fetch(currentPollMessageId); await msg.edit({ embeds: [buildEmbed()], components: [buildButtons()] }); } catch (err) { console.error("Failed to update poll message:", err); } } async function postDailyPoll() { try { const channel = await client.channels.fetch(CHANNEL_ID); if (!channel) return console.error("Channel not found."); votes = { yes: new Map(), no: new Map() }; currentPollMessageId = null; const msg = await channel.send({ embeds: [buildEmbed()], components: [buildButtons()], }); currentPollMessageId = msg.id; console.log(`[${new Date().toISOString()}] Poll posted.`); } catch (err) { console.error("Failed to post poll:", err); } } client.on("interactionCreate", async (interaction) => { if (!interaction.isButton()) return; if (!["tg_yes", "tg_no"].includes(interaction.customId)) return; const userId = interaction.user.id; const member = await interaction.guild.members.fetch(userId); const username = member.nickname ?? interaction.user.username; const votedYes = interaction.customId === "tg_yes"; const now = nowFormatted(); // Ignore if already voted the same option if (votedYes && votes.yes.has(userId)) return interaction.deferUpdate(); if (!votedYes && votes.no.has(userId)) return interaction.deferUpdate(); if (votedYes) { // Switching from No → Yes votes.no.delete(userId); votes.yes.set(userId, { username, votedAt: now }); } else { // Switching from Yes → No — track when they were Yes const previousYes = votes.yes.get(userId); votes.yes.delete(userId); votes.no.set(userId, { username, votedAt: now, previousYesAt: previousYes ? previousYes.votedAt : null, }); } await interaction.reply({ content: votedYes ? `✅ You voted **Yes** for TG tonight!` : `❌ You voted **No** for TG tonight, you roaching out?`, ephemeral: true, }); const channel = await client.channels.fetch(CHANNEL_ID); await updatePollMessage(channel); }); client.once("clientReady", () => { console.log(`Logged in as ${client.user.tag}`); cron.schedule(`0 ${POST_HOUR} * * *`, () => postDailyPoll()); postDailyPoll() console.log(`Poll scheduled daily at ${POST_HOUR}:00.`); }); client.login(TOKEN);