Initial Bot

main
Benjamin Kraft 4 weeks ago
commit 9c324573a3
  1. 4
      .gitignore
  2. 1
      .nvmrc
  3. 171
      package-lock.json
  4. 27
      package.json
  5. 115
      src/index.ts
  6. 13
      tsconfig.json

4
.gitignore vendored

@ -0,0 +1,4 @@
.idea
node_modules
.env
out

@ -0,0 +1 @@
v20.10.0

171
package-lock.json generated

@ -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…
Cancel
Save