Allow filtering submissions on date range + add extra info to .csv #8
@ -2,6 +2,9 @@ const { SlashCommandBuilder } = require('@discordjs/builders');
|
|||||||
const { MessageAttachment } = require("discord.js");
|
const { MessageAttachment } = require("discord.js");
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
const { submissions, commands } = require("../config/config.js");
|
const { submissions, commands } = require("../config/config.js");
|
||||||
|
const { parse } = require("csv-parse/sync");
|
||||||
|
const { getSubmissionLine } = require("../common.js");
|
||||||
|
const Sugar = require("sugar-date");
|
||||||
|
|
||||||
async function execute(interaction) {
|
async function execute(interaction) {
|
||||||
const game = interaction.options.getString("game");
|
const game = interaction.options.getString("game");
|
||||||
@ -17,10 +20,91 @@ async function execute(interaction) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const csv = fs.readFileSync(fname);
|
const afterDateStr = interaction.options.getString("after_date");
|
||||||
const file = new MessageAttachment(csv, fname);
|
const beforeDateStr = interaction.options.getString("before_date");
|
||||||
await interaction.reply({files: [file]});
|
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\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},${line.username}\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 MessageAttachment(Buffer.from(csvString), 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 <t:${afterTimestamp}:d> to <t:${beforeTimestamp}:d>`;
|
||||||
|
}
|
||||||
|
else if (!isNaN(afterTimestamp)) {
|
||||||
|
return `after <t:${afterTimestamp}:d>`;
|
||||||
|
}
|
||||||
|
else if (!isNaN(beforeTimestamp)) {
|
||||||
|
return `before <t:${beforeTimestamp}:d>`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -32,6 +116,12 @@ module.exports = {
|
|||||||
.setDescription("Select the maptest game")
|
.setDescription("Select the maptest game")
|
||||||
.setRequired(true)
|
.setRequired(true)
|
||||||
.addChoices(...commands))
|
.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
|
execute
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ const fs = require('node:fs');
|
|||||||
const noblox = require("noblox.js");
|
const noblox = require("noblox.js");
|
||||||
const axios = require("axios").default;
|
const axios = require("axios").default;
|
||||||
const { submissions, commands, cookies } = require("../config/config.js");
|
const { submissions, commands, cookies } = require("../config/config.js");
|
||||||
const { AssetType, getAssetInfo } = require("../common.js");
|
const { AssetType, getAssetInfo, SubmissionColumnsString, createSubmissionLine, getSubmissionLine } = require("../common.js");
|
||||||
|
|
||||||
async function robloxUserFromDiscord(id) {
|
async function robloxUserFromDiscord(id) {
|
||||||
if (isNaN(id)) return undefined;
|
if (isNaN(id)) return undefined;
|
||||||
@ -42,7 +42,7 @@ async function execute(interaction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(fname)) {
|
if (!fs.existsSync(fname)) {
|
||||||
fs.writeFileSync(fname, "id,timestamp\n");
|
fs.writeFileSync(fname, SubmissionColumnsString);
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = interaction.options.getInteger("asset_id");
|
const id = interaction.options.getInteger("asset_id");
|
||||||
@ -71,24 +71,24 @@ async function execute(interaction) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const csv = fs.readFileSync(fname);
|
const csvFile = fs.readFileSync(fname);
|
||||||
const records = parse(csv, {delimiter: ',', fromLine: 2});
|
const lines = parse(csvFile, {delimiter: ',', fromLine: 2});
|
||||||
|
|
||||||
let s = "id,timestamp\n";
|
let csvString = SubmissionColumnsString;
|
||||||
for (let record of records) {
|
for (let lineStr of lines) {
|
||||||
const rid = record[0];
|
const line = getSubmissionLine(lineStr);
|
||||||
const rtimestamp = record[1];
|
|
||||||
if (id === rid) {
|
if (id === line.modelId) {
|
||||||
await interaction.reply({content: `This map (id: ${id}) was already submitted on <t:${rtimestamp}:d>.`, ephemeral: true});
|
await interaction.reply({content: `This map (id: ${id}) was already submitted on <t:${line.timestamp}:d>.`, ephemeral: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
s += `${rid},${rtimestamp}\n`;
|
|
||||||
|
csvString += createSubmissionLine(line.modelId, line.timestamp, line.userId, line.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
const unix = Math.round(+new Date()/1000);
|
const unixTimestamp = Math.round(+new Date()/1000);
|
||||||
s += `${id},${unix}\n`;
|
csvString += createSubmissionLine(id, unixTimestamp, userId, await robloxUsernameFromId(userId));
|
||||||
|
fs.writeFileSync(fname, csvString);
|
||||||
fs.writeFileSync(fname, s);
|
|
||||||
|
|
||||||
await interaction.reply(`Map (id: ${id}) successfully submitted.`);
|
await interaction.reply(`Map (id: ${id}) successfully submitted.`);
|
||||||
}
|
}
|
||||||
|
35
common.js
35
common.js
@ -52,4 +52,37 @@ async function getAssetInfo(assetId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { AssetType, getAssetInfo };
|
const SubmissionColumnsString = "map_id,unix_timestamp,user_id,username\n";
|
||||||
|
|
||||||
|
const SubmissionColumn = {
|
||||||
|
ModelID: 0,
|
||||||
|
UnixTimestamp: 1,
|
||||||
|
UserID: 2,
|
||||||
|
Username: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSubmissionLine(line) {
|
||||||
|
const modelId = Number(line[SubmissionColumn.ModelID]);
|
||||||
|
const timestamp = Number(line[SubmissionColumn.UnixTimestamp]);
|
||||||
|
let userId = "";
|
||||||
|
let username = "";
|
||||||
|
|
||||||
|
// Backwards compatibility
|
||||||
|
if (line.length > 2) {
|
||||||
|
userId = Number(line[SubmissionColumn.UserID]);
|
||||||
|
username = line[SubmissionColumn.Username];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
modelId: modelId,
|
||||||
|
timestamp: timestamp,
|
||||||
|
userId: userId,
|
||||||
|
username: username
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSubmissionLine(modelId, timestamp, userId, username) {
|
||||||
|
return `${modelId},${timestamp},${userId},${username}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { AssetType, getAssetInfo, SubmissionColumn, SubmissionColumnsString, getSubmissionLine, createSubmissionLine };
|
@ -7,6 +7,7 @@
|
|||||||
"discord-api-types": "^0.32.1",
|
"discord-api-types": "^0.32.1",
|
||||||
"discord.js": "^13.6.0",
|
"discord.js": "^13.6.0",
|
||||||
"noblox.js": "^4.15.1",
|
"noblox.js": "^4.15.1",
|
||||||
"node-csv": "^0.1.2"
|
"node-csv": "^0.1.2",
|
||||||
|
"sugar-date": "^2.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user