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 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
@@ -28,6 +28,7 @@ https://hub.docker.com/r/tomerh2001/israeli-banks-actual-budget-importer
28
28
  services:
29
29
  importer:
30
30
  image: tomerh2001/israeli-banks-actual-budget-importer:latest
31
+ restart: always
31
32
  cap_add:
32
33
  - SYS_ADMIN
33
34
  environment:
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 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.3.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.60.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": "^5.4.7",
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
- // ExecutablePath: '/opt/homebrew/bin/chromium',
20
- args: ['--user-data-dir=/app/chrome-data'],
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
- console.debug('Progress', companyId, payload);
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
- console.log('Imported', importResult.added, 'transactions');
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
- console.log('Balance diff', balanceDiff);
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
- console.info('Added a reconciliation transaction from', currentBalance, 'to', accountBalance);
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