A booklet cannot load bank-to-ynab.js dynamically due to https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP.
Instead, booklet-ify bank-to-ynab.js with https://chriszarate.github.io/bookmarkleter/.
A booklet cannot load bank-to-ynab.js dynamically due to https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP.
Instead, booklet-ify bank-to-ynab.js with https://chriszarate.github.io/bookmarkleter/.
| var months = { | |
| "jan.": "01", | |
| "feb.": "02", | |
| "mar.": "03", | |
| "apr.": "04", | |
| "maj": "05", | |
| "juni": "06", | |
| "juli": "07", | |
| "aug.": "08", | |
| "sept.": "09", | |
| "okt.": "10", | |
| "nov.": "11", | |
| "dec.": "12" | |
| }; | |
| var save = (transactions) => { | |
| const filename = "ynab.csv"; | |
| const blob = new Blob([transactions], {type: 'text/csv'}); | |
| if (window.navigator.msSaveOrOpenBlob) { | |
| window.navigator.msSaveBlob(blob, filename); | |
| } else { | |
| const elem = document.createElement('a'); | |
| elem.href = window.URL.createObjectURL(blob); | |
| elem.download = filename; | |
| document.body.appendChild(elem); | |
| elem.click(); | |
| document.body.removeChild(elem); | |
| window.URL.revokeObjectURL(elem); | |
| } | |
| } | |
| var stot = (e, selector) => e.querySelector(selector).innerText; | |
| var perItem = (prev, cur) => { | |
| // YNAB wants date format dd/mm/yyyy. | |
| // Date field format is "dd. mmm. yyyy" ... | |
| var date = stot(cur, '.transaction-field--date'); | |
| // ... unless it is "I dag" or "I går", then construct the value dynamically. | |
| if (date.startsWith("I ")) { | |
| const d = new Date(Date.now()); | |
| if (date === "I går") { | |
| d.setDate(d.getDate() - 1); | |
| } | |
| const opts = { year: "numeric", month: "numeric", day: "numeric" }; | |
| // dd/mm/yyyy | |
| date = d.toLocaleDateString('en-DK', opts); | |
| } else { | |
| for (const k in months) { | |
| date = date.replace(`. ${k} `, `-${months[k]}-`) | |
| } | |
| date = date.replace(/-/g, '/'); | |
| } | |
| const statement = stot(cur, '.transaction-field--statementText'); | |
| const memoRegex = /\s+((?:Nota (?:nr. )?[A-Z0-9]+)|(?:Aftalenr. \d+))/; | |
| var components = statement.split(memoRegex).map(s => s.replace(/[\n\r]/, ' ')); | |
| const payee = '"' + components[0] + '"'; | |
| const memo = components.length > 1 ? '"' + components[1] + '"' : ''; | |
| // Naively US-ify transaction amount. | |
| const value = stot(cur, '.transaction-field--amount').replace('.','').replace(',', '.'); | |
| const isOutflow = value.startsWith('-'); | |
| const inflow = isOutflow ? '' : value; | |
| const outflow = isOutflow ? value.substr(1) : ''; | |
| return prev + date + ',' + payee + ',,' + memo + ',' + outflow + ',' + inflow + '\n'; | |
| } | |
| var csv = Array.prototype.reduce.call(document.querySelectorAll('.transaction-item.transaction-item--hasClick'), perItem, 'Date,Payee,Category,Memo,Outflow,Inflow\n'); | |
| save(csv); |
| #!/usr/bin/env python3 | |
| import csv | |
| import codecs | |
| import sys | |
| import re | |
| regex = re.compile( | |
| r""" | |
| \s+( | |
| (?:Nota (?:nr. )?[a-zA-Z0-9]+) | |
| | | |
| (?:Aftalenr. \d+) | |
| | | |
| (?:\)*\s*\d+) | |
| ) | |
| """, | |
| re.X) | |
| def payee_and_memo_of(statement): | |
| comps = regex.split(statement) | |
| memo = "" | |
| payee = comps[0] | |
| if len(comps) > 1: | |
| memo = comps[1] | |
| memo = memo.replace("))))", "") | |
| payee = payee.replace("Nettoløn", "Danske Bank") | |
| payee = payee.replace("EDWIN RAHRS VEJ", "Danske Bank PER") | |
| payee = payee.replace("MobilePay:", "") | |
| payee = payee.replace("MobilePay køb", "") | |
| payee = payee.replace("MobilePay", "") | |
| payee = payee.replace("kontaktløs Dankort", "") | |
| payee = payee.replace("Visa/Dankort", "") | |
| payee = payee.replace("Dankort-køb", "") | |
| payee = payee.replace("Dankort", "") | |
| payee = payee.replace("Betalingsservice", "") | |
| if "gebyr" in payee.lower(): | |
| memo = f"{memo} {payee}" | |
| payee = "Danske Bank" | |
| payee = payee.strip() | |
| memo = memo.strip() | |
| return (payee, memo) | |
| if len(sys.argv) < 2: | |
| print("usage: %s <csv>" % __file__, file=sys.stderr) | |
| exit(1) | |
| ynab_header = ["Date", "Payee", "Category", "Memo", "Outflow", "Inflow"] | |
| def read(f): | |
| return codecs.open(f, encoding="latin1") | |
| with read(sys.argv[1]) as f: | |
| reader = csv.reader(f, delimiter=";") | |
| writer = csv.writer(sys.stdout, delimiter=",") | |
| writer.writerow(ynab_header) | |
| # payee startswith "MobilePay:" | |
| for row in reader: | |
| date = payee = category = memo = inflow = outflow = "" | |
| date = row[0] | |
| # Skip header if present | |
| if date == "Dato": | |
| continue | |
| date = date.replace(".", "/") | |
| flow = row[2] | |
| flow = flow.replace(".", "") | |
| flow = flow.replace(",", ".") | |
| payee, memo = payee_and_memo_of(row[1]) | |
| if flow.startswith("-"): | |
| outflow = flow[1:] | |
| else: | |
| inflow = flow | |
| writer.writerow([date, payee, category, memo, outflow, inflow]) |
| "Dato";"Tekst";"Belřb";"Saldo";"Status";"Afstemt" | |
| "08.06.2023";"VISA/DANKORT gebyr";"-269,00";"29.873,31";"Udfřrt";"Nej" | |
| "19.06.2023";"Fřtex City Vest 28255";"-374,20";"29.499,11";"Udfřrt";"Nej" | |
| "20.06.2023";"Fřtex City Vest )))) 02573";"-220,85";"29.278,26";"Udfřrt";"Nej" | |
| "23.06.2023";"Fřtex City Vest )))) 80928";"-121,10";"29.157,16";"Udfřrt";"Nej" | |
| "23.06.2023";"Fřtex City Vest )))) 04871";"-153,80";"29.003,36";"Udfřrt";"Nej" | |
| "26.06.2023";"Fřtex City Vest )))) 93578";"-131,15";"28.872,21";"Udfřrt";"Nej" | |
| "27.06.2023";"Nettolřn";"35.246,35";"64.118,56";"Udfřrt";"Nej" | |
| "28.06.2023";"EDWIN RAHRS VEJ";"-20,00";"64.098,56";"Udfřrt";"Nej" | |
| "28.06.2023";"Report ID 157297";"153,80";"64.252,36";"Udfřrt";"Nej" | |
| "29.06.2023";"Fřtex City Vest )))) 50350";"-333,50";"63.918,86";"Udfřrt";"Nej" | |
| "30.06.2023";"Mikkel budget";"-7.000,00";"56.918,86";"Udfřrt";"Nej" | |
| "30.06.2023";"Mĺnedsopsparing";"-3.000,00";"53.918,86";"Udfřrt";"Nej" | |
| "30.06.2023";"Gebyrer i alt";"-5,00";"53.913,86";"Udfřrt";"Nej" |
| Date | Payee | Category | Memo | Outflow | Inflow | |
|---|---|---|---|---|---|---|
| 04/01/2022 | out 19 | 590.00 | ||||
| 04/01/2022 | out 18 | 138.90 | ||||
| 03/01/2022 | out 17 | 1494.00 | ||||
| 03/01/2022 | out 16 | 1420.00 | ||||
| 03/01/2022 | out 15 | 494.23 | ||||
| 03/01/2022 | out 14 | 250.00 | ||||
| 03/01/2022 | out 13 | Nota c6d1df766c2 | 228.00 | |||
| 03/01/2022 | out 12 | 110.95 | ||||
| 03/01/2022 | out 10 | 79.00 | ||||
| 03/01/2022 | out 9 | 330.75 | ||||
| 03/01/2022 | out 8 | 6.26 | ||||
| 03/01/2022 | out 7 | 166.00 | ||||
| 31/12/2021 | out 6 | 653.24 | ||||
| 30/12/2021 | out 5 | 510.00 | ||||
| 30/12/2021 | out 4 | Nota nr. 64393 | 6000.00 | |||
| 30/12/2021 | in 1 | 26418.35 | ||||
| 23/12/2021 | out 3/ | Nota Z24000617998 | 703.00 | |||
| 23/12/2021 | out 2 | Aftalenr. 901711476 | 132.00 | |||
| 22/12/2021 | out 1 æøåÆØÅ | 113.20 |
| #!/usr/bin/env python3 | |
| import csv | |
| import codecs | |
| import sys | |
| import re | |
| regex = re.compile("\\s+((?:Nota (?:nr. )?[a-zA-Z0-9]+)|(?:Aftalenr. \\d+))") | |
| def payee_and_memo_of(statement): | |
| comps = regex.split(statement) | |
| memo = "" | |
| payee = comps[0] | |
| if len(comps) > 1: | |
| memo = comps[1] | |
| payee = payee.replace("MobilePay:", "") | |
| payee = payee.replace("MobilePay køb", "") | |
| payee = payee.replace("MobilePay", "") | |
| payee = payee.replace("kontaktløs Dankort", "") | |
| payee = payee.replace("Visa/Dankort", "") | |
| payee = payee.replace("Dankort-køb", "") | |
| payee = payee.replace("Dankort", "") | |
| payee = payee.replace("Betalingsservice", "") | |
| payee = payee.strip() | |
| return (payee, memo) | |
| if len(sys.argv) < 2: | |
| print("usage: %s <csv>" % __file__, file=sys.stderr) | |
| exit(1) | |
| ynab_header = ["Date", "Payee", "Category", "Memo", "Outflow", "Inflow"] | |
| def read(f): | |
| return codecs.open(f, encoding="utf-8-sig") | |
| with read(sys.argv[1]) as f: | |
| reader = csv.reader(f, delimiter=";") | |
| writer = csv.writer(sys.stdout, delimiter=",") | |
| writer.writerow(ynab_header) | |
| # payee startswith "MobilePay:" | |
| for row in reader: | |
| date = payee = category = memo = inflow = outflow = "" | |
| date = row[0] | |
| date = date.replace("-", "/") | |
| flow = row[2] | |
| flow = flow.replace(".", "") | |
| flow = flow.replace(",", ".") | |
| payee, memo = payee_and_memo_of(row[1]) | |
| if flow.startswith("-"): | |
| outflow = flow[1:] | |
| else: | |
| inflow = flow | |
| writer.writerow([date, payee, category, memo, outflow, inflow]) |
| 04-01-2022;out 19;-590 | 00;477.986 | 44;DKK | |
|---|---|---|---|
| 04-01-2022;out 18;-138 | 90;478.576 | 44;DKK | |
| 03-01-2022;out 17;-1.494 | 00;478.715 | 34;DKK | |
| 03-01-2022;out 16;-1.420 | 00;480.209 | 34;DKK | |
| 03-01-2022;out 15;-494 | 23;481.629 | 34;DKK | |
| 03-01-2022;out 14;-250 | 00;482.123 | 57;DKK | |
| 03-01-2022;MobilePay out 13 Nota c6d1df766c2;-228 | 00;482.373 | 57;DKK | |
| 03-01-2022;Betalingsservice out 12;-110 | 95;482.601 | 57;DKK | |
| 03-01-2022;Dankort out 10;-79 | 00;482.712 | 52;DKK | |
| 03-01-2022;Dankort-køb out 9;-330 | 75;482.791 | 52;DKK | |
| 03-01-2022;Visa/Dankort out 8;-6 | 26;483.122 | 27;DKK | |
| 03-01-2022;kontaktløs Dankort out 7;-166 | 00;483.128 | 53;DKK | |
| 31-12-2021;MobilePay køb out 6;-653 | 24;483.294 | 53;DKK | |
| 30-12-2021;MobilePay: out 5;-510 | 00;483.947 | 77;DKK | |
| 30-12-2021;out 4 Nota nr. 64393;-6.000 | 00;484.457 | 77;DKK | |
| 30-12-2021;in 1;26.418 | 35;490.457 | 77;DKK | |
| 23-12-2021;out 3/ Nota Z24000617998;-703 | 00;464.039 | 42;DKK | |
| 23-12-2021;out 2 Aftalenr. 901711476;-132 | 00;464.742 | 42;DKK | |
| 22-12-2021;out 1 æøåÆØÅ;-113 | 20;464.874 | 42;DKK |
| Date | Payee | Category | Memo | Outflow | Inflow | |
|---|---|---|---|---|---|---|
| 25/07/2010 | Sample Payee | Sample Category | Sample Memo for an outflow | 100.00 | ||
| 26/07/2010 | Sample Payee 2 | Sample Category | Sample memo for an inflow | 500.00 |