import * as actualApi from "@actual-app/api" import { existsSync, mkdirSync } from "node:fs" import { join } from "node:path" import { Temporal } from "temporal-polyfill" import { CONFIG_DIR } from "./config" import type { Config } from "./config" import type { Sb1Transaction, ActualAccount } from "./types" import type { ImportTransactionEntity } from "@actual-app/api/@types/loot-core/src/types/models/import-transaction" let inited = false export async function initActual(config: Config["actual"]) { if (inited) return const dataDir = join(CONFIG_DIR, "actualDataDir") if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true }) process.env.ACTUAL_DATA_DIR = dataDir await actualApi.init({ password: config.password, serverURL: config.host, dataDir }) await actualApi.downloadBudget(config.fileId) await actualApi.sync() inited = true } export async function getAccounts(config: Config["actual"]): Promise { await initActual(config) return actualApi.getAccounts() as Promise } export async function importTransactions( config: Config["actual"], accountId: string, transactions: Sb1Transaction[], dryRun: boolean ) { await initActual(config) const mapped: ImportTransactionEntity[] = transactions .filter(t => t.bookingStatus === "BOOKED") .map(t => ({ account: accountId, date: Temporal.Instant.fromEpochMilliseconds(t.date) .toString({ timeZone: "Europe/Oslo" }) .split("T")[0], amount: Math.round(t.amount * 100), payee_name: t.cleanedDescription, notes: t.description?.toLowerCase().trim() !== t.cleanedDescription?.toLowerCase().trim() ? t.description : undefined })) return actualApi.importTransactions(accountId, mapped, { dryRun }) }