Added bookmarklets for auto download of files

This commit is contained in:
2026-01-15 15:59:21 -08:00
commit 85bc5e7ebf
4 changed files with 398 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
javascript:(async function() {
function getProperty() {
try {
const el = document.querySelector('.acct-level-menu-address-single .exclude-translation');
const text = el ? el.innerText : document.body.innerText;
if (text.includes("23511")) return "23511 E 2nd";
if (text.includes("23513")) return "23513 E 2nd";
if (text.includes("Myrtlewood")) return "Myrtlewood";
if (text.includes("E Upriver")) return "Brookstone";
if (text.includes("W 2nd")) return "Ascott";
if (text.includes("E 36th") || text.includes("E 37th")) return "Commons";
return "Unknown";
} catch(e) { return "Unknown"; }
}
const propertyName = getProperty();
const limitInput = prompt("Detected Property: " + propertyName + "\n\nLimit downloads to how many?\n(Leave blank for ALL, click Cancel to stop script)");
if (limitInput === null) return;
let maxDownloads = Infinity;
if (limitInput.trim() !== "") {
const parsed = parseInt(limitInput, 10);
if (!isNaN(parsed)) maxDownloads = parsed;
}
const processedUrls = new Set();
let totalDownloaded = 0;
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
alert("Starting process.\nTarget: " + (maxDownloads === Infinity ? "ALL" : maxDownloads) + " files.\n\nPlease keep this tab open.");
async function scanAndDownload() {
const rows = document.querySelectorAll('tr[ng-repeat="entry in pageData.Entries"]');
const currentTotalRows = rows.length;
for (const row of rows) {
if (totalDownloaded >= maxDownloads) return { count: currentTotalRows, lastText: "", stop: true };
const dateCell = row.querySelector('td[data-heading-label="Date"]');
const pdfCell = row.querySelector('td[data-heading-label="View Bill PDF"]');
if (!dateCell || !pdfCell || pdfCell.classList.contains('ng-hide')) continue;
const linkElement = pdfCell.querySelector('a');
if (!linkElement || !linkElement.href) continue;
if (processedUrls.has(linkElement.href)) continue;
processedUrls.add(linkElement.href);
const rawDate = dateCell.innerText.trim();
const parts = rawDate.split('/');
const cleanDate = parts[2] + "-" + parts[0].padStart(2, '0') + "-" + parts[1].padStart(2, '0');
const safeProp = propertyName.replace(/[^a-z0-9\s-_]/gi, '').trim();
const filename = "Avista - " + safeProp + " - " + cleanDate + ".pdf";
try {
const response = await fetch(linkElement.href);
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = blobUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(blobUrl);
document.body.removeChild(a);
totalDownloaded++;
await wait(800);
} catch (err) { console.error("Download failed", err); }
}
const lastRowText = rows.length > 0 ? rows[rows.length - 1].innerText : "";
if (totalDownloaded >= maxDownloads) return { count: currentTotalRows, lastText: lastRowText, stop: true };
return { count: currentTotalRows, lastText: lastRowText, stop: false };
}
async function waitForNewRows(previousState) {
let attempts = 0;
while (attempts < 30) {
const rows = document.querySelectorAll('tr[ng-repeat="entry in pageData.Entries"]');
const currentCount = rows.length;
const currentLastText = rows.length > 0 ? rows[rows.length - 1].innerText : "";
if (currentCount > previousState.count || currentLastText !== previousState.lastText) {
return true;
}
await wait(500);
attempts++;
}
return false;
}
let keepGoing = true;
let currentState = await scanAndDownload();
if (currentState.stop) keepGoing = false;
while (keepGoing) {
const allSpans = Array.from(document.querySelectorAll('span.btn-secondary'));
const moreButton = allSpans.find(s => s.innerText && s.innerText.toLowerCase().includes('load more') && s.offsetParent !== null);
const navWrapper = document.querySelector('.nav-pagination-mobile');
const isHidden = navWrapper && navWrapper.classList.contains('ng-hide');
if (moreButton && !isHidden) {
moreButton.click();
await waitForNewRows(currentState);
currentState = await scanAndDownload();
if (currentState.stop) keepGoing = false;
} else {
keepGoing = false;
}
}
alert("Job Done!\n\nTotal bills downloaded: " + totalDownloaded);
})();

View File

@@ -0,0 +1,63 @@
javascript:(async function() {
console.log(">>> MR COOPER DOWNLOADER STARTED");
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
let capturedUrl = null;
const originalWindowOpen = window.open;
function enableInterceptor() {
capturedUrl = null;
window.open = function(url) {
console.log(">>> Intercepted URL: " + url);
capturedUrl = url;
return { focus:()=>{}, close:()=>{} };
};
}
function disableInterceptor() { window.open = originalWindowOpen; }
const dateDivs = Array.from(document.querySelectorAll(".statement-date-no-type"));
if (dateDivs.length === 0) {
alert("No statements found! Make sure you are on the Statements page.");
return;
}
let downloadCount = 0;
for (const dateDiv of dateDivs) {
if (dateDiv.innerText.trim() === "DATE") continue;
const row = dateDiv.closest(".row.collapse");
const btn = row.querySelector(".view-statement-button");
if (!btn) continue;
const rawDate = dateDiv.innerText.trim();
const d = new Date(rawDate);
if (isNaN(d.getTime())) continue;
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
const cleanDate = yyyy + "-" + mm + "-" + dd;
const filename = "Mr Cooper - E 2nd - " + cleanDate + ".pdf";
enableInterceptor();
btn.click();
let attempts = 0;
while (capturedUrl === null && attempts < 20) {
await wait(200);
attempts++;
}
disableInterceptor();
if (capturedUrl) {
try {
const response = await fetch(capturedUrl);
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = blobUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(blobUrl);
document.body.removeChild(a);
downloadCount++;
} catch (e) {
console.error("Fetch failed", e);
}
}
await wait(1500);
}
alert("Done! Downloaded " + downloadCount + " files.");
})();

View File

@@ -0,0 +1,104 @@
javascript:(async function() {
console.log(">>> LINK HARVESTER STARTED");
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const capturedLinks = [];
const dateCounts = {};
let capturedUrl = null;
const originalWindowOpen = window.open;
function enableInterceptor() {
capturedUrl = null;
window.open = function(url) {
if (url) {
capturedUrl = url;
return { focus:()=>{}, close:()=>{} };
}
const spyWindow = {
focus: () => {}, close: () => {},
document: { write: () => {}, close: () => {} }
};
Object.defineProperty(spyWindow, 'location', {
set: function(val) { capturedUrl = val; },
get: function() { return { set href(val) { capturedUrl = val; }, assign: (val) => { capturedUrl = val; }, replace: (val) => { capturedUrl = val; } }; }
});
return spyWindow;
};
}
function disableInterceptor() { window.open = originalWindowOpen; }
const rows = Array.from(document.querySelectorAll("mat-row"));
if (rows.length === 0) {
alert("No rows found. Please go to the Billing/Documents list.");
return;
}
for (const row of rows) {
const dateCell = row.querySelector(".mat-column-date");
if (!dateCell) continue;
const rawDate = dateCell.innerText.trim();
const d = new Date(rawDate);
if (isNaN(d.getTime())) continue;
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
const baseDate = yyyy + "-" + mm + "-" + dd;
if (!dateCounts[baseDate]) dateCounts[baseDate] = 0;
dateCounts[baseDate]++;
const suffix = dateCounts[baseDate] > 1 ? " (" + dateCounts[baseDate] + ")" : "";
const filename = "Roundpoint - Myrtlewood - " + baseDate + suffix + ".pdf";
const downloadCell = row.querySelector(".mat-column-download");
const clickTarget = downloadCell ? (downloadCell.querySelector("i") || downloadCell) : null;
if (!clickTarget) continue;
enableInterceptor();
clickTarget.click();
let attempts = 0;
while (capturedUrl === null && attempts < 30) {
await wait(100);
attempts++;
}
disableInterceptor();
if (capturedUrl) {
const urlWithHash = capturedUrl + "#" + filename;
capturedLinks.push({ filename: filename, url: urlWithHash });
}
await wait(200);
}
const container = document.querySelector('mat-drawer-content > div') || document.querySelector('.data-table-shell') || document.querySelector('mat-drawer-content');
if (!container) {
alert("Could not find the table container to replace. Check console for links.");
console.log(capturedLinks);
return;
}
container.innerHTML = `
<div style="padding: 20px; font-family: sans-serif; background: #fff; color: #000; height: 100%; overflow: auto;">
<h2 style="color: #333; margin-top: 0;">Harvest Complete (${capturedLinks.length} files)</h2>
<p style="background: #e3f2fd; padding: 10px; border-radius: 4px; border-left: 5px solid #2196F3; font-size: 14px;">
<strong>INSTRUCTIONS:</strong><br>
Right-click -> <strong>DownThemAll!</strong><br>
(Use default settings or <code>*text*</code> mask)
</p>
<table style="width: 100%; border-collapse: collapse; margin-top: 10px;">
${capturedLinks.map(item => `
<tr style="border-bottom: 1px solid #ddd;">
<td style="padding: 8px;">
<a href="${item.url}" download="${item.filename}" style="font-size: 14px; color: #0066cc; text-decoration: none; font-family: monospace;">${item.filename}</a>
</td>
</tr>
`).join('')}
</table>
</div>
`;
})();

View File

@@ -0,0 +1,134 @@
javascript:(async function() {
console.log(">>> SCRIPT STARTED");
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function parseDate(input) {
const d = new Date(input);
if (isNaN(d.getTime())) return null;
d.setHours(0, 0, 0, 0);
return d;
}
function getUnitName(addressText) {
const text = addressText.trim();
if (text.toLowerCase().includes("laundry")) {
if (text.includes("504")) return "CY1 Laundry";
if (text.includes("13129")) return "CY2 Laundry";
if (text.includes("13203")) return "CY3 Laundry";
if (text.includes("524")) return "CY4 Laundry";
return "Laundry";
}
const parts = text.split(" ");
return parts[parts.length - 1];
}
let capturedUrl = null;
const originalWindowOpen = window.open;
function enableInterceptor() {
capturedUrl = null;
window.open = function(url) { capturedUrl = url; return { focus:()=>{}, close:()=>{} }; };
}
function disableInterceptor() { window.open = originalWindowOpen; }
async function setTableTo100() {
const wrapper = document.querySelector("#statement-history-table-title")?.parentElement;
if (!wrapper) return;
const select = wrapper.querySelector("select[name*='length']");
if (select && select.value !== "100") {
select.value = "100";
select.dispatchEvent(new Event('change', { bubbles: true }));
await wait(2000);
}
}
async function ensureMenuOpen() {
const table = document.querySelector("#my-accounts-table");
if (table && table.offsetParent !== null) return;
const buttons = Array.from(document.querySelectorAll("button, a, span[role='button']"));
const trigger = buttons.find(b => {
const t = b.innerText.toLowerCase();
return t.includes("switch account") || t.includes("change account") || t.includes("choose account");
});
if (trigger) { trigger.click(); await wait(1000); }
else {
const header = document.querySelector("address");
if (header) { header.click(); await wait(1000); }
}
}
async function processCurrentAccount(cutoffDate) {
await setTableTo100();
const addrSpan = document.querySelector("address span[data-bind*='fullAddress']");
const fullAddress = addrSpan ? addrSpan.innerText : "Unknown";
const extraName = getUnitName(fullAddress);
const rows = document.querySelectorAll("#statements-table tbody tr");
let downloadCount = 0;
for (const row of rows) {
const dateCell = row.cells[2];
const btn = row.querySelector(".view-statement-details-button");
if (!dateCell || !btn) continue;
const rawDate = dateCell.innerText.trim();
const stmtDate = new Date(rawDate);
stmtDate.setHours(0,0,0,0);
if (stmtDate < cutoffDate) continue;
const yyyy = stmtDate.getFullYear();
const mm = String(stmtDate.getMonth()+1).padStart(2,'0');
const dd = String(stmtDate.getDate()).padStart(2,'0');
const cleanDate = yyyy + "-" + mm + "-" + dd;
const filename = "Vera - Brookstone " + extraName + " - " + cleanDate + ".pdf";
enableInterceptor();
btn.click();
let attempts = 0;
while (capturedUrl === null && attempts < 15) { await wait(200); attempts++; }
disableInterceptor();
if (capturedUrl) {
try {
const response = await fetch(capturedUrl);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
downloadCount++;
} catch (e) { console.error("Fetch failed", e); }
}
await wait(1000);
}
}
const inputStr = prompt("Enter Cutoff Date (MM/DD/YYYY).\nLeave BLANK for ALL history.\nClick Cancel to stop.", "9/18/2025");
if (inputStr === null) return;
let cutoffDate;
if (inputStr.trim() === "") {
cutoffDate = new Date("1900-01-01");
} else {
cutoffDate = parseDate(inputStr);
if (!cutoffDate) {
alert("Invalid Date format. Please use MM/DD/YYYY.");
return;
}
}
let accountRows = document.querySelectorAll("#my-accounts-table tbody tr");
if (accountRows.length === 0) {
alert("Please open the 'Choose Account' popup first!");
return;
}
const totalAccounts = accountRows.length;
for (let i = 0; i < totalAccounts; i++) {
await ensureMenuOpen();
accountRows = document.querySelectorAll("#my-accounts-table tbody tr");
if (!accountRows[i]) break;
const row = accountRows[i];
const targetCell = row.cells[2] || row.cells[1];
const targetAcctNum = row.cells[1].innerText.trim();
targetCell.click();
let loaded = false;
for (let j=0; j<20; j++) {
await wait(500);
const headerAcct = document.querySelector("address span[data-bind*='accountNumber']");
if (headerAcct && headerAcct.innerText.trim() === targetAcctNum) { loaded = true; break; }
}
if (!loaded) continue;
await wait(2000);
await processCurrentAccount(cutoffDate);
}
alert("Batch completed. All accounts processed.");
})();