tournament-helper-bot/index.js

184 lines
7.9 KiB
JavaScript

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){
// 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: `<t:${value.unix_checkin_end}:f>\n(<t:${value.unix_checkin_end}:R>)`},
{ name: "**Старт турнира**", value: `<t:${value.unix_start}:f>\n(<t:${value.unix_start}:R>)`},
);
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('✅');
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`);
teto.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: `Старт планировался в <t:${value.unix_start}:f> (<t:${value.unix_start}:R>)` });
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);