最終更新 1 month ago

nuno revised this gist 1 month ago. Go to revision

1 file changed, 166 insertions

gistfile1.txt(file created)

@@ -0,0 +1,166 @@
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);
Newer Older