const fs = require('node:fs'); require('dotenv/config'); const path = require('node:path'); const { Client, Collection, Events, GatewayIntentBits, REST, Routes, MessageFlags, Partials, EmbedBuilder } = require('discord.js'); const { reactionCheck, unreactionCheck, updateTournamentsJSON } = require('./utils.js'); const { Tournament } = require('./data_objects/tournament.js'); function readTournamentsJSON() { try { const data = fs.readFileSync('./tournaments.json', { encoding: 'utf8' }); return new Map(Object.entries(JSON.parse(data))); } catch (err) { return new Map(); } } function readWhitelistJSON() { try { const data = fs.readFileSync('./whitelist.json', { encoding: 'utf8' }); return new Set(JSON.parse(data)); } catch (err) { return new Set(); } } function readBlacklistJSON() { try { const data = fs.readFileSync('./blacklist.json', { encoding: 'utf8' }); return new Set(JSON.parse(data)); } catch (err) { return new Set(); } } const tournaments = readTournamentsJSON(); const trackedTournaments = []; const whitelist = readWhitelistJSON(); const blacklist = readBlacklistJSON(); module.exports = { tournaments, trackedTournaments, blacklist, whitelist } const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessageReactions], partials: [Partials.Message, Partials.Channel, Partials.Reaction, Partials.User], } ); client.commands = new Collection(); const foldersPath = path.join(__dirname, 'commands'); const commandFolders = fs.readdirSync(foldersPath); for (const folder of commandFolders) { const commandsPath = path.join(foldersPath, folder); const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); for (const file of commandFiles) { const filePath = path.join(commandsPath, file); const command = require(filePath); // Set a new item in the Collection with the key as the command name and the value as the exported module if ('data' in command && 'execute' in command) { client.commands.set(command.data.name, command); } else { console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); } } } client.on('ready', () => { console.log(`Logged in as ${client.user.tag}!`); // Thing, that keeps track of clocks setInterval( () => { let current_time = Date.now()/1000; tournaments.forEach(async (value, key, map) => { if (!trackedTournaments.find((element) => element === key) && value.status === 0 && value.unix_checkin_start > current_time && value.channelID && value.messageID){ // need to recheck for participants const msg_channel = await client.channels.fetch(value.channelID); const msg = await msg_channel.messages.fetch(value.messageID); const collectorFilter = (reaction, user) => { return reaction.emoji.name === '✅' && user.id !== msg.author.id; }; tournaments.set(key, Tournament.fromObject(value)); // We gonna make sure, that user is eligible for a participation const collector = msg.createReactionCollector({ filter: collectorFilter, time: value.unix_reg_end*1000 - current_time*1000, dispose: true }); collector.on('collect', async (reaction, user) => reactionCheck(reaction, user, client, msg_channel.guild, tournaments.get(key), value.participant_role)); collector.on('remove', async (reaction, user) => unreactionCheck(reaction, user, msg_channel.guild, tournaments.get(key), value.participant_role)); trackedTournaments.push(key); } if (value.status === 0 && value.unix_checkin_start < current_time) { value.status = 1; console.log("Check in started"); const checkInEmbed = new EmbedBuilder() .setColor(0x00FF00) .setTitle(value.title) .setDescription(`Время подтверждать регистрацию\nКоличество регистрантов: ${value.participants.length}`) .setFooter({ text: 'Поставьте ✅, чтобы подтвердить участие' }) .addFields( { name: "Завершение check in", value: `\n()`}, { name: "**Старт турнира**", value: `\n()`}, ); const check_in_channel = await client.channels.fetch(value.channelID); const check_in_message = await check_in_channel.send({ content: `<@&${value.participant_role}>`, embeds: [checkInEmbed] }); value.setCheckInMessageID() const collectorFilter = (reaction, user) => { return reaction.emoji.name === '✅' && user.id !== client.user.id; }; check_in_message.react('✅'); // NOTE: only 24,855127314815 days max const collector = check_in_message.createReactionCollector({ filter: collectorFilter, time: value.unix_checkin_end*1000 - current_time*1000, dispose: true }); collector.on('collect', async (reaction, user) => { try{ if (value.check_in(user.id)){ check_in_message.guild.members.addRole({ user: user, reason: "Подтвердил участие", role: value.checked_in_role }); //h console.log(`${user.tag} checked in for a ${value.title} event`); }else{ reaction.users.remove(user.id); const exampleEmbed = new EmbedBuilder() .setColor(0xFF0000) .setTitle("Вы не регистрировались на данный турнир") .setDescription(value.unix_reg_end < current_time ? "Участники на данный турнир больше не принимаются" : "...но вы всё ещё можете зарегистрироваться!") .setTimestamp(); check_in_channel.send({ content: `<@${user.id}>`, embeds: [exampleEmbed] }).then(msg => { setTimeout(() => msg.delete(), 10000) }); } } catch (error) { const check_in_channel = await client.channels.fetch(process.env.BOT_LOGS_CHANNEL); check_in_channel.send({ content: `Я поймал ошибку:\n\`${error}\`` }); return; } updateTournamentsJSON(); }); collector.on('remove', async (reaction, user) => { check_in_message.guild.members.addRole({ user: user, reason: "Расхотел участвовать", role: value.checked_in_role }); console.log(`${user.tag} unregistred for a ${value.title} event`); value.removeChecked(user.id); updateTournamentsJSON(); }); } if (value.status === 1 && value.unix_checkin_end < current_time) { value.status = 2; console.log("Check in ended"); value.checked_in.sort((a, b) => b.tr - a.tr); const checkInEmbed = new EmbedBuilder() .setColor(0x0000FF) .setTitle(`Check-in на ${value.title} завершен`) .setDescription(`Количество участников: ${value.checked_in.length}\n\`${value.participants.map((element) => `${element.username.padEnd(18)}${Intl.NumberFormat("ru-RU", {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(element.tr)} TR\n`)}\``) .setFooter({ text: `Старт планировался в ()` }); const check_in_channel = await client.channels.fetch(process.env.RESULTS_CHANNEL); check_in_channel.send({ embeds: [checkInEmbed] }); } }); }, 1000); }); client.on(Events.InteractionCreate, async interaction => { if (!interaction.isChatInputCommand()) return; const command = interaction.client.commands.get(interaction.commandName); if (!command) { console.error(`No command matching ${interaction.commandName} was found.`); return; } try { await command.execute(interaction); } catch (error) { console.error(error); if (interaction.replied || interaction.deferred) { await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); } else { await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); } } }); client.login(process.env.DISCORD_TOKEN);