Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mark05e/4e8bcfa54df846529a0bd756c27f2222 to your computer and use it in GitHub Desktop.
Save mark05e/4e8bcfa54df846529a0bd756c27f2222 to your computer and use it in GitHub Desktop.
Export transactions from Wealthsimple to a CSV file for YNAB import
// ==UserScript==
// @name Export Wealthsimple transactions to CSV for YNAB
// @namespace https://shotasenga.com/
// @version 2024090300
// @description Export transactions from Wealthsimple to a CSV file for YNAB import
// @author Shota Senga
// @match https://my.wealthsimple.com/app/activity*
// @icon https://www.google.com/s2/favicons?sz=64&domain=wealthsimple.com
// @grant none
// ==/UserScript==
/*
* DISCLAIMER:
* This script extracts sensitive financial information (transaction data) from Wealthsimple.
* Ensure that you use this script in a secure environment and handle the extracted data responsibly.
* The developer of this script is not responsible for any issues or troubles that arise from its use.
*/
(function () {
"use strict";
waitUntilElementExists("//h1[contains(., 'Activity')]", (element) => {
const button = document.createElement("button");
button.innerText = "Export transactions";
button.onclick = exportTransactions;
element.parentElement.appendChild(button);
});
async function exportTransactions() {
const transactions = [];
for (const button of x(
`//button[contains(., 'Cash')][contains(., 'CAD')]`
)) {
const payee = button.querySelector("p").innerText;
const amount = x(`.//p[contains(., 'CAD')]`, button).next().value
.innerText;
button.click();
await nextTick();
const [date, _] = Array.from(
x(
`.//p[contains(., 'Date')]/following-sibling::*//p`,
button.parentElement.parentElement
)
).map((el) => el.innerText);
transactions.push({
payee,
amount,
date: formatDateForYNAB(date),
});
}
const csv = [];
csv.push("Date, Payee, Amount");
for (const transaction of transactions) {
csv.push(
[transaction.date, transaction.payee, transaction.amount]
.map(escapeCsvField)
.join(",")
);
}
// save as a file
const blob = new Blob([csv.join("\n")], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "transactions.csv";
a.click();
}
function* x(xpath, root = document) {
const xpathResult = document.evaluate(
xpath,
root,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (let i = 0; i < xpathResult.snapshotLength; i++) {
yield xpathResult.snapshotItem(i);
}
}
function nextTick() {
return new Promise((resolve) => setTimeout(resolve, 0));
}
function waitUntilElementExists(xpath, callback) {
const observer = new MutationObserver(() => {
const element = x(xpath).next().value;
if (element) {
observer.disconnect();
callback(element);
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
}
function escapeCsvField(field) {
return `"${field}"`;
}
function formatDateForYNAB(str) {
// "August 19, 2024" to "2024-08-19" using RegExp
const [, month_s, day_s, year] = str.match(/(\w+) (\d+), (\d+)/);
const month = (new Date(Date.parse(`${month_s} 1, 2020`)).getMonth() + 1)
.toString()
.padStart(2, "0");
const day = day_s.padStart(2, "0");
return `${year}-${month}-${day}`;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment