commit
9c324573a3
6 changed files with 331 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||||||
|
.idea |
||||||
|
node_modules |
||||||
|
.env |
||||||
|
out |
@ -0,0 +1,171 @@ |
|||||||
|
{ |
||||||
|
"name": "transactionparser", |
||||||
|
"version": "1.0.0", |
||||||
|
"lockfileVersion": 3, |
||||||
|
"requires": true, |
||||||
|
"packages": { |
||||||
|
"": { |
||||||
|
"name": "transactionparser", |
||||||
|
"version": "1.0.0", |
||||||
|
"license": "ISC", |
||||||
|
"dependencies": { |
||||||
|
"dotenv": "^16.4.5", |
||||||
|
"grammy": "^1.29.0" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/node": "^22.4.2", |
||||||
|
"typescript": "^5.5.4" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/@grammyjs/types": { |
||||||
|
"version": "3.13.0", |
||||||
|
"resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.13.0.tgz", |
||||||
|
"integrity": "sha512-Oyq6fBuVPyX6iWvxT/0SxJvNisC9GHUEkhZ60qJBHRmwNX4hIcOfhrNEahicn3K9SYyreGPVw3d9wlLRds83cw==", |
||||||
|
"license": "MIT" |
||||||
|
}, |
||||||
|
"node_modules/@types/node": { |
||||||
|
"version": "22.4.2", |
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.2.tgz", |
||||||
|
"integrity": "sha512-nAvM3Ey230/XzxtyDcJ+VjvlzpzoHwLsF7JaDRfoI0ytO0mVheerNmM45CtA0yOILXwXXxOrcUWH3wltX+7PSw==", |
||||||
|
"dev": true, |
||||||
|
"license": "MIT", |
||||||
|
"dependencies": { |
||||||
|
"undici-types": "~6.19.2" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/abort-controller": { |
||||||
|
"version": "3.0.0", |
||||||
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", |
||||||
|
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", |
||||||
|
"license": "MIT", |
||||||
|
"dependencies": { |
||||||
|
"event-target-shim": "^5.0.0" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">=6.5" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/debug": { |
||||||
|
"version": "4.3.6", |
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", |
||||||
|
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", |
||||||
|
"license": "MIT", |
||||||
|
"dependencies": { |
||||||
|
"ms": "2.1.2" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">=6.0" |
||||||
|
}, |
||||||
|
"peerDependenciesMeta": { |
||||||
|
"supports-color": { |
||||||
|
"optional": true |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/dotenv": { |
||||||
|
"version": "16.4.5", |
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", |
||||||
|
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", |
||||||
|
"license": "BSD-2-Clause", |
||||||
|
"engines": { |
||||||
|
"node": ">=12" |
||||||
|
}, |
||||||
|
"funding": { |
||||||
|
"url": "https://dotenvx.com" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/event-target-shim": { |
||||||
|
"version": "5.0.1", |
||||||
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", |
||||||
|
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", |
||||||
|
"license": "MIT", |
||||||
|
"engines": { |
||||||
|
"node": ">=6" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/grammy": { |
||||||
|
"version": "1.29.0", |
||||||
|
"resolved": "https://registry.npmjs.org/grammy/-/grammy-1.29.0.tgz", |
||||||
|
"integrity": "sha512-lj/6K6TGmVAdOpHj0PVFK7N37EGe76bpkbgvN+yqCqXYBIwuQosTe7qLhCls7/4pbDxf2+UVSqSXcOILgGGKWQ==", |
||||||
|
"license": "MIT", |
||||||
|
"dependencies": { |
||||||
|
"@grammyjs/types": "3.13.0", |
||||||
|
"abort-controller": "^3.0.0", |
||||||
|
"debug": "^4.3.4", |
||||||
|
"node-fetch": "^2.7.0" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": "^12.20.0 || >=14.13.1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/ms": { |
||||||
|
"version": "2.1.2", |
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", |
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", |
||||||
|
"license": "MIT" |
||||||
|
}, |
||||||
|
"node_modules/node-fetch": { |
||||||
|
"version": "2.7.0", |
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", |
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", |
||||||
|
"license": "MIT", |
||||||
|
"dependencies": { |
||||||
|
"whatwg-url": "^5.0.0" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": "4.x || >=6.0.0" |
||||||
|
}, |
||||||
|
"peerDependencies": { |
||||||
|
"encoding": "^0.1.0" |
||||||
|
}, |
||||||
|
"peerDependenciesMeta": { |
||||||
|
"encoding": { |
||||||
|
"optional": true |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/tr46": { |
||||||
|
"version": "0.0.3", |
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", |
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", |
||||||
|
"license": "MIT" |
||||||
|
}, |
||||||
|
"node_modules/typescript": { |
||||||
|
"version": "5.5.4", |
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", |
||||||
|
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", |
||||||
|
"dev": true, |
||||||
|
"license": "Apache-2.0", |
||||||
|
"bin": { |
||||||
|
"tsc": "bin/tsc", |
||||||
|
"tsserver": "bin/tsserver" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">=14.17" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/undici-types": { |
||||||
|
"version": "6.19.8", |
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", |
||||||
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", |
||||||
|
"dev": true, |
||||||
|
"license": "MIT" |
||||||
|
}, |
||||||
|
"node_modules/webidl-conversions": { |
||||||
|
"version": "3.0.1", |
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", |
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", |
||||||
|
"license": "BSD-2-Clause" |
||||||
|
}, |
||||||
|
"node_modules/whatwg-url": { |
||||||
|
"version": "5.0.0", |
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", |
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", |
||||||
|
"license": "MIT", |
||||||
|
"dependencies": { |
||||||
|
"tr46": "~0.0.3", |
||||||
|
"webidl-conversions": "^3.0.0" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
{ |
||||||
|
"name": "transactionparser", |
||||||
|
"version": "1.0.0", |
||||||
|
"description": "Telegram Bot to parse transactions and insert into Firefly III", |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "https://git.benjamin-kraft.eu/benjamin/TransactionParser.git" |
||||||
|
}, |
||||||
|
"keywords": [ |
||||||
|
"bot", |
||||||
|
"telegram", |
||||||
|
"finance" |
||||||
|
], |
||||||
|
"scripts": { |
||||||
|
"start": "node out/index.js" |
||||||
|
}, |
||||||
|
"author": "Benjamin Kraft", |
||||||
|
"license": "ISC", |
||||||
|
"devDependencies": { |
||||||
|
"@types/node": "^22.4.2", |
||||||
|
"typescript": "^5.5.4" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"dotenv": "^16.4.5", |
||||||
|
"grammy": "^1.29.0" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,115 @@ |
|||||||
|
import {Bot} from "grammy"; |
||||||
|
import {BotCommand, User} from "@grammyjs/types" |
||||||
|
import {configDotenv} from "dotenv"; |
||||||
|
|
||||||
|
configDotenv() |
||||||
|
|
||||||
|
|
||||||
|
const botToken = process.env["BOT_TOKEN"]!; |
||||||
|
const fireflyToken = process.env["FIREFLY_TOKEN"]!; |
||||||
|
const fireflyURL = process.env["FIREFLY_URL"]!; |
||||||
|
const sourceAccountId = process.env["SOURCE_ACCOUNT_ID"]!; |
||||||
|
const expenseAccountId = process.env["EXPENSE_ACCOUNT_ID"]!; |
||||||
|
|
||||||
|
const bot = new Bot(botToken); |
||||||
|
|
||||||
|
function isSenderAllowed(user: User){ |
||||||
|
return user.username === process.env["TELEGRAM_USER"]; |
||||||
|
} |
||||||
|
|
||||||
|
async function fetchBudgets(){ |
||||||
|
const budgets: Record<string, string> = {}; |
||||||
|
|
||||||
|
const res = await fetch(`${fireflyURL}/api/v1/budgets`, { |
||||||
|
method: "GET", |
||||||
|
headers: [ |
||||||
|
["Authorization", `Bearer ${fireflyToken}`], |
||||||
|
["Accept", "application/vnd.api+json"] |
||||||
|
] |
||||||
|
}); |
||||||
|
const result = await res.json(); |
||||||
|
for (const budget of result.data) |
||||||
|
budgets[budget.id] = budget.attributes.name; |
||||||
|
|
||||||
|
console.log("Budgets: ", budgets); |
||||||
|
|
||||||
|
return budgets; |
||||||
|
} |
||||||
|
|
||||||
|
async function start(){ |
||||||
|
const budgets = await fetchBudgets(); |
||||||
|
|
||||||
|
let currentBudgetID = ""; |
||||||
|
|
||||||
|
const budgetCommands: BotCommand[] = [] |
||||||
|
for (const [id, name] of Object.entries(budgets)){ |
||||||
|
bot.command(`budget${name.toLowerCase()}`, async ctx => { |
||||||
|
if (!isSenderAllowed(ctx.message?.from!)) |
||||||
|
return; |
||||||
|
currentBudgetID = id; |
||||||
|
console.log(`Changed current Budget to ${budgets[id]}`) |
||||||
|
}); |
||||||
|
budgetCommands.push({ |
||||||
|
command: `budget${name.toLowerCase()}`, |
||||||
|
description: "Change budget" |
||||||
|
}); |
||||||
|
} |
||||||
|
budgetCommands.push({ |
||||||
|
command: "currentbudget", |
||||||
|
description: "Show currently selected budget" |
||||||
|
}); |
||||||
|
|
||||||
|
bot.command("currentbudget", async ctx => { |
||||||
|
if (!isSenderAllowed(ctx.message?.from!)) |
||||||
|
return; |
||||||
|
await ctx.reply(currentBudgetID || "No budget selected!"); |
||||||
|
}); |
||||||
|
|
||||||
|
await bot.api.setMyCommands(budgetCommands); |
||||||
|
|
||||||
|
bot.on("message", async ctx => { |
||||||
|
if (!isSenderAllowed(ctx.message.from!)) |
||||||
|
return; |
||||||
|
|
||||||
|
if (currentBudgetID === ""){ |
||||||
|
await ctx.reply("Select a budget first!"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const text = ctx.message.text!; |
||||||
|
const parts = text.split(" "); |
||||||
|
const amount = parts[0]; |
||||||
|
|
||||||
|
let description = parts.slice(1).join(" ") || budgets[currentBudgetID]; |
||||||
|
|
||||||
|
const res = await fetch(`${fireflyURL}/api/v1/transactions`, { |
||||||
|
method: "POST", |
||||||
|
headers: [ |
||||||
|
["Authorization", `Bearer ${fireflyToken}`], |
||||||
|
["Content-Type", "application/json"] |
||||||
|
], |
||||||
|
body: JSON.stringify({ |
||||||
|
transactions: [ |
||||||
|
{ |
||||||
|
type: "withdrawal", |
||||||
|
date: new Date().toISOString(), |
||||||
|
amount: amount, |
||||||
|
description: description, |
||||||
|
source_id: sourceAccountId, |
||||||
|
destination_id: expenseAccountId, |
||||||
|
budget_id: currentBudgetID |
||||||
|
} |
||||||
|
] |
||||||
|
}) |
||||||
|
}); |
||||||
|
|
||||||
|
if (res.status === 200){ |
||||||
|
console.log(`Added ${amount} to ${budgets[currentBudgetID]}`); |
||||||
|
} |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
await bot.start(); |
||||||
|
} |
||||||
|
|
||||||
|
start(); |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "es2023", |
||||||
|
"module": "commonjs", |
||||||
|
"esModuleInterop": true, |
||||||
|
"forceConsistentCasingInFileNames": true, |
||||||
|
"strict": true, |
||||||
|
"skipLibCheck": true, |
||||||
|
"sourceMap": true, |
||||||
|
"outDir": "out" |
||||||
|
}, |
||||||
|
"include": ["src/**/*.ts"] |
||||||
|
} |
Loading…
Reference in new issue