您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically download QFX statements from Amex
// ==UserScript== // @name Amex QFX Statement Downloader // @namespace Violentmonkey Scripts // @license MIT // @version 1.1 // @description Automatically download QFX statements from Amex // @match https://*.americanexpress.com/* // @grant GM_download // @grant GM_xmlhttpRequest // ==/UserScript== (function () { 'use strict'; // src/common.ts async function getElement(selector, POLL_INTERVAL) { return new Promise((resolve) => { const check = setInterval(() => { let el; if (typeof selector === "string") { el = document.querySelector(selector); } else { el = Array.from(selector).map((s) => document.querySelector(s)).find(Boolean); } if (el) { clearInterval(check); resolve(el); } }, POLL_INTERVAL); }); } function formatDateYYYYdMMdDD(date) { return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`; } function GM_xmlhttpRequest_promise(pack) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ ...pack, onload: resolve, onerror: reject }); }); } async function easyDownload({ content, name, saveAs = true }) { await GM_download_promise({ url: URL.createObjectURL(new Blob([content.trim()], { type: "application/x-qfx" })), name, saveAs }); } function GM_download_promise(option) { const { url, name, saveAs } = option; return new Promise((resolve, reject) => { GM_download({ url, name, saveAs, onload: resolve, onerror: reject }); }); } // src/amex/lib.ts var BANK_ID = "amex"; var LOGGER_prefix = `[${BANK_ID} Downloader]`; var POLL_INTERVAL = 500; async function addDownloadButton() { const container = await getElement(["div.DynamicLayout"], POLL_INTERVAL); const CLASS = "my-download-btn"; if (container.querySelector(`.${CLASS}`)) return; const btn = document.createElement("button"); btn.textContent = "Download QFX"; btn.className = CLASS; btn.style.cssText = ` padding: 8px 12px; margin: 0px 0px 12px 0px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; width: fit-content; `; btn.addEventListener("click", async () => { try { await fireDownloadProcess(); } catch (err) { console.error(`[${BANK_ID} Downloader] Error:`, err); } }); container.insertBefore(btn, container.firstChild); } async function fireDownloadProcess() { const a = document.querySelector('a[title="Download your Statements"]'); if (!a || !a.href) { console.error(`${LOGGER_prefix} Failed to find download link`); return; } const url = a.href; const match = url.match(/[?&]account_key=([^&]+)/); if (!match || match.length < 2 || !match[1]) { console.error(`${LOGGER_prefix} Failed to extract account_key`); return; } const accountKey = match[1]; console.log(`${LOGGER_prefix} Extracted account_key:`, accountKey); const [payload, endDate] = await buildPayload(accountKey); const U = new URL("https://global.americanexpress.com/api/servicing/v1/financials/documents"); payload?.forEach((value, key) => U.searchParams.append(decodeURIComponent(key), decodeURIComponent(value))); const { status, responseText: content } = await GM_xmlhttpRequest_promise({ method: "GET", url: U.toString() + "&=", headers: {} }); if (status !== 200) { throw new Error(`Request failed: ${status} ${content}`); } const acctIdMatch = content.match(/<ACCTID>(.*?)<\/ACCTID>/); if (!acctIdMatch || acctIdMatch.length < 2 || !acctIdMatch[1]) { console.error(`${LOGGER_prefix} Failed to extract ACCTID`); return; } let acctId = acctIdMatch[1].replace(/[^a-zA-Z0-9]/g, ""); console.log(`${LOGGER_prefix} Extracted ACCTID:`, acctId); await easyDownload({ content, name: `${BANK_ID}_${acctId}_${endDate}_80.qfx`, saveAs: true }); } async function buildPayload(accountKey) { const payload = new URLSearchParams; const endDate = new Date; const startDate = new Date(endDate); startDate.setDate(endDate.getDate() - 80); const eStr = formatDateYYYYdMMdDD(endDate); payload.append("end_date", eStr); payload.append("start_date", formatDateYYYYdMMdDD(startDate)); payload.append("file_format", "quicken"); payload.append("limit", "3000"); payload.append("status", "posted"); payload.append("account_key", accountKey); payload.append("client_id", "AmexAPI"); return [payload, eStr]; } // src/amex/index.ts try { addDownloadButton(); const observer = new MutationObserver(addDownloadButton); observer.observe(document.body, { childList: true, subtree: true }); } catch (err) { console.error("[WF Downloader] Error:", err); } })();