israeli-banks-actual-budget-importer 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -0
- package/compose.yml +2 -1
- package/package.json +4 -3
- package/src/index.ts +7 -0
- package/src/utils.ts +23 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.4.0](https://github.com/tomerh2001/israeli-banks-actual-budget-importer/compare/v1.3.1...v1.4.0) (2025-05-24)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* improved the logs ([8ca8c61](https://github.com/tomerh2001/israeli-banks-actual-budget-importer/commit/8ca8c616a394501412262243e98e535a9bace6ad))
|
|
7
|
+
|
|
1
8
|
## [1.3.1](https://github.com/tomerh2001/israeli-banks-actual-budget-importer/compare/v1.3.0...v1.3.1) (2025-05-23)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
package/compose.yml
CHANGED
|
@@ -5,12 +5,13 @@ services:
|
|
|
5
5
|
cap_add:
|
|
6
6
|
- SYS_ADMIN
|
|
7
7
|
platform: linux/amd64
|
|
8
|
+
restart: always
|
|
8
9
|
build:
|
|
9
10
|
context: .
|
|
10
11
|
dockerfile: Dockerfile
|
|
11
12
|
environment:
|
|
12
13
|
- TZ=Asia/Jerusalem
|
|
13
|
-
- SCHEDULE=0
|
|
14
|
+
# - SCHEDULE=0 */6 * * *
|
|
14
15
|
volumes:
|
|
15
16
|
- ./config.json:/app/config.json
|
|
16
17
|
- ./cache:/app/cache
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.4.0",
|
|
3
3
|
"name": "israeli-banks-actual-budget-importer",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"type": "module",
|
|
@@ -21,15 +21,16 @@
|
|
|
21
21
|
"papaparse": "^5.5.2",
|
|
22
22
|
"semantic-release": "^24.2.3",
|
|
23
23
|
"typescript": "^5.8.3",
|
|
24
|
-
"xo": "^0.
|
|
24
|
+
"xo": "^1.0.0"
|
|
25
25
|
},
|
|
26
26
|
"packageManager": "yarn@4.9.1",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@actual-app/api": "^25.5.0",
|
|
29
29
|
"cronstrue": "^2.61.0",
|
|
30
|
-
"israeli-bank-scrapers": "^
|
|
30
|
+
"israeli-bank-scrapers": "^6.0.0",
|
|
31
31
|
"lodash": "^4.17.21",
|
|
32
32
|
"moment": "^2.30.1",
|
|
33
|
+
"mute-stdout": "^2.0.0",
|
|
33
34
|
"node-cron": "^4.0.7",
|
|
34
35
|
"p-queue": "^8.1.0",
|
|
35
36
|
"tsx": "^4.19.4"
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
1
2
|
/* eslint-disable unicorn/no-process-exit */
|
|
2
3
|
|
|
3
4
|
/* eslint-disable no-await-in-loop */
|
|
@@ -11,6 +12,7 @@ import Queue from 'p-queue';
|
|
|
11
12
|
import moment from 'moment';
|
|
12
13
|
import cron, {type ScheduledTask, validate} from 'node-cron';
|
|
13
14
|
import cronstrue from 'cronstrue';
|
|
15
|
+
import stdout from 'mute-stdout';
|
|
14
16
|
import config from '../config.json' assert {type: 'json'};
|
|
15
17
|
import type {ConfigBank} from './config.d.ts';
|
|
16
18
|
import {scrapeAndImportTransactions} from './utils.ts';
|
|
@@ -25,15 +27,20 @@ async function run() {
|
|
|
25
27
|
intervalCap: 10,
|
|
26
28
|
});
|
|
27
29
|
|
|
30
|
+
stdout.mute();
|
|
28
31
|
await actual.init(config.actual.init);
|
|
29
32
|
await actual.downloadBudget(config.actual.budget.syncId, config.actual.budget);
|
|
33
|
+
stdout.unmute();
|
|
30
34
|
|
|
31
35
|
for (const [companyId, bank] of _.entries(config.banks) as Array<[CompanyTypes, ConfigBank]>) {
|
|
32
36
|
await queue.add(async () => scrapeAndImportTransactions({companyId, bank}));
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
await queue.onIdle();
|
|
40
|
+
|
|
41
|
+
stdout.mute();
|
|
36
42
|
await actual.shutdown();
|
|
43
|
+
stdout.unmute();
|
|
37
44
|
|
|
38
45
|
console.log('Done');
|
|
39
46
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/naming-convention */
|
|
2
1
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
4
|
+
|
|
3
5
|
/* eslint-disable import/extensions */
|
|
4
6
|
/* eslint-disable n/file-extension-in-import */
|
|
5
7
|
|
|
@@ -9,21 +11,26 @@ import _ from 'lodash';
|
|
|
9
11
|
import moment from 'moment';
|
|
10
12
|
import actual from '@actual-app/api';
|
|
11
13
|
import {type PayeeEntity, type TransactionEntity} from '@actual-app/api/@types/loot-core/types/models';
|
|
14
|
+
import stdout from 'mute-stdout';
|
|
12
15
|
import {type ScrapeTransactionsContext} from './utils.d';
|
|
13
16
|
|
|
14
17
|
export async function scrapeAndImportTransactions({companyId, bank}: ScrapeTransactionsContext) {
|
|
18
|
+
function log(status: any, other?: Record<string, unknown>) {
|
|
19
|
+
console.debug({bank: companyId, status, ...other});
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
try {
|
|
16
23
|
const scraper = createScraper({
|
|
17
24
|
companyId,
|
|
18
25
|
startDate: moment().subtract(6, 'month').toDate(),
|
|
19
|
-
|
|
20
|
-
args: ['--user-data-dir
|
|
26
|
+
executablePath: '/opt/homebrew/bin/chromium',
|
|
27
|
+
args: ['--user-data-dir=./chrome-data'],
|
|
21
28
|
additionalTransactionInformation: true,
|
|
22
29
|
verbose: process.env?.VERBOSE === 'true',
|
|
23
30
|
showBrowser: process.env?.SHOW_BROWSER === 'true',
|
|
24
31
|
});
|
|
25
32
|
scraper.onProgress((companyId, payload) => {
|
|
26
|
-
|
|
33
|
+
log(payload.type);
|
|
27
34
|
});
|
|
28
35
|
|
|
29
36
|
const result = await scraper.scrape(bank as ScraperCredentials);
|
|
@@ -42,11 +49,7 @@ export async function scrapeAndImportTransactions({companyId, bank}: ScrapeTrans
|
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
const accounts = await actual.getAccounts() as TransactionEntity[];
|
|
46
|
-
const account = _.find(accounts, {id: bank.actualAccountId})!;
|
|
47
52
|
const accountBalance = result.accounts![0].balance!;
|
|
48
|
-
console.log('Account', account, 'Balance', accountBalance);
|
|
49
|
-
|
|
50
53
|
const payees: PayeeEntity[] = await actual.getPayees();
|
|
51
54
|
const mappedTransactions = transactions.map(async x => ({
|
|
52
55
|
date: moment(x.date).format('YYYY-MM-DD'),
|
|
@@ -57,12 +60,15 @@ export async function scrapeAndImportTransactions({companyId, bank}: ScrapeTrans
|
|
|
57
60
|
imported_id: `${x.identifier}-${moment(x.date).format('YYYY-MM-DD HH:mm:ss')}`,
|
|
58
61
|
}));
|
|
59
62
|
|
|
63
|
+
stdout.mute();
|
|
60
64
|
const importResult = await actual.importTransactions(bank.actualAccountId, await Promise.all(mappedTransactions), {defaultCleared: true});
|
|
65
|
+
stdout.unmute();
|
|
66
|
+
|
|
61
67
|
if (_.isEmpty(importResult)) {
|
|
62
68
|
console.error('Errors', importResult.errors);
|
|
63
69
|
throw new Error('Failed to import transactions');
|
|
64
70
|
} else {
|
|
65
|
-
|
|
71
|
+
log('IMPORTED', {transactions: importResult.added.length});
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
if (!bank.reconcile) {
|
|
@@ -75,7 +81,9 @@ export async function scrapeAndImportTransactions({companyId, bank}: ScrapeTrans
|
|
|
75
81
|
return;
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
|
|
84
|
+
log('RECONCILIATION', {from: currentBalance, to: accountBalance, diff: balanceDiff});
|
|
85
|
+
|
|
86
|
+
stdout.mute();
|
|
79
87
|
const reconciliationResult = await actual.importTransactions(bank.actualAccountId, [{
|
|
80
88
|
date: moment().format('YYYY-MM-DD'),
|
|
81
89
|
amount: actual.utils.amountToInteger(balanceDiff),
|
|
@@ -84,13 +92,17 @@ export async function scrapeAndImportTransactions({companyId, bank}: ScrapeTrans
|
|
|
84
92
|
notes: `Reconciliation from ${currentBalance.toLocaleString()} to ${accountBalance.toLocaleString()}`,
|
|
85
93
|
imported_id: `reconciliation-${moment().format('YYYY-MM-DD HH:mm:ss')}`,
|
|
86
94
|
}]);
|
|
95
|
+
stdout.unmute();
|
|
96
|
+
|
|
87
97
|
if (_.isEmpty(reconciliationResult)) {
|
|
88
98
|
console.error('Reconciliation errors', reconciliationResult.errors);
|
|
89
99
|
} else {
|
|
90
|
-
|
|
100
|
+
log('RECONCILIATION_ADDED', {transactions: reconciliationResult.added.length});
|
|
91
101
|
}
|
|
92
102
|
} catch (error) {
|
|
93
103
|
console.error('Error', companyId, error);
|
|
104
|
+
} finally {
|
|
105
|
+
log('DONE');
|
|
94
106
|
}
|
|
95
107
|
}
|
|
96
108
|
|