const { SlashCommandBuilder } = require('@discordjs/builders'); const { AttachmentBuilder } = require("discord.js"); const fs = require('node:fs'); const { submissions, commands } = require("../config/config.js"); const { parse } = require("csv-parse/sync"); const { getSubmissionLine, safeCsvFormat } = require("../common.js"); const Sugar = require("sugar-date"); const { Buffer } = require("buffer"); /** * @param {import('discord.js').ChatInputCommandInteraction} interaction */ async function execute(interaction) { const game = interaction.options.getString("game", true); const fname = submissions[game]; if (fname === undefined) { await interaction.reply({content: "Invalid game specified!", ephemeral: true}); return; } if (!fs.existsSync(fname)) { await interaction.reply(`No submissions exist yet for ${game}.`); return; } const afterDateStr = interaction.options.getString("after_date"); const beforeDateStr = interaction.options.getString("before_date"); let afterTimestamp = undefined; let beforeTimestamp = undefined; // Using Sugar for magic date parsing if (afterDateStr) { const afterDate = Sugar.Date.create(afterDateStr); if (isNaN(afterDate)) { await interaction.reply({content: `Could not convert '${afterDateStr}' to a valid date.`, ephemeral: true}); return; } afterTimestamp = Math.round(afterDate / 1000); } if (beforeDateStr) { const beforeDate = Sugar.Date.create(beforeDateStr); if (isNaN(beforeDate)) { await interaction.reply({content: `Could not convert '${beforeDateStr}' to a valid date.`, ephemeral: true}); return; } beforeTimestamp = Math.round(beforeDate / 1000); } if (!isNaN(afterTimestamp) && !isNaN(beforeTimestamp) && beforeTimestamp < afterTimestamp) { await interaction.reply({content: `Your date range is invalid: ${getDateRangeString(afterTimestamp, beforeTimestamp)}.`, ephemeral: true}); return; } const csvFile = fs.readFileSync(fname); const lines = parse(csvFile, {delimiter: ',', fromLine: 2}); let csvString = "map_id,unix_timestamp,date_string,user_id,username,display_name,creator\n"; let found = false; for (let lineStr of lines) { const line = getSubmissionLine(lineStr); // Must be between the given date range if ((!isNaN(afterTimestamp) && line.timestamp < afterTimestamp) || (!isNaN(beforeTimestamp) && line.timestamp > beforeTimestamp)) { continue; } found = true; const dateStr = new Date(line.timestamp * 1000).toLocaleString("en-US", {dateStyle: "short"}); csvString += `${line.modelId},${line.timestamp},${dateStr},${line.userId},${safeCsvFormat(line.username)},${safeCsvFormat(line.displayName)},${safeCsvFormat(line.creator)}\n`; } if (!found) { let msg; const dateRangeStr = getDateRangeString(afterTimestamp, beforeTimestamp); if (dateRangeStr) { msg = `Could not find any submissions ${dateRangeStr}.`; } else { msg = "Could not find any submissions."; } await interaction.reply({content: msg, ephemeral: true}); return; } const file = new AttachmentBuilder(Buffer.from(csvString), { name: fname }); const dateRangeStr = getDateRangeString(afterTimestamp, beforeTimestamp); if (dateRangeStr) { await interaction.reply({content: `Using date range ${dateRangeStr}:`, files: [file]}); } else { await interaction.reply({files: [file]}); } } function getDateRangeString(afterTimestamp, beforeTimestamp) { if (!isNaN(afterTimestamp) && !isNaN(beforeTimestamp)) { return `between to `; } else if (!isNaN(afterTimestamp)) { return `after `; } else if (!isNaN(beforeTimestamp)) { return `before `; } else { return ""; } } module.exports = { data: new SlashCommandBuilder() .setName('submissions') .setDescription('View the submissions in .csv format') .addStringOption(option => option.setName("game") .setDescription("Select the maptest game") .setRequired(true) .addChoices(...commands)) .addStringOption(option => option.setName("after_date") .setDescription("Filter out submissions that weren't submitted after the given date")) .addStringOption(option => option.setName("before_date") .setDescription("Filter out submissions that weren't submitted before the given date")) , execute };