diff --git a/.gitignore b/.gitignore index 6b1ba3c..fdbbf6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ config.json +config.test.json old/ bot.log data/ diff --git a/commands/receipts/receiptCommands.js b/commands/receipts/receiptCommands.js index 5522077..cc368fe 100644 --- a/commands/receipts/receiptCommands.js +++ b/commands/receipts/receiptCommands.js @@ -1,6 +1,16 @@ -const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); +const { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder } = require('discord.js'); +const axios = require('axios').default; const { HOUSEHOLD_IDS } = require('../../core/constants.js'); +const CHART_COLORS = { + [HOUSEHOLD_IDS.MINZ]: '#4e79a7', + [HOUSEHOLD_IDS.MIFFY]: '#f28e2b', +}; +const DISPLAY_NAMES = { + [HOUSEHOLD_IDS.MINZ]: 'Minz', + [HOUSEHOLD_IDS.MIFFY]: 'Miffy', +}; + module.exports = { category: 'utility', global: true, @@ -43,6 +53,15 @@ module.exports = { option.setName('user') .setRequired(false) .setDescription('User to view stats for')) + ) + .addSubcommand(subcommand => + subcommand + .setName('chart') + .setDescription('Render a bar chart comparing Minz and Miffy spendings over time') + .addIntegerOption(option => + option.setName('weeks') + .setRequired(false) + .setDescription('Number of recent weeks to include (default 8)')) ), async execute(interaction) { let db = await interaction.client.localDB; @@ -159,6 +178,70 @@ module.exports = { await interaction.reply({ embeds: [statsEmbed] }); break; + case 'chart': { + const weeks = Math.min(Math.max(interaction.options.getInteger('weeks') ?? 8, 1), 52); + + const chartPeriods = db.prepare(` + SELECT id, start_date, end_date + FROM weekly_budgets + ORDER BY start_date DESC + LIMIT ? + `).all(weeks).reverse(); + + if (chartPeriods.length === 0) { + await interaction.reply("No budget periods on record."); + return; + } + + await interaction.deferReply(); + + const spentByUserAndPeriod = db.prepare(` + SELECT COALESCE(SUM(amount), 0) as total + FROM grocery_spendings + WHERE budget_id = ? AND discord_id = ? + `); + + const labels = chartPeriods.map(period => period.start_date.slice(5)); + const datasets = [HOUSEHOLD_IDS.MINZ, HOUSEHOLD_IDS.MIFFY].map(userId => ({ + label: DISPLAY_NAMES[userId], + backgroundColor: CHART_COLORS[userId], + data: chartPeriods.map(period => spentByUserAndPeriod.get(period.id, userId).total), + })); + + const chartConfig = { + type: 'bar', + data: { labels, datasets }, + options: { + title: { display: true, text: 'Weekly Grocery Spending (€)' }, + scales: { + yAxes: [{ ticks: { beginAtZero: true }, scaleLabel: { display: true, labelString: '€' } }], + }, + }, + }; + + try { + const response = await axios.post('https://quickchart.io/chart', { + chart: chartConfig, + width: 800, + height: 400, + backgroundColor: 'white', + }, { responseType: 'arraybuffer' }); + + const attachment = new AttachmentBuilder(Buffer.from(response.data), { name: 'budget-chart.png' }); + const chartEmbed = new EmbedBuilder() + .setColor(0x0099ff) + .setTitle('Weekly Grocery Spending') + .setDescription(`Last ${chartPeriods.length} week(s): \`${chartPeriods[0].start_date}\` to \`${chartPeriods[chartPeriods.length - 1].end_date}\``) + .setImage('attachment://budget-chart.png') + .setTimestamp(); + + await interaction.editReply({ embeds: [chartEmbed], files: [attachment] }); + } catch (error) { + console.error('[budget chart] Failed to render chart:', error); + await interaction.editReply("Failed to render the chart, sorry!"); + } + break; + } default: await interaction.reply(`Sub mismatch. Switching on ${interaction.options.getSubcommand()}`); break; diff --git a/deploy-commands.js b/deploy-commands.js index 4694bd0..c38ba9b 100644 --- a/deploy-commands.js +++ b/deploy-commands.js @@ -1,5 +1,5 @@ const { REST, Routes } = require('discord.js'); -const { clientId, guildId, token } = require('./config.json'); +const { clientId, guildId, token } = require(process.env.CONFIG_FILE ? `./${process.env.CONFIG_FILE}` : './config.json'); const fs = require('node:fs'); const path = require('node:path'); diff --git a/main.js b/main.js index 8e38a51..e8188f4 100644 --- a/main.js +++ b/main.js @@ -1,7 +1,7 @@ const fs = require('node:fs'); const path = require('node:path'); const { Client, Collection, Events, GatewayIntentBits, Options, Partials } = require('discord.js'); -const { token } = require('./config.json'); +const { token } = require(process.env.CONFIG_FILE ? `./${process.env.CONFIG_FILE}` : './config.json'); const createDbConnection = require('./core/db.js');