// ============================================================================= // ANZ Statement Download Helper // ============================================================================= // PASTE + ENTER, then click Download buttons normally. // The blob tab still opens (just close it), BUT you also get a properly // named PDF file downloaded automatically. // // TO STOP: just refresh the page (F5) // ============================================================================= (function () { 'use strict'; let downloadCount = 0; let lastClickedFilename = null; // Track which button was clicked document.addEventListener('click', function (e) { const btn = e.target.closest('button[data-clicktrack="Download"]'); if (btn) { const label = btn.getAttribute('aria-label') || ''; const match = label.match( /Download the statement issued on (\d{2}) (\w{3}) (\d{4}) (\w+) \d{4}/ ); if (match) { const [, , monthShort, year, monthFull] = match; const monthNum = new Date(`${monthShort} 1, 2000`).getMonth() + 1; lastClickedFilename = `ANZ-Statement-${year}-${String(monthNum).padStart(2, '0')}-${monthFull}.pdf`; } else { lastClickedFilename = `ANZ-Statement-${Date.now()}.pdf`; } console.log(`⏳ ${lastClickedFilename}...`); } }, true); // Intercept XHR — READ ONLY, don't modify anything const origOpen = XMLHttpRequest.prototype.open; const origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url, ...rest) { this._url = url; return origOpen.call(this, method, url, ...rest); }; XMLHttpRequest.prototype.send = function (body) { if (this._url && this._url.includes('download-statement')) { // Just listen — don't modify anything. ANZ's code runs normally. this.addEventListener('load', function () { try { const text = typeof this.response === 'string' ? this.response : this.responseText; const json = JSON.parse(text); const pdf = findPdfData(json); if (pdf) { const fn = lastClickedFilename || `ANZ-Statement-${Date.now()}.pdf`; savePdf(pdf, fn); downloadCount++; console.log(`✅ #${downloadCount} ${fn} (${(pdf.length * 3 / 4 / 1024).toFixed(0)} KB)`); console.log(` Close the blob tab, then click next Download button.`); } } catch (e) { // Silently ignore — ANZ's handler still runs fine } }); } return origSend.call(this, body); }; function findPdfData(obj, depth) { if ((depth || 0) > 5) return null; if (typeof obj === 'string' && obj.length > 1000) { if (obj.startsWith('JVBERi')) return obj; if (obj.match(/^[A-Za-z0-9+/=]+$/) && obj.length > 10000) return obj; } if (Array.isArray(obj)) { for (const item of obj) { const r = findPdfData(item, (depth || 0) + 1); if (r) return r; } } if (obj && typeof obj === 'object') { for (const key of Object.keys(obj)) { const r = findPdfData(obj[key], (depth || 0) + 1); if (r) return r; } } return null; } function savePdf(base64, filename) { const bin = atob(base64); const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); const blob = new Blob([bytes], { type: 'application/pdf' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 2000); } console.log(''); console.log('✅ Download helper active!'); console.log('👆 Click Download buttons normally.'); console.log(' Each click: PDF saves as file + blob tab opens (close it).'); console.log(' To stop: refresh (F5)'); console.log(''); })();