Skip to content

Instantly share code, notes, and snippets.

@shotasenga
Last active April 28, 2025 15:20
Show Gist options
  • Save shotasenga/aa5e83b9406f7fcc2910cc7c43a30d4b to your computer and use it in GitHub Desktop.
Save shotasenga/aa5e83b9406f7fcc2910cc7c43a30d4b to your computer and use it in GitHub Desktop.
Export Wealthsimple transactions as CSV
/*
* 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.
*/
(async function () {
"use strict";
const transactions = [];
for (const button of x(`//button[contains(., 'Chequing')][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}`;
}
})();
@shotasenga
Copy link
Author

shotasenga commented Nov 22, 2024

This is a bookmarklet version of the CSV export user script for Wealthsimple.

Instruction

  1. Create a bookmarklet with the script below
  2. Open the "Activity" page on Wealthsimple
  3. Click the bookmarklet created
javascript:(function()%7B%2F*%0A%20*%20DISCLAIMER%3A%0A%20*%20This%20script%20extracts%20sensitive%20financial%20information%20(transaction%20data)%20from%20Wealthsimple.%0A%20*%20Ensure%20that%20you%20use%20this%20script%20in%20a%20secure%20environment%20and%20handle%20the%20extracted%20data%20responsibly.%0A%20*%20The%20developer%20of%20this%20script%20is%20not%20responsible%20for%20any%20issues%20or%20troubles%20that%20arise%20from%20its%20use.%0A%20*%2F%0A(async%20function%20()%20%7B%0A%20%20%22use%20strict%22%3B%0A%0A%20%20const%20transactions%20%3D%20%5B%5D%3B%0A%0A%20%20for%20(const%20button%20of%20x(%60%2F%2Fbutton%5Bcontains(.%2C%20'Chequing')%5D%5Bcontains(.%2C%20'CAD')%5D%60))%20%7B%0A%20%20%20%20const%20payee%20%3D%20button.querySelector(%22p%22).innerText%3B%0A%20%20%20%20const%20amount%20%3D%20x(%60.%2F%2Fp%5Bcontains(.%2C%20'CAD')%5D%60%2C%20button).next().value.innerText%3B%0A%0A%20%20%20%20button.click()%3B%0A%20%20%20%20await%20nextTick()%3B%0A%0A%20%20%20%20const%20%5Bdate%2C%20_%5D%20%3D%20Array.from(%0A%20%20%20%20%20%20x(%0A%20%20%20%20%20%20%20%20%60.%2F%2Fp%5Bcontains(.%2C%20'Date')%5D%2Ffollowing-sibling%3A%3A*%2F%2Fp%60%2C%0A%20%20%20%20%20%20%20%20button.parentElement.parentElement%0A%20%20%20%20%20%20)%0A%20%20%20%20).map((el)%20%3D%3E%20el.innerText)%3B%0A%0A%20%20%20%20transactions.push(%7B%0A%20%20%20%20%20%20payee%2C%0A%20%20%20%20%20%20amount%2C%0A%20%20%20%20%20%20date%3A%20formatDateForYNAB(date)%2C%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20const%20csv%20%3D%20%5B%5D%3B%0A%20%20csv.push(%22Date%2C%20Payee%2C%20Amount%22)%3B%0A%0A%20%20for%20(const%20transaction%20of%20transactions)%20%7B%0A%20%20%20%20csv.push(%0A%20%20%20%20%20%20%5Btransaction.date%2C%20transaction.payee%2C%20transaction.amount%5D%0A%20%20%20%20%20%20%20%20.map(escapeCsvField)%0A%20%20%20%20%20%20%20%20.join(%22%2C%22)%0A%20%20%20%20)%3B%0A%20%20%7D%0A%0A%20%20%2F%2F%20save%20as%20a%20file%0A%20%20const%20blob%20%3D%20new%20Blob(%5Bcsv.join(%22%5Cn%22)%5D%2C%20%7B%20type%3A%20%22text%2Fcsv%22%20%7D)%3B%0A%20%20const%20url%20%3D%20URL.createObjectURL(blob)%3B%0A%20%20const%20a%20%3D%20document.createElement(%22a%22)%3B%0A%20%20a.href%20%3D%20url%3B%0A%20%20a.download%20%3D%20%22transactions.csv%22%3B%0A%20%20a.click()%3B%0A%0A%20%20function*%20x(xpath%2C%20root%20%3D%20document)%20%7B%0A%20%20%20%20const%20xpathResult%20%3D%20document.evaluate(%0A%20%20%20%20%20%20xpath%2C%0A%20%20%20%20%20%20root%2C%0A%20%20%20%20%20%20null%2C%0A%20%20%20%20%20%20XPathResult.ORDERED_NODE_SNAPSHOT_TYPE%2C%0A%20%20%20%20%20%20null%0A%20%20%20%20)%3B%0A%0A%20%20%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20xpathResult.snapshotLength%3B%20i%2B%2B)%20%7B%0A%20%20%20%20%20%20yield%20xpathResult.snapshotItem(i)%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%0A%20%20function%20nextTick()%20%7B%0A%20%20%20%20return%20new%20Promise((resolve)%20%3D%3E%20setTimeout(resolve%2C%200))%3B%0A%20%20%7D%0A%0A%20%20function%20waitUntilElementExists(xpath%2C%20callback)%20%7B%0A%20%20%20%20const%20observer%20%3D%20new%20MutationObserver(()%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20element%20%3D%20x(xpath).next().value%3B%0A%20%20%20%20%20%20if%20(element)%20%7B%0A%20%20%20%20%20%20%20%20observer.disconnect()%3B%0A%20%20%20%20%20%20%20%20callback(element)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20observer.observe(document.documentElement%2C%20%7B%0A%20%20%20%20%20%20childList%3A%20true%2C%0A%20%20%20%20%20%20subtree%3A%20true%2C%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20function%20escapeCsvField(field)%20%7B%0A%20%20%20%20return%20%60%22%24%7Bfield%7D%22%60%3B%0A%20%20%7D%0A%0A%20%20function%20formatDateForYNAB(str)%20%7B%0A%20%20%20%20%2F%2F%20%22August%2019%2C%202024%22%20to%20%222024-08-19%22%20using%20RegExp%0A%20%20%20%20const%20%5B%2C%20month_s%2C%20day_s%2C%20year%5D%20%3D%20str.match(%2F(%5Cw%2B)%20(%5Cd%2B)%2C%20(%5Cd%2B)%2F)%3B%0A%20%20%20%20const%20month%20%3D%20(new%20Date(Date.parse(%60%24%7Bmonth_s%7D%201%2C%202020%60)).getMonth()%20%2B%201)%0A%20%20%20%20%20%20.toString()%0A%20%20%20%20%20%20.padStart(2%2C%20%220%22)%3B%0A%20%20%20%20const%20day%20%3D%20day_s.padStart(2%2C%20%220%22)%3B%0A%20%20%20%20return%20%60%24%7Byear%7D-%24%7Bmonth%7D-%24%7Bday%7D%60%3B%0A%20%20%7D%0A%7D)()%3B%7D)()%3B

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.

@shotasenga
Copy link
Author

Updated the script to reflect the wording changes on Wealthsimple (from "Cache" to "Chequing")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment