From e5933aacc2b1c8aafbe68ed7b51580bba6921ba9 Mon Sep 17 00:00:00 2001 From: Minz Date: Mon, 19 Jan 2026 08:47:21 +0100 Subject: [PATCH] WIP: receipt feature implementation --- commands/utility/receiptCommands.js | 52 +++++++++++++++++++++++++++++ core/db.js | 22 ++++++++++++ deploy-commands.js | 4 +-- events/receiptsMessageCreate.js | 46 +++++++++++++++++++++++++ timers/receiptTimer.js | 26 +++++++++++---- 5 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 commands/utility/receiptCommands.js create mode 100644 events/receiptsMessageCreate.js diff --git a/commands/utility/receiptCommands.js b/commands/utility/receiptCommands.js new file mode 100644 index 0000000..a205477 --- /dev/null +++ b/commands/utility/receiptCommands.js @@ -0,0 +1,52 @@ +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')) + ) + .addSubcommand(subcommand => + subcommand + .setName('show') + .setDescription('not implemented') + ), + async execute(interaction) { + console.log('budget command entrypoint'); + let db = await interaction.client.localDB; + 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 budget = interaction.options.get('amount').value + db.prepare(`INSERT OR REPLACE INTO bot_config (id, weekly_budget) VALUES (1, ?)`).run(budget); + await interaction.reply(`Budget set to ${budget}`); + break; + case 'show': + console.log('budget command show'); + await interaction.reply('Not implemented'); + break + default: + await interaction.reply(`Sub mismatch. Switching on ${interaction.options.getSubcommand()}`); + break; + } + }, +}; \ No newline at end of file diff --git a/core/db.js b/core/db.js index 2bbacb8..76b1ba4 100644 --- a/core/db.js +++ b/core/db.js @@ -34,6 +34,28 @@ async function initDB(db) { lastfm_name TEXT ); `); + await db.exec(` + CREATE TABLE IF NOT EXISTS bot_config ( + id INTEGER PRIMARY KEY CHECK (id = 1), + weekly_budget REAL DEFAULT 0, + exchange_rate_eur_kr REAL DEFAULT 0, + last_budget_notification_date TEXT + ); + `); + await db.exec(` + CREATE TABLE IF NOT EXISTS grocery_budgets ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + discord_id VARCHAR(50) NOT NULL, + budget_spent REAL, + total_spent REAL + ); + `); + + // Optional: Initialize the row if it doesn't exist yet + await db.exec(` + INSERT OR IGNORE INTO bot_config (id, weekly_budget, last_budget_notification_date) + VALUES (1, 0, NULL); + `); console.log('[DATABASE] Created new DB table'); } diff --git a/deploy-commands.js b/deploy-commands.js index 2a06395..4694bd0 100644 --- a/deploy-commands.js +++ b/deploy-commands.js @@ -36,14 +36,14 @@ const rest = new REST().setToken(token); // and deploy your commands! (async () => { try { - console.log(`[GUILD] Started refreshing ${guildCommands.length} application (/) commands.`); + console.log(`[GUILD] Started refreshing ${guildCommands.length} Guild (/) commands.`); let data = await rest.put( Routes.applicationGuildCommands(clientId, guildId), { body: guildCommands }, ); - console.log(`[GUILD] Successfully reloaded ${data.length} application (/) commands.`); + console.log(`[GUILD] Successfully reloaded ${data.length} Guild (/) commands.`); console.log(`[GLOBAL] Started refreshing ${globalCommands.length} application (/) commands.`); diff --git a/events/receiptsMessageCreate.js b/events/receiptsMessageCreate.js new file mode 100644 index 0000000..93989dd --- /dev/null +++ b/events/receiptsMessageCreate.js @@ -0,0 +1,46 @@ +const { Events, channelLink, discordSort } = require('discord.js'); + +module.exports = { + name: Events.MessageCreate, + async execute(message) { + if (message.author.bot) return; + + if(message.channel.id === '1462060674766344370') { + + let reply = ""; + let db = await message.client.localDB; + const receiptsChannel = message.channel; + const authorId = message.author.id; + + let budgets = await db.prepare(`SELECT * FROM grocery_budgets WHERE discord_id = ?`).get(authorId); + let weeklyBudget = await (await db.prepare(`SELECT weekly_budget FROM bot_config`).get()).weekly_budget; + + console.log(weeklyBudget, budgets); + if (budgets === undefined) { + console.log(`No budget row in db for ${authorId}`); + db.prepare(`INSERT OR REPLACE INTO grocery_budgets (discord_id, budget_spent, total_spent) VALUES (?,0,0)`).run(authorId); + } + + let matches = []; + let lines = message.content.split("\n"); + let additionalSpent = 0.0; + lines.forEach(line => {0 + if (line.startsWith('-')) { + console.log(`Matching on line ${line}`); + const regex = /-(?=[0-9])([0-9]*,?[0-9]*)/m; + let match = regex.exec(line) + console.log(match); + additionalSpent = additionalSpent + parseFloat(match[1]); + } + }); + + if (additionalSpent > 0) { + db.prepare(`UPDATE grocery_budgets SET budget_spent = ?, total_spent = ? WHERE discord_id = ?`).run(budgets.budget_spent + additionalSpent, budgets.total_spent + additionalSpent, authorId); + let remainingBudget = weeklyBudget - (budgets.budget_spent + additionalSpent); + + reply = `${message.author.globalName} spent ${additionalSpent} of their budget.\nThey have ${remainingBudget} remaining.`; + await receiptsChannel.send(reply); + } + } + }, +}; \ No newline at end of file diff --git a/timers/receiptTimer.js b/timers/receiptTimer.js index a4059de..af1a1ce 100644 --- a/timers/receiptTimer.js +++ b/timers/receiptTimer.js @@ -1,6 +1,6 @@ module.exports = { - timeout: 1000, - immediate: false, + timeout: 1000, + immediate: false, name: 'Receipt Day Announcements', data: { channelId: '1462060674766344370', @@ -17,17 +17,29 @@ module.exports = { year: 'numeric' }); const channel = await client.channels.fetch(timer.data.channelId); - if (channel) await channel.send(`Today is ${currentDate}`); + await channel.send(`${currentDate}`); + if (currentDate.startsWith('Sunday')) { + const response = await fetch(`https://api.frankfurter.dev/v1/latest?amount=1&from=EUR&to=SEK`); + + if (!response.ok) await channel.send(`Failed to fetch exchange rate:\n${response.statusText}`); + let db = await client.localDB; + const data = await response.json(); + db.prepare(`UPDATE bot_config SET exchange_rate_eur_kr = ? WHERE id = 1`).run(data.rates.SEK); + let weeklyBudget = await (await db.prepare(`SELECT weekly_budget FROM bot_config`).get()).weekly_budget; + await channel.send(`Fetched new exchange rate:\n${JSON.stringify(data)}`); + db.prepare(`UPDATE grocery_budgets SET budget_spent = 0`).run(); + await channel.send(`Reset weekly budget to ${weeklyBudget}EUR / ${weeklyBudget*data.rates.SEK}SEK`); + } } catch (error) { console.error('[TIMER] Error:', error); } finally { // 2. Schedule the NEXT tick manually - this.scheduleNext(client, timer); + await this.scheduleNext(client, timer); } }, - scheduleNext(client, timer) { + async scheduleNext(client, timer) { const now = new Date(); const next = new Date(); @@ -40,11 +52,13 @@ module.exports = { const delay = next.getTime() - now.getTime(); if (timer.instance) clearTimeout(timer.instance); - + timer.instance = setTimeout(() => { this.tick(client, timer); }, delay); + const channel = await client.channels.fetch(timer.data.channelId); + if (channel) await channel.send(`Next message scheduled for: ${next.toLocaleString()} with ms delta of ${delay} / ${delay/3600000}`); console.log(`[TIMER] Next message scheduled for: ${next.toLocaleString()}`); } }; \ No newline at end of file