diff --git a/README.md b/README.md index 3a44bbb..e2ccb0b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ This is a collection of useful scripts to help you manage your Actual Budget. - [Sync Remote Banks](#sync-remote-banks) - [Loan Interest Calculator](#loan-interest-calculator) - [Tracking Home Prices (Zillow's Zestimate)](#tracking-home-prices-zillows-zestimate) + - [Tracking Car Prices (Kelley Blue Book)](#tracking-car-prices-kelley-blue-book) ## Requirements @@ -118,3 +119,41 @@ $ node zestimate.js ``` It is recommended to run this script once per month. + +### Tracking Car Prices (Kelley Blue Book) + +This script tracks the Kelley Blue Book value for a car. It adds new +transactions to keep the account balance equal to the latest KBB value. + +To use this script, first you need to use the KBB website to find the value +of your car. Be sure to select "Private Party" for the value. It should show +something like this: + +![KBB price of a car](images/kbb-price.png) + +Then right click on the price and select "Inspect" to view the page HTML. +From there, grab the URL for the image: + +![HTML](images/kbb-html.png) + +Then for your Actual account, set the following tags in the account note based +on the values in the URL. + +- `kbbURL:https://upa.syndication.kbb.com/usedcar/privateparty/sell/?apikey=XX-XX-XX-XX-XX` +- `kbbZipcode:XXXXX` +- `kbbCondition:good` (or whatever condition you want to use) +- `kbbMileage:XXXXX` (miles on the car, no commas) +- `kbbVehicleid:XXXXXX` +- `kbbOptions:XXX,XXX,XXX,...` + +You can optionally change the payee used for the transactions by setting +`IMPORTER_KBB_PAYEE_NAME` in the `.env` file. + +To run: + +```console +$ node kbb.js +``` + +It is recommended to run this script once per month. Note that you will have +to periodically update the mileage in the account note. diff --git a/images/kbb-html.png b/images/kbb-html.png new file mode 100644 index 0000000..2d56d86 Binary files /dev/null and b/images/kbb-html.png differ diff --git a/images/kbb-price.png b/images/kbb-price.png new file mode 100644 index 0000000..1674ce4 Binary files /dev/null and b/images/kbb-price.png differ diff --git a/kbb.js b/kbb.js new file mode 100644 index 0000000..6c09371 --- /dev/null +++ b/kbb.js @@ -0,0 +1,77 @@ +const api = require('@actual-app/api'); +const jsdom = require("jsdom"); +const { closeBudget, ensurePayee, getAccountBalance, getAccountNote, getTagValue, openBudget, sleep } = require('./utils'); +require("dotenv").config(); + +async function getKBB(URL) { + URL = URL + '&format=html&requesteddataversiondate=' + new Date().toLocaleDateString(); + console.log('KBB URL:', URL); + const response = await fetch(URL, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', + 'Accept-Language': 'en-GB,en;q=0.6', + 'Referer': 'https://www.google.com/', + } + }); + + const html = await response.text(); + const dom = new jsdom.JSDOM(html); + + const kbbText = dom.window.document.getElementById('PriceAdvisor').getElementsByTagName('text')[3].textContent; + return parseInt(kbbText.replace('$', '').replace(',', '')) * 100; +} + +(async function() { + await openBudget(); + + const payeeId = await ensurePayee(process.env.IMPORTER_KBB_PAYEE_NAME || 'KBB'); + + const accounts = await api.getAccounts(); + for (const account of accounts) { + const note = await getAccountNote(account); + + if (note) { + let URL = getTagValue(note, 'kbbURL'); + if (URL) { + const zip = getTagValue(note, 'kbbZipcode', '46237'); + if (zip) URL += `&zipcode=${zip}`; + + const condition = getTagValue(note, 'kbbCondition', 'good'); + if (condition) URL += `&condition=${condition}`; + + const mileage = getTagValue(note, 'kbbMileage', 30000); + if (mileage) URL += `&mileage=${mileage}`; + + const vehicleid = getTagValue(note, 'kbbVehicleid'); + if (vehicleid) URL += `&vehicleid=${vehicleid}`; + + const options = getTagValue(note, 'kbbOptions'); + if (options) URL += `&optionids=${options}`; + + console.log('Fetching KBB for account:', account.name); + + const kbb = await getKBB(URL); + const balance = await getAccountBalance(account); + const diff = kbb - balance; + + console.log('KBB:', kbb); + console.log('Balance:', balance); + console.log('Difference:', diff); + + if (diff != 0) { + await api.importTransactions(account.id, [{ + date: new Date(), + payee: payeeId, + amount: diff, + notes: `Update KBB to ${kbb / 100}`, + }]); + } + + await sleep(1324); + } + } + } + + await closeBudget(); +})();