gistfile1.txt
· 5.4 KiB · Text
Bruto
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);
| 1 | const { |
| 2 | Client, |
| 3 | GatewayIntentBits, |
| 4 | ButtonBuilder, |
| 5 | ButtonStyle, |
| 6 | ActionRowBuilder, |
| 7 | EmbedBuilder, |
| 8 | } = require("discord.js"); |
| 9 | const cron = require("node-cron"); |
| 10 | |
| 11 | // ─── Config ──────────────────────────────────────────────────────────────── |
| 12 | const TOKEN = process.env.DISCORD_TOKEN; // your bot token |
| 13 | const CHANNEL_ID = process.env.CHANNEL_ID; // TG planning channel ID |
| 14 | const TG_HOUR = 20; // TG starts at 20:00 |
| 15 | const POST_HOUR = 10; // bot posts at 19:00 (1h before) |
| 16 | const TIMEZONE = "Etc/GMT-2"; // GMT+2 (change to e.g. "Europe/Lisbon" if needed) |
| 17 | // ──────────────────────────────────────────────────────────────────────────── |
| 18 | |
| 19 | const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers] }); |
| 20 | |
| 21 | // vote entry: { username, votedAt, previousYesAt? } |
| 22 | let votes = { yes: new Map(), no: new Map() }; |
| 23 | let currentPollMessageId = null; |
| 24 | |
| 25 | function nowFormatted() { |
| 26 | return new Date().toLocaleTimeString("en-GB", { |
| 27 | timeZone: TIMEZONE, |
| 28 | hour: "2-digit", |
| 29 | minute: "2-digit", |
| 30 | }); |
| 31 | } |
| 32 | |
| 33 | function formatYesVoters() { |
| 34 | if (votes.yes.size === 0) return "—"; |
| 35 | return [...votes.yes.values()] |
| 36 | .map((v) => `${v.username} · ${v.votedAt}`) |
| 37 | .join("\n"); |
| 38 | } |
| 39 | |
| 40 | function formatNoVoters() { |
| 41 | if (votes.no.size === 0) return "—"; |
| 42 | return [...votes.no.values()] |
| 43 | .map((v) => { |
| 44 | const switched = v.previousYesAt |
| 45 | ? ` (was Yes at ${v.previousYesAt})` |
| 46 | : ""; |
| 47 | return `${v.username} · ${v.votedAt}${switched}`; |
| 48 | }) |
| 49 | .join("\n"); |
| 50 | } |
| 51 | |
| 52 | function buildEmbed() { |
| 53 | return new EmbedBuilder() |
| 54 | .setTitle("⚔️ TG — Tonight?") |
| 55 | .setDescription( |
| 56 | `Is **TG happening tonight at ${TG_HOUR}:00**?\n` |
| 57 | ) |
| 58 | .setColor(0xe8a317) |
| 59 | .addFields( |
| 60 | { |
| 61 | name: `✅ Yes (${votes.yes.size})`, |
| 62 | value: formatYesVoters(), |
| 63 | inline: true, |
| 64 | }, |
| 65 | { |
| 66 | name: `❌ No (${votes.no.size})`, |
| 67 | value: formatNoVoters(), |
| 68 | inline: true, |
| 69 | } |
| 70 | ) |
| 71 | .setFooter({ text: "Vote updates live • Anyone can vote • You can switch your vote" }) |
| 72 | .setTimestamp(); |
| 73 | } |
| 74 | |
| 75 | function buildButtons() { |
| 76 | const yesBtn = new ButtonBuilder() |
| 77 | .setCustomId("tg_yes") |
| 78 | .setLabel("✅ Yes") |
| 79 | .setStyle(ButtonStyle.Success); |
| 80 | |
| 81 | const noBtn = new ButtonBuilder() |
| 82 | .setCustomId("tg_no") |
| 83 | .setLabel("❌ No") |
| 84 | .setStyle(ButtonStyle.Danger); |
| 85 | |
| 86 | return new ActionRowBuilder().addComponents(yesBtn, noBtn); |
| 87 | } |
| 88 | |
| 89 | async function updatePollMessage(channel) { |
| 90 | if (!currentPollMessageId) return; |
| 91 | try { |
| 92 | const msg = await channel.messages.fetch(currentPollMessageId); |
| 93 | await msg.edit({ embeds: [buildEmbed()], components: [buildButtons()] }); |
| 94 | } catch (err) { |
| 95 | console.error("Failed to update poll message:", err); |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | async function postDailyPoll() { |
| 100 | try { |
| 101 | const channel = await client.channels.fetch(CHANNEL_ID); |
| 102 | if (!channel) return console.error("Channel not found."); |
| 103 | |
| 104 | votes = { yes: new Map(), no: new Map() }; |
| 105 | currentPollMessageId = null; |
| 106 | |
| 107 | const msg = await channel.send({ |
| 108 | embeds: [buildEmbed()], |
| 109 | components: [buildButtons()], |
| 110 | }); |
| 111 | currentPollMessageId = msg.id; |
| 112 | console.log(`[${new Date().toISOString()}] Poll posted.`); |
| 113 | } catch (err) { |
| 114 | console.error("Failed to post poll:", err); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | client.on("interactionCreate", async (interaction) => { |
| 119 | if (!interaction.isButton()) return; |
| 120 | if (!["tg_yes", "tg_no"].includes(interaction.customId)) return; |
| 121 | |
| 122 | const userId = interaction.user.id; |
| 123 | const member = await interaction.guild.members.fetch(userId); |
| 124 | const username = member.nickname ?? interaction.user.username; |
| 125 | const votedYes = interaction.customId === "tg_yes"; |
| 126 | const now = nowFormatted(); |
| 127 | |
| 128 | // Ignore if already voted the same option |
| 129 | if (votedYes && votes.yes.has(userId)) return interaction.deferUpdate(); |
| 130 | if (!votedYes && votes.no.has(userId)) return interaction.deferUpdate(); |
| 131 | |
| 132 | if (votedYes) { |
| 133 | // Switching from No → Yes |
| 134 | votes.no.delete(userId); |
| 135 | votes.yes.set(userId, { username, votedAt: now }); |
| 136 | } else { |
| 137 | // Switching from Yes → No — track when they were Yes |
| 138 | const previousYes = votes.yes.get(userId); |
| 139 | votes.yes.delete(userId); |
| 140 | votes.no.set(userId, { |
| 141 | username, |
| 142 | votedAt: now, |
| 143 | previousYesAt: previousYes ? previousYes.votedAt : null, |
| 144 | }); |
| 145 | } |
| 146 | |
| 147 | await interaction.reply({ |
| 148 | content: votedYes |
| 149 | ? `✅ You voted **Yes** for TG tonight!` |
| 150 | : `❌ You voted **No** for TG tonight, you roaching out?`, |
| 151 | ephemeral: true, |
| 152 | }); |
| 153 | |
| 154 | const channel = await client.channels.fetch(CHANNEL_ID); |
| 155 | await updatePollMessage(channel); |
| 156 | }); |
| 157 | |
| 158 | client.once("clientReady", () => { |
| 159 | console.log(`Logged in as ${client.user.tag}`); |
| 160 | |
| 161 | cron.schedule(`0 ${POST_HOUR} * * *`, () => postDailyPoll()); |
| 162 | postDailyPoll() |
| 163 | console.log(`Poll scheduled daily at ${POST_HOUR}:00.`); |
| 164 | }); |
| 165 | |
| 166 | client.login(TOKEN); |