diff --git a/commands/utility/receiptCommands.js b/commands/receipts/receiptCommands.js similarity index 74% rename from commands/utility/receiptCommands.js rename to commands/receipts/receiptCommands.js index a205477..5e2a588 100644 --- a/commands/utility/receiptCommands.js +++ b/commands/receipts/receiptCommands.js @@ -14,11 +14,21 @@ module.exports = { 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('show') - .setDescription('not implemented') + .setName('spendings') + .setDescription('View your grocery spendings for the current or previous week') + .addBooleanOption(option => + option + .setName('lastweek') + .setDescription('Show spendings from the previous budget cycle instead of the current one') + .setRequired(false) + ) ), async execute(interaction) { console.log('budget command entrypoint'); @@ -31,7 +41,7 @@ module.exports = { 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': @@ -40,7 +50,7 @@ module.exports = { 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': + case 'spendings': console.log('budget command show'); await interaction.reply('Not implemented'); break diff --git a/core/db.js b/core/db.js index 76b1ba4..0936523 100644 --- a/core/db.js +++ b/core/db.js @@ -38,23 +38,39 @@ async function initDB(db) { 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 + last_date_msg_receipts DATE NOT NULL DEFAULT CURRENT_TIMESTAMP ); `); 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 + CREATE TABLE IF NOT EXISTS grocery_spendings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + discord_id VARCHAR(50) NOT NULL, + message_id VARCHAR(50) NOT NULL, + message_raw TEXT DEFAULT "-", + amount DECIMAL(10, 2) NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + + budget_id INTEGER, + FOREIGN KEY (budget_id) REFERENCES weekly_budgets(id) ); `); + await db.exec(` + CREATE TABLE IF NOT EXISTS weekly_budgets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + start_date DATE NOT NULL, + end_date DATE NOT NULL, + budget_amount DECIMAL(10, 2) NOT NULL, + exchange_rate DECIMAL(10, 6) NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT date_check CHECK (end_date > start_date) + ); + CREATE INDEX IF NOT EXISTS idx_budget_dates ON weekly_budgets (start_date, end_date); + `); + - // 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); + INSERT OR IGNORE INTO bot_config (id, weekly_budget) + VALUES (1, 10); `); console.log('[DATABASE] Created new DB table'); } diff --git a/events/receiptsMessageCreate.js b/events/receiptsMessageCreate.js index 93989dd..04e035e 100644 --- a/events/receiptsMessageCreate.js +++ b/events/receiptsMessageCreate.js @@ -6,19 +6,40 @@ module.exports = { if (message.author.bot) return; if(message.channel.id === '1462060674766344370') { + const CURRENCY_MAP = { + "222457277708369928": "EUR", // Minz + "372115788498468864": "SEK", // Miffy + }; 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); + + + const currentBudget = db.prepare(` + SELECT id, exchange_rate, budget_amount FROM weekly_budgets + WHERE date('now', 'localtime') BETWEEN start_date AND end_date + ORDER BY created_at DESC + LIMIT 1 + `).get(); + + if (message.content === "fuck") { + + let lastSpend = db.prepare(` + SELECT id, amount, message_raw, created_at + FROM grocery_spendings + WHERE discord_id = ? AND budget_id = ? + ORDER BY created_at DESC, id DESC + LIMIT 1 + `).get(authorId, currentBudget.id); + if (lastSpend) { + db.prepare(`DELETE FROM grocery_spendings WHERE id = ?`).run(lastSpend.id); + await message.reply(`Your last spending of ${lastSpend.amount.toFixed(2)}€ has been deleted.`); + } else { + await message.reply("You don't have any entries within the current budgeting period!"); + } + return; } let matches = []; @@ -27,19 +48,56 @@ module.exports = { lines.forEach(line => {0 if (line.startsWith('-')) { console.log(`Matching on line ${line}`); - const regex = /-(?=[0-9])([0-9]*,?[0-9]*)/m; + const regex = /-(?=[0-9])([0-9]*[.,]?[0-9]*)/m; let match = regex.exec(line) console.log(match); - additionalSpent = additionalSpent + parseFloat(match[1]); + if (match) { + additionalSpent = additionalSpent + parseFloat(match[1].replace(',', '.')); + } } }); - 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); + if (additionalSpent > 0) { + + + const currency = CURRENCY_MAP[authorId] || "EUR" + switch (currency) { + case "SEK": + additionalSpent = additionalSpent / currentBudget.exchange_rate; + break; + default: + break; + } + + db.prepare(` + INSERT INTO grocery_spendings ( + discord_id, + message_id, + message_raw, + amount, + budget_id + ) VALUES (?, ?, ?, ?, ?) + `).run( + authorId, + message.id, + message.content, + additionalSpent, + currentBudget.id + ); + + const allSpendings = db.prepare(` + SELECT amount + FROM grocery_spendings + WHERE budget_id = ? AND discord_id = ? + `).all(currentBudget.id, authorId); + + const totalSpent = allSpendings.reduce((acc, entry) => { + return acc + entry.amount; + }, 0); + + reply = `${message.author.globalName} spent ${additionalSpent}€ / ${(additionalSpent * currentBudget.exchange_rate).toFixed(2)} kr of their budget.\nThey have ${currentBudget.budget_amount - totalSpent}€ / ${((currentBudget.budget_amount - totalSpent) * currentBudget.exchange_rate).toFixed(2)} kr remaining.`; + await message.reply(reply); } } }, diff --git a/notes b/notes new file mode 100644 index 0000000..e69de29 diff --git a/timers/receiptTimer.js b/timers/receiptTimer.js index af1a1ce..380ab42 100644 --- a/timers/receiptTimer.js +++ b/timers/receiptTimer.js @@ -8,29 +8,69 @@ module.exports = { targetMinute: 1 }, async tick(client, timer) { + try { - // 1. Send the message - const currentDate = new Date().toLocaleDateString('en-GB', { - weekday: 'long', - day: '2-digit', - month: '2-digit', - year: 'numeric' - }); + const today = new Date(); + const lastMonday = new Date(today); + lastMonday.setDate(today.getDate() - ((today.getDay() === 0) ? 6 : today.getDay() - 1)); + const startDate = lastMonday.toISOString().split('T')[0]; + + const nextSunday = new Date(lastMonday); + nextSunday.setDate(lastMonday.getDate() + 6); + const endDate = nextSunday.toISOString().split('T')[0]; + // 0 1 2 3 4 5 6 + // S M T W T F S + let db = await client.localDB; const channel = await client.channels.fetch(timer.data.channelId); - await channel.send(`${currentDate}`); - if (currentDate.startsWith('Sunday')) { + + const config = await (await db.prepare(`SELECT weekly_budget, last_date_msg_receipts FROM bot_config`).get()); + const existingBudget = db.prepare(`SELECT id FROM weekly_budgets WHERE start_date = ?`).get(startDate); + + if (!existingBudget) { + console.log(`No budget found for week starting ${startDate}.`); 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(`Fetched new exchange rate:\n${JSON.stringify(data)}`); await channel.send(`Reset weekly budget to ${weeklyBudget}EUR / ${weeklyBudget*data.rates.SEK}SEK`); + + + db.prepare(` + INSERT INTO weekly_budgets (start_date, end_date, budget_amount, exchange_rate) + VALUES (?, ?, ?, ?) + `).run(startDate, endDate, config.weekly_budget, data.rates.SEK); + } else { + console.log(`budget found for week starting ${startDate}.`); } + const notification = db.prepare(` + SELECT EXISTS ( + SELECT 1 FROM bot_config + WHERE date(last_date_msg_receipts) = date('now', 'localtime') + ) as was_sent_today + `).get(); + + if (!notification.was_sent_today) { + const currentDate = new Date().toLocaleDateString('en-GB', { + weekday: 'long', + day: '2-digit', + month: '2-digit', + year: 'numeric' + }); + await channel.send(`${currentDate}`); + console.log("No notification today, sending..."); + db.prepare(` + UPDATE bot_config + SET last_date_msg_receipts = CURRENT_TIMESTAMP + WHERE id = 1 + `).run(); + } else { + console.log("Notification already sent"); + } + + } catch (error) { console.error('[TIMER] Error:', error); } finally { @@ -58,7 +98,7 @@ module.exports = { }, 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}`); + //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