Created
June 16, 2024 21:17
-
-
Save psybers/56a7947282c734cec5b68253a5a7ceb6 to your computer and use it in GitHub Desktop.
Have Actual Budget track a home's zestimate. Add "zestimate:https://www.zillow.com/homedetails/HOME/ID_zpid/" to the account note. If you own a percent of the house, add "ownership:0.50" to the note. Run `npm install` to install packages, then `node zestimate.js` to run it (I run it in a weekly/monthly cron).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"dependencies": { | |
"@actual-app/api": "^6.8.1", | |
"dotenv": "^16.4.5", | |
"jsdom": "^24.1.0" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require("dotenv").config(); | |
const api = require('@actual-app/api'); | |
const payeeName = process.env.IMPORTER_PAYEE_NAME || 'Zestimate'; | |
const url = process.env.ACTUAL_SERVER_URL || ''; | |
const password = process.env.ACTUAL_SERVER_PASSWORD || ''; | |
const file_password = process.env.ACTUAL_FILE_PASSWORD || ''; | |
const sync_id = process.env.ACTUAL_SYNC_ID || ''; | |
const cache = process.env.IMPORTER_CACHE_DIR || './cache'; | |
if (!url || !password || !sync_id) { | |
console.error('Required settings for Actual not provided.'); | |
process.exit(1); | |
} | |
const jsdom = require("jsdom"); | |
async function getZestimate(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 zestimateText = dom.window.document.getElementById('home-details-home-values').getElementsByTagName('h3')[0].textContent; | |
return parseInt(zestimateText.replace('$', '').replace(',', '')) * 100; | |
} | |
function sleep(ms) { | |
return new Promise((resolve) => { | |
setTimeout(resolve, ms); | |
}); | |
} | |
const getAccountBalance = async (account) => { | |
const data = await api.runQuery( | |
api.q('transactions') | |
.filter({ | |
'account': account.id, | |
}) | |
.calculate({ $sum: '$amount' }) | |
.options({ splits: 'grouped' }) | |
); | |
return data.data; | |
}; | |
(async function() { | |
console.log("connect"); | |
await api.init({ serverURL: url, password: password, dataDir: cache }); | |
console.log("open file"); | |
if (file_password) { | |
await api.downloadBudget(sync_id, { password: file_password, }); | |
} else { | |
await api.downloadBudget(sync_id); | |
} | |
const payees = await api.getPayees(); | |
let payeeId = payees.find(p => p.name === payeeName).id; | |
if (!payeeId) { | |
payeeId = await api.createPayee({ name: payeeName }); | |
} | |
if (!payeeId) { | |
console.error('Failed to create payee:', payeeName); | |
process.exit(1); | |
} | |
const accounts = await api.getAccounts(); | |
for (const account of accounts) { | |
const notes = await api.runQuery( | |
api.q('notes') | |
.filter({ | |
id: `account-${account.id}`, | |
}) | |
.select('*') | |
); | |
if (notes.data.length && notes.data[0].note) { | |
const note = notes.data[0].note; | |
if (note.indexOf('zestimate:') > -1) { | |
const URL = note.split('zestimate:')[1].split(' ')[0]; | |
let ownership = 1; | |
if (note.indexOf('ownership:') > -1) { | |
ownership = parseFloat(note.split('ownership:')[1].split(' ')[0]); | |
} | |
console.log('Fetching zestimate for account:', account.name); | |
console.log('Zillow URL:', URL); | |
const zestimate = await getZestimate(URL); | |
const balance = await getAccountBalance(account); | |
const diff = (zestimate * ownership) - balance; | |
console.log('Zestimate:', zestimate); | |
console.log('Ownership:', zestimate * ownership); | |
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 Zestimate to ${zestimate * ownership / 100} (${zestimate / 100}*${ownership * 100}%)`, | |
}]); | |
} | |
await sleep(1324); | |
} | |
} | |
} | |
console.log("done"); | |
await api.shutdown(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment