Fix receipts bugs: budget queries, undo/transfer desync, timer spam
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
|
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
|
||||||
|
const { HOUSEHOLD_IDS } = require('../../core/constants.js');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
category: 'utility',
|
category: 'utility',
|
||||||
@@ -44,46 +45,42 @@ module.exports = {
|
|||||||
.setDescription('User to view stats for'))
|
.setDescription('User to view stats for'))
|
||||||
),
|
),
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
console.log('budget command entrypoint');
|
|
||||||
let db = await interaction.client.localDB;
|
let db = await interaction.client.localDB;
|
||||||
let targetUser = interaction.options.getUser('user') ?? interaction.user;
|
let targetUser = interaction.options.getUser('user') ?? interaction.user;
|
||||||
let discordId = interaction.member.id;
|
let discordId = interaction.member.id;
|
||||||
console.log(db, discordId);
|
let allowed = [HOUSEHOLD_IDS.MIFFY, HOUSEHOLD_IDS.MINZ].includes(discordId);
|
||||||
let allowed = ['372115788498468864', '222457277708369928'].includes(discordId);
|
|
||||||
console.log(allowed);
|
|
||||||
if (!allowed) {
|
if (!allowed) {
|
||||||
console.log('budget command denied');
|
|
||||||
await interaction.reply('This command may only be invoked by Miffy!');
|
await interaction.reply('This command may only be invoked by Miffy!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`Switcching on sub ${interaction.options.getSubcommand()}`);
|
|
||||||
switch (interaction.options.getSubcommand()) {
|
switch (interaction.options.getSubcommand()) {
|
||||||
case 'set':
|
case 'set':
|
||||||
console.log('budget command set');
|
|
||||||
let override = interaction.options.getBoolean('override') ?? false;
|
let override = interaction.options.getBoolean('override') ?? false;
|
||||||
let budget = interaction.options.get('amount').value;
|
let budget = interaction.options.get('amount').value;
|
||||||
db.prepare(`INSERT OR REPLACE INTO bot_config (id, weekly_budget) VALUES (1, ?)`).run(budget);
|
db.prepare(`INSERT OR REPLACE INTO bot_config (id, weekly_budget) VALUES (1, ?)`).run(budget);
|
||||||
|
let overrideApplied = true;
|
||||||
if (override) {
|
if (override) {
|
||||||
db.prepare(`
|
const result = db.prepare(`
|
||||||
UPDATE weekly_budgets
|
UPDATE weekly_budgets
|
||||||
SET budget_amount = ?
|
SET budget_amount = ?
|
||||||
WHERE id = (
|
WHERE id = (
|
||||||
SELECT id FROM weekly_budgets
|
SELECT id FROM weekly_budgets
|
||||||
WHERE date('now', 'localtime') BETWEEN start_date AND end_date
|
WHERE date('now', 'localtime') BETWEEN start_date AND end_date
|
||||||
ORDER BY id DESC
|
ORDER BY id DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)`).run(budget);
|
)`).run(budget);
|
||||||
|
overrideApplied = result.changes > 0;
|
||||||
}
|
}
|
||||||
await interaction.reply(`Budget set for ${targetUser.username} to ${Number(budget).toFixed(2)}.`);
|
const overrideNote = override && !overrideApplied ? " (no active budget period to override this week)" : "";
|
||||||
|
await interaction.reply(`Budget set for ${targetUser.username} to ${Number(budget).toFixed(2)}.${overrideNote}`);
|
||||||
break;
|
break;
|
||||||
case 'view':
|
case 'view':
|
||||||
const lastWeek = interaction.options.getBoolean('lastweek') ?? false;
|
const lastWeek = interaction.options.getBoolean('lastweek') ?? false;
|
||||||
const currentBudget = db.prepare(`
|
const currentBudget = db.prepare(`
|
||||||
SELECT id, budget_amount, exchange_rate, start_date, end_date
|
SELECT id, budget_amount, exchange_rate, start_date, end_date
|
||||||
FROM weekly_budgets
|
FROM weekly_budgets
|
||||||
WHERE ${lastWeek ? "start_date < date('now', 'localtime')" : "date('now', 'localtime') BETWEEN start_date AND end_date"}
|
WHERE ${lastWeek ? "end_date < date('now', 'localtime')" : "date('now', 'localtime') BETWEEN start_date AND end_date"}
|
||||||
ORDER BY start_date DESC
|
ORDER BY start_date DESC, created_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`).get();
|
`).get();
|
||||||
|
|
||||||
@@ -123,11 +120,11 @@ module.exports = {
|
|||||||
break
|
break
|
||||||
case 'stats':
|
case 'stats':
|
||||||
const allBudgets = db.prepare(`
|
const allBudgets = db.prepare(`
|
||||||
SELECT id, budget_amount, exchange_rate, start_date, end_date
|
SELECT id, budget_amount, exchange_rate, start_date, end_date
|
||||||
FROM weekly_budgets
|
FROM weekly_budgets
|
||||||
ORDER BY start_date ASC
|
ORDER BY start_date DESC
|
||||||
LIMIT 4
|
LIMIT 4
|
||||||
`).all();
|
`).all().reverse();
|
||||||
|
|
||||||
let statsDescription = "";
|
let statsDescription = "";
|
||||||
let cumulativeSavingsEur = 0;
|
let cumulativeSavingsEur = 0;
|
||||||
|
|||||||
6
core/constants.js
Normal file
6
core/constants.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
HOUSEHOLD_IDS: {
|
||||||
|
MINZ: '222457277708369928',
|
||||||
|
MIFFY: '372115788498468864',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const { Events, channelLink, discordSort } = require('discord.js');
|
const { Events, channelLink, discordSort } = require('discord.js');
|
||||||
|
const { HOUSEHOLD_IDS } = require('../core/constants.js');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: Events.MessageCreate,
|
name: Events.MessageCreate,
|
||||||
@@ -8,8 +9,8 @@ module.exports = {
|
|||||||
const receiptsChannelId = process.env.NODE_ENV === 'development' ? '1468186493251227658' : '1462060674766344370';
|
const receiptsChannelId = process.env.NODE_ENV === 'development' ? '1468186493251227658' : '1462060674766344370';
|
||||||
if(message.channel.id === receiptsChannelId) {
|
if(message.channel.id === receiptsChannelId) {
|
||||||
const CURRENCY_MAP = {
|
const CURRENCY_MAP = {
|
||||||
"222457277708369928": "EUR", // Minz
|
[HOUSEHOLD_IDS.MINZ]: "EUR",
|
||||||
"372115788498468864": "SEK", // Miffy
|
[HOUSEHOLD_IDS.MIFFY]: "SEK",
|
||||||
};
|
};
|
||||||
|
|
||||||
let reply = "";
|
let reply = "";
|
||||||
@@ -25,12 +26,17 @@ module.exports = {
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
`).get();
|
`).get();
|
||||||
|
|
||||||
|
if (!currentBudget) {
|
||||||
|
await message.reply("No active budget period found for this week yet - try again in a moment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (message.content === "fuck") {
|
if (message.content === "fuck") {
|
||||||
let lastSpend = db.prepare(`
|
let lastSpend = db.prepare(`
|
||||||
SELECT id, amount, message_raw, created_at
|
SELECT id, amount, message_raw, created_at
|
||||||
FROM grocery_spendings
|
FROM grocery_spendings
|
||||||
WHERE discord_id = ? AND budget_id = ?
|
WHERE discord_id = ? AND budget_id = ? AND is_transfer = 0
|
||||||
ORDER BY created_at DESC, id DESC
|
ORDER BY created_at DESC, id DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`).get(authorId, currentBudget.id);
|
`).get(authorId, currentBudget.id);
|
||||||
if (lastSpend) {
|
if (lastSpend) {
|
||||||
@@ -45,7 +51,7 @@ module.exports = {
|
|||||||
const transferRegex = /^transfer\s+(\d+(?:[.,]\d+)?)$/i;
|
const transferRegex = /^transfer\s+(\d+(?:[.,]\d+)?)$/i;
|
||||||
const transferMatch = message.content.match(transferRegex);
|
const transferMatch = message.content.match(transferRegex);
|
||||||
if (transferMatch) {
|
if (transferMatch) {
|
||||||
if (authorId !== "222457277708369928" && authorId !== "372115788498468864") {
|
if (authorId !== HOUSEHOLD_IDS.MINZ && authorId !== HOUSEHOLD_IDS.MIFFY) {
|
||||||
await message.reply("Only Minz and Miffy can use the transfer command.");
|
await message.reply("Only Minz and Miffy can use the transfer command.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -53,11 +59,11 @@ module.exports = {
|
|||||||
let amount = parseFloat(transferMatch[1].replace(',', '.'));
|
let amount = parseFloat(transferMatch[1].replace(',', '.'));
|
||||||
|
|
||||||
|
|
||||||
const targetId = authorId === "222457277708369928" ? "372115788498468864" : "222457277708369928";
|
const targetId = authorId === HOUSEHOLD_IDS.MINZ ? HOUSEHOLD_IDS.MIFFY : HOUSEHOLD_IDS.MINZ;
|
||||||
|
|
||||||
const insertSpending = db.prepare(`
|
const insertSpending = db.prepare(`
|
||||||
INSERT INTO grocery_spendings (discord_id, message_id, message_raw, amount, budget_id)
|
INSERT INTO grocery_spendings (discord_id, message_id, message_raw, amount, budget_id, is_transfer)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, 1)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const runTransfer = db.transaction(() => {
|
const runTransfer = db.transaction(() => {
|
||||||
@@ -67,18 +73,17 @@ module.exports = {
|
|||||||
|
|
||||||
runTransfer();
|
runTransfer();
|
||||||
|
|
||||||
const targetName = authorId === "222457277708369928" ? "Miffy" : "Minz";
|
const targetName = authorId === HOUSEHOLD_IDS.MINZ ? "Miffy" : "Minz";
|
||||||
await message.reply(`Transferred ${amount.toFixed(2)}€ to ${targetName}.`);
|
await message.reply(`Transferred ${amount.toFixed(2)}€ to ${targetName}.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let matches = [];
|
|
||||||
let lines = message.content.split("\n");
|
let lines = message.content.split("\n");
|
||||||
let additionalSpent = 0.0;
|
let additionalSpent = 0.0;
|
||||||
lines.forEach(line => {0
|
lines.forEach(line => {
|
||||||
if (line.startsWith('-')) {
|
if (line.startsWith('-')) {
|
||||||
console.log(`Matching on line ${line}`);
|
console.log(`Matching on line ${line}`);
|
||||||
const regex = /-(?=[0-9])([0-9]*[.,]?[0-9]*)/m;
|
const regex = /-\s*([0-9]+(?:[.,][0-9]+)?)/;
|
||||||
let match = regex.exec(line)
|
let match = regex.exec(line)
|
||||||
console.log(match);
|
console.log(match);
|
||||||
if (match) {
|
if (match) {
|
||||||
|
|||||||
10
migrations/02_receipts_fixes.js
Normal file
10
migrations/02_receipts_fixes.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
up: async ({ context: db }) => {
|
||||||
|
db.exec(`ALTER TABLE grocery_spendings ADD COLUMN is_transfer INTEGER NOT NULL DEFAULT 0;`);
|
||||||
|
db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_weekly_budgets_unique_start ON weekly_budgets (start_date);`);
|
||||||
|
},
|
||||||
|
down: async ({ context: db }) => {
|
||||||
|
db.exec(`DROP INDEX IF EXISTS idx_weekly_budgets_unique_start;`);
|
||||||
|
db.exec(`ALTER TABLE grocery_spendings DROP COLUMN is_transfer;`);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
const { formatDate } = require('../core/utils.js');
|
const { formatDate } = require('../core/utils.js');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
timeout: 10000,
|
// scheduleNext() below drives the real daily schedule via setTimeout;
|
||||||
|
// this is just a safety-net interval in case that chain ever breaks.
|
||||||
|
timeout: 24 * 60 * 60 * 1000,
|
||||||
immediate: true,
|
immediate: true,
|
||||||
name: 'Receipt Day Announcements',
|
name: 'Receipt Day Announcements',
|
||||||
data: {
|
data: {
|
||||||
@@ -26,30 +28,24 @@ module.exports = {
|
|||||||
let db = await client.localDB;
|
let db = await client.localDB;
|
||||||
const channel = await client.channels.fetch(timer.data.channelId);
|
const channel = await client.channels.fetch(timer.data.channelId);
|
||||||
|
|
||||||
const config = await (await db.prepare(`SELECT weekly_budget, last_date_msg_receipts FROM bot_config`).get());
|
const config = 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);
|
const existingBudget = db.prepare(`SELECT id FROM weekly_budgets WHERE start_date = ?`).get(startDate);
|
||||||
|
|
||||||
if (!existingBudget) {
|
if (!existingBudget) {
|
||||||
await channel.send(`Format date last monday ${lastMonday} as startDate Format date next sunday ${nextSunday} as endDate`);
|
|
||||||
await channel.send(`\`\`\`SELECT id FROM weekly_budgets WHERE start_date = ${startDate}\`\`\`
|
|
||||||
${JSON.stringify(existingBudget)}`);
|
|
||||||
console.log(`No budget found for week starting ${startDate}.`);
|
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`);
|
const response = await fetch(`https://api.frankfurter.dev/v1/latest?amount=1&from=EUR&to=SEK`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
if (!response.ok) await channel.send(`Failed to fetch exchange rate:\n${response.statusText}`);
|
await channel.send(`Failed to fetch exchange rate:\n${response.statusText}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
//await channel.send(`Fetched new exchange rate:\n${JSON.stringify(data)}`);
|
|
||||||
await channel.send(`Reset weekly budget to ${config.weekly_budget.toFixed(2)}€ / ${(config.weekly_budget * data.rates.SEK).toFixed(2)} kr`);
|
await channel.send(`Reset weekly budget to ${config.weekly_budget.toFixed(2)}€ / ${(config.weekly_budget * data.rates.SEK).toFixed(2)} kr`);
|
||||||
|
|
||||||
|
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
INSERT INTO weekly_budgets (start_date, end_date, budget_amount, exchange_rate)
|
INSERT INTO weekly_budgets (start_date, end_date, budget_amount, exchange_rate)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
`).run(startDate, endDate, config.weekly_budget, data.rates.SEK);
|
`).run(startDate, endDate, config.weekly_budget, data.rates.SEK);
|
||||||
|
|
||||||
await channel.send(`\`\`\`INSERT INTO weekly_budgets (start_date, end_date, budget_amount, exchange_rate)
|
|
||||||
VALUES (${startDate}, ${endDate}, ${config.weekly_budget}, ${data.rates.SEK})\`\`\``);
|
|
||||||
} else {
|
} else {
|
||||||
console.log(`budget found for week starting ${startDate}.`);
|
console.log(`budget found for week starting ${startDate}.`);
|
||||||
}
|
}
|
||||||
@@ -61,25 +57,7 @@ module.exports = {
|
|||||||
) as was_sent_today
|
) as was_sent_today
|
||||||
`).get();
|
`).get();
|
||||||
|
|
||||||
const debug = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
date(last_date_msg_receipts, 'localtime') AS last_date,
|
|
||||||
datetime('now', 'localtime') AS current_time_full,
|
|
||||||
(date(last_date_msg_receipts, 'localtime') = date('now', 'localtime')) AS was_sent_today
|
|
||||||
FROM bot_config
|
|
||||||
LIMIT 1;
|
|
||||||
`).get();
|
|
||||||
|
|
||||||
|
|
||||||
if (!notification.was_sent_today) {
|
if (!notification.was_sent_today) {
|
||||||
await channel.send(`\`\`\`SELECT
|
|
||||||
date(last_date_msg_receipts, 'localtime') AS last_date,
|
|
||||||
datetime('now', 'localtime') AS current_time_full,
|
|
||||||
(date(last_date_msg_receipts, 'localtime') = date('now', 'localtime')) AS was_sent_today
|
|
||||||
FROM bot_config
|
|
||||||
LIMIT 1;\`\`\`
|
|
||||||
|
|
||||||
${JSON.stringify(debug)}`);
|
|
||||||
const currentDate = new Date().toLocaleDateString('en-GB', {
|
const currentDate = new Date().toLocaleDateString('en-GB', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
|
|||||||
Reference in New Issue
Block a user