170 lines
8.1 KiB
JavaScript
170 lines
8.1 KiB
JavaScript
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
|
|
|
|
module.exports = {
|
|
category: 'utility',
|
|
global: true,
|
|
data: new SlashCommandBuilder()
|
|
.setName('budget')
|
|
.setDescription('The thing')
|
|
.addSubcommand(subcommand =>
|
|
subcommand
|
|
.setName('set')
|
|
.setDescription('Set weekly budget')
|
|
.addIntegerOption(option =>
|
|
option.setName('amount')
|
|
.setRequired(true)
|
|
.setDescription('Amount as int'))
|
|
.addBooleanOption(option =>
|
|
option.setName('override')
|
|
.setRequired(false)
|
|
.setDescription('Override budget of current period'))
|
|
)
|
|
.addSubcommand(subcommand =>
|
|
subcommand
|
|
.setName('view')
|
|
.setDescription('View your grocery spendings for the current or previous week')
|
|
.addUserOption(option =>
|
|
option.setName('user')
|
|
.setRequired(false)
|
|
.setDescription('User to view budget for'))
|
|
.addBooleanOption(option =>
|
|
option
|
|
.setName('lastweek')
|
|
.setDescription('Show spendings from the previous budget cycle instead of the current one')
|
|
.setRequired(false)
|
|
)
|
|
)
|
|
.addSubcommand(subcommand =>
|
|
subcommand
|
|
.setName('stats')
|
|
.setDescription('View detailed spending and savings statistics')
|
|
.addUserOption(option =>
|
|
option.setName('user')
|
|
.setRequired(false)
|
|
.setDescription('User to view stats for'))
|
|
),
|
|
async execute(interaction) {
|
|
console.log('budget command entrypoint');
|
|
let db = await interaction.client.localDB;
|
|
let targetUser = interaction.options.getUser('user') ?? interaction.user;
|
|
let discordId = interaction.member.id;
|
|
console.log(db, discordId);
|
|
let allowed = ['372115788498468864', '222457277708369928'].includes(discordId);
|
|
console.log(allowed);
|
|
if (!allowed) {
|
|
console.log('budget command denied');
|
|
await interaction.reply('This command may only be invoked by Miffy!');
|
|
return;
|
|
}
|
|
console.log(`Switcching on sub ${interaction.options.getSubcommand()}`);
|
|
switch (interaction.options.getSubcommand()) {
|
|
case 'set':
|
|
console.log('budget command set');
|
|
let override = interaction.options.getBoolean('override') ?? false;
|
|
let budget = interaction.options.get('amount').value;
|
|
db.prepare(`INSERT OR REPLACE INTO bot_config (id, weekly_budget) VALUES (1, ?)`).run(budget);
|
|
if (override) {
|
|
db.prepare(`
|
|
UPDATE weekly_budgets
|
|
SET budget_amount = ?
|
|
WHERE id = (
|
|
SELECT id FROM weekly_budgets
|
|
WHERE date('now', 'localtime') BETWEEN start_date AND end_date
|
|
ORDER BY id DESC
|
|
LIMIT 1
|
|
)`).run(budget);
|
|
|
|
}
|
|
await interaction.reply(`Budget set for ${targetUser.username} to ${Number(budget).toFixed(2)}.`);
|
|
break;
|
|
case 'view':
|
|
const lastWeek = interaction.options.getBoolean('lastweek') ?? false;
|
|
const currentBudget = db.prepare(`
|
|
SELECT id, budget_amount, exchange_rate, start_date, end_date
|
|
FROM weekly_budgets
|
|
WHERE ${lastWeek ? "start_date < date('now', 'localtime')" : "date('now', 'localtime') BETWEEN start_date AND end_date"}
|
|
ORDER BY start_date DESC
|
|
LIMIT 1
|
|
`).get();
|
|
|
|
if (!currentBudget) {
|
|
await interaction.reply("No budget period found.");
|
|
return;
|
|
}
|
|
|
|
const userSpendings = db.prepare(`
|
|
SELECT amount, message_raw
|
|
FROM grocery_spendings
|
|
WHERE budget_id = ? AND discord_id = ?
|
|
`).all(currentBudget.id, targetUser.id);
|
|
|
|
const totalSpentEur = userSpendings.reduce((acc, s) => {
|
|
return acc + s.amount;
|
|
}, 0);
|
|
|
|
const remainingEur = currentBudget.budget_amount - totalSpentEur;
|
|
const remainingSek = remainingEur * currentBudget.exchange_rate;
|
|
|
|
const todayTimestamp = Math.floor(Date.now() / 1000);
|
|
const endTimestamp = Math.floor(new Date(`${currentBudget.end_date}T23:59:59`).getTime() / 1000);
|
|
|
|
const budgetEmbed = new EmbedBuilder()
|
|
.setColor(remainingEur > 0 ? 0x00ff00 : 0xff0000)
|
|
.setTitle(`${targetUser.globalName ?? targetUser.username} Budget as of <t:${todayTimestamp}:F>`)
|
|
.setDescription(`Period: \`${currentBudget.start_date}\` to \`${currentBudget.end_date}\`\nReset <t:${endTimestamp}:R>`)
|
|
.addFields(
|
|
{ name: 'Spent', value: `${totalSpentEur.toFixed(2)} €\n${(totalSpentEur * currentBudget.exchange_rate).toFixed(2)} kr`, inline: true },
|
|
{ name: 'Remaining', value: `${remainingEur.toFixed(2)} €\n${remainingSek.toFixed(2)} kr`, inline: true },
|
|
)
|
|
.setFooter({ text: `Exchange rate this week: 1 € = ${currentBudget.exchange_rate.toFixed(2)} kr` })
|
|
.setTimestamp();
|
|
|
|
await interaction.reply({ embeds: [budgetEmbed] });
|
|
break
|
|
case 'stats':
|
|
const allBudgets = db.prepare(`
|
|
SELECT id, budget_amount, exchange_rate, start_date, end_date
|
|
FROM weekly_budgets
|
|
ORDER BY start_date DESC
|
|
LIMIT 4
|
|
`).all();
|
|
|
|
let statsDescription = "";
|
|
let cumulativeSavingsEur = 0;
|
|
|
|
for (const budgetPeriod of allBudgets) {
|
|
const periodSpendings = db.prepare(`
|
|
SELECT amount
|
|
FROM grocery_spendings
|
|
WHERE budget_id = ? AND discord_id = ?
|
|
`).all(budgetPeriod.id, targetUser.id);
|
|
|
|
const totalSpentInPeriodEur = periodSpendings.reduce((acc, s) => acc + s.amount, 0);
|
|
const savedInPeriodEur = budgetPeriod.budget_amount - totalSpentInPeriodEur;
|
|
cumulativeSavingsEur += savedInPeriodEur;
|
|
|
|
statsDescription += `**Period: ${budgetPeriod.start_date} - ${budgetPeriod.end_date}**\n`;
|
|
statsDescription += `Budget: ${budgetPeriod.budget_amount.toFixed(2)}€\n`;
|
|
statsDescription += `Spent: ${totalSpentInPeriodEur.toFixed(2)}€ (${(totalSpentInPeriodEur * budgetPeriod.exchange_rate).toFixed(2)} kr)\n`;
|
|
statsDescription += `Saved: ${savedInPeriodEur.toFixed(2)}€ (${(savedInPeriodEur * budgetPeriod.exchange_rate).toFixed(2)} kr)\n`;
|
|
statsDescription += `Cumulative: ${cumulativeSavingsEur.toFixed(2)}€\n\n`;
|
|
}
|
|
|
|
if (allBudgets.length === 0) {
|
|
statsDescription = "No budget periods on record.";
|
|
}
|
|
|
|
const statsEmbed = new EmbedBuilder()
|
|
.setColor(0x0099ff)
|
|
.setTitle(`${targetUser.globalName ?? targetUser.username}'s Budget Statistics`)
|
|
.setDescription(statsDescription)
|
|
.setTimestamp();
|
|
|
|
await interaction.reply({ embeds: [statsEmbed] });
|
|
break;
|
|
default:
|
|
await interaction.reply(`Sub mismatch. Switching on ${interaction.options.getSubcommand()}`);
|
|
break;
|
|
}
|
|
},
|
|
}; |