commit 85bc5e7ebfa43d8fc3e94609801b356be43b269a Author: Abijah Date: Thu Jan 15 15:59:21 2026 -0800 Added bookmarklets for auto download of files diff --git a/avista_download_bookmarklet.js b/avista_download_bookmarklet.js new file mode 100644 index 0000000..ba39638 --- /dev/null +++ b/avista_download_bookmarklet.js @@ -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); +})(); + diff --git a/mrcooper_download_bookmarklet.js b/mrcooper_download_bookmarklet.js new file mode 100644 index 0000000..4597ee6 --- /dev/null +++ b/mrcooper_download_bookmarklet.js @@ -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."); +})(); diff --git a/roundpoint_download_bookmarklet.js b/roundpoint_download_bookmarklet.js new file mode 100644 index 0000000..270ca49 --- /dev/null +++ b/roundpoint_download_bookmarklet.js @@ -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 = ` +
+

Harvest Complete (${capturedLinks.length} files)

+

+ INSTRUCTIONS:
+ Right-click -> DownThemAll!
+ (Use default settings or *text* mask) +

+ + ${capturedLinks.map(item => ` + + + + `).join('')} +
+ ${item.filename} +
+
+ `; +})(); diff --git a/vera_download_bookmarklet.js b/vera_download_bookmarklet.js new file mode 100644 index 0000000..7dcc658 --- /dev/null +++ b/vera_download_bookmarklet.js @@ -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."); +})();