Forked from shotasenga/wealthsimple-transaction-for-ynab.user.js
Last active
February 5, 2026 02:29
-
-
Save aleung/78bab19c8151c0f85c362869698d4c9f to your computer and use it in GitHub Desktop.
Export transactions from Wealthsimple to a CSV file for Actual Budget import
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
| // ==UserScript== | |
| // @name Wealthsimple Activity Export (Stable Version) | |
| // @namespace https://leoliang.cn.eu.org | |
| // @version 2026.02.04.1 | |
| // @description Stable export button injection for Wealthsimple | |
| // @author Gemini / Shota Senga | |
| // @match https://my.wealthsimple.com/app/activity* | |
| // @icon https://www.google.com/s2/favicons?sz=64&domain=wealthsimple.com | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| "use strict"; | |
| // 1. 每隔 1 秒检查一次页面,确保按钮存在 | |
| const checkInterval = setInterval(injectButton, 1000); | |
| function injectButton() { | |
| // 如果按钮已经存在,就不再注入 | |
| if (document.getElementById('ws-export-container')) return; | |
| // 寻找 Activity 页面的主标题 | |
| // Wealthsimple 的标题通常在 main 标签内的第一个 h1 | |
| const header = document.querySelector('main h1'); | |
| if (header && header.innerText.includes('Activity')) { | |
| const container = document.createElement("div"); | |
| container.id = 'ws-export-container'; | |
| container.style.display = "inline-flex"; | |
| container.style.alignItems = "center"; | |
| const btn = document.createElement("button"); | |
| btn.innerText = "Export Visible Chequing"; | |
| btn.onclick = runImmediateExport; | |
| btn.style.cssText = ` | |
| margin-left: 15px; | |
| padding: 6px 14px; | |
| background-color: #000; | |
| color: #fff; | |
| border: none; | |
| border-radius: 6px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| font-size: 14px; | |
| height: fit-content; | |
| `; | |
| container.appendChild(btn); | |
| header.parentElement.appendChild(container); | |
| console.log("Wealthsimple Export: Button injected successfully."); | |
| } | |
| } | |
| function runImmediateExport() { | |
| const results = []; | |
| const h2s = Array.from(document.querySelectorAll('h2')); | |
| if (h2s.length === 0) { | |
| alert("No transactions found on page. Please make sure you are on the Activity tab."); | |
| return; | |
| } | |
| h2s.forEach(h2 => { | |
| const dateObj = parseWsDate(h2.innerText); | |
| const isoDate = dateObj.toISOString().split('T')[0]; | |
| let nextEl = h2.nextElementSibling; | |
| while (nextEl && nextEl.tagName !== 'H2') { | |
| const buttons = nextEl.querySelectorAll('button[type="button"]'); | |
| buttons.forEach(btn => { | |
| const pElements = Array.from(btn.querySelectorAll('p')); | |
| const isChequing = pElements.some(p => p.innerText === "Chequing"); | |
| if (isChequing) { | |
| const payee = pElements[0]?.innerText || "Unknown Payee"; | |
| const amountRaw = pElements.find(p => p.innerText.includes('$'))?.innerText || "0"; | |
| results.push({ | |
| date: isoDate, | |
| payee: payee, | |
| amount: cleanAmount(amountRaw) | |
| }); | |
| } | |
| }); | |
| nextEl = nextEl.nextElementSibling; | |
| } | |
| }); | |
| if (results.length > 0) { | |
| downloadCsv(results); | |
| } else { | |
| alert("No Chequing transactions found in the visible list."); | |
| } | |
| } | |
| function parseWsDate(str) { | |
| const now = new Date(); | |
| const lower = str.toLowerCase(); | |
| if (lower.includes('today')) return new Date(now.setHours(0,0,0,0)); | |
| if (lower.includes('yesterday')) { | |
| const y = new Date(); | |
| y.setDate(y.getDate() - 1); | |
| return new Date(y.setHours(0,0,0,0)); | |
| } | |
| const d = new Date(str); | |
| return isNaN(d.getTime()) ? now : d; | |
| } | |
| function cleanAmount(val) { | |
| return val.replace(/[−\u2212]/g, '-').replace(/[CAD\s$,]/g, '').trim(); | |
| } | |
| function downloadCsv(data) { | |
| const rows = [["Date", "Payee", "Amount"].join(",")]; | |
| data.forEach(t => { | |
| rows.push([`"${t.date}"`, `"${t.payee.replace(/"/g, '""')}"`, `"${t.amount}"`].join(",")); | |
| }); | |
| const blob = new Blob([rows.join("\n")], { type: 'text/csv;charset=utf-8;' }); | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement("a"); | |
| link.href = url; | |
| link.download = `ws-export-${new Date().toISOString().slice(0,10)}.csv`; | |
| link.click(); | |
| setTimeout(() => URL.revokeObjectURL(url), 100); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment