102 lines
4.3 KiB
JavaScript
102 lines
4.3 KiB
JavaScript
// =============================================================================
|
|
// 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('');
|
|
})();
|