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