// ==UserScript== // @name USPS Address Validation - View Page // @namespace https://github.com/nate-kean/ // @version 2025.12.11.1 // @description Integrate USPS address validation into the Address field. // @author Nate Kean // @match https://jamesriver.fellowshiponego.com/members/view/* // @icon https://www.google.com/s2/favicons?sz=64&domain=fellowshiponego.com // @grant none // @license MIT // @require https://update.greasyfork.icu/scripts/555040/1707051/USPS%20Address%20Validation%20-%20Common.js // @downloadURL none // ==/UserScript== /** * Entry point for the program. * Holds the View-page-specific logic for capturing addresses. */ // @ts-check (async () => { document.head.insertAdjacentHTML("beforeend", ` `); /** * @param {number} id * @param {Element} addressPanel * @param {string} targetSelector */ function addToAddressPanel(id, addressPanel, targetSelector) { console.trace(id, addressPanel, targetSelector); const validator = new Validator(); const indicator = new Indicator( tryQuerySelector(addressPanel, targetSelector) ); const detailsP = tryQuerySelector( addressPanel, `.panel-body :nth-child(${id} of .address-details) > p`, ); const streetAddressEl = detailsP.children[0]; const streetAddrLines = []; for (const child of streetAddressEl.childNodes) { if (!child.textContent) continue; streetAddrLines.push(child.textContent.trim()); } const streetAddress = normalizeStreetAddressQuery(streetAddrLines); const line2 = detailsP.children[1].textContent.trim(); const line2Chunks = line2.split(","); const city = line2Chunks[0]; const [state, zip] = line2Chunks[1].trim().split(" "); const country = detailsP.children[2].textContent.trim(); validator.onNewAddressQuery( indicator, { streetAddress, city, state, zip, country }, ); indicator.button.addEventListener("click", () => { // Act on the correction the indicator is suggesting. if (indicator.status.code !== Validator.Code.CORRECTION) return; // TODO(Nate): what in sam hill is .filter(Boolean) const f1UID = window.location.pathname.split("/").filter(Boolean).pop(); window.location.href = `/members/edit/${f1UID}?autofill-addr=${id}#addresslabel1_chosen`; }); } /** * @param {string[]} streetAddrLines * @returns {string} */ function normalizeStreetAddressQuery(streetAddrLines) { // If the individual has an Address Validation flag, ignore the first // line of the street address, because it's probably a message about the // address. const addDetailsKeys = document.querySelectorAll( ".other-panel > .panel-body > .info-left-column > .other-lbl" ); let iStartStreetAddr = 0; if (streetAddrLines.length > 1) { for (const key of addDetailsKeys) { if (key.textContent.trim() !== "Address Validation") continue; // Skip first two nodes within the street address element: // The address validation message, and the
underneath it. iStartStreetAddr = 1; break; } } // Construct the street address, ignoring beginning lines if the above // block says to, and using spaces instead of
s or newlines. let streetAddress = ""; for (let i = iStartStreetAddr; i < streetAddrLines.length; i++) { const text = streetAddrLines[i]; streetAddress += text.trim(); if (i + 1 !== streetAddrLines.length) { streetAddress += " "; } } return streetAddress; } function onDocumentEnd() { return new Promise(resolve => { window.addEventListener("load", resolve); }); } console.log("USPS Address Validator"); /** * @type {Element | undefined} */ let addressPanel; try { addressPanel = tryQuerySelector( document, ".address-panel", { logError: false }, ); } catch (err) { // Exit early if profile has no address return; } if (document.querySelectorAll(".address-details").length === 1) { addToAddressPanel(1, addressPanel, ".panel-heading"); } else { await onDocumentEnd(); // Conduct a live refactor on F1 Go's address panel to use grid so I can // align two validator indicators next to each address addressPanel.classList.add("jrc-address-panel"); const selector = ".address-lbl, .address-details"; const parents = new Set(); for (const el of document.querySelectorAll(selector)) { // Take the info elements out of their now-unneeded column elements // https://stackoverflow.com/a/66136416 const parent = el.parentElement; const grandparent = parent?.parentElement; if (!grandparent) { console.error(el); throw new Error("Element doesn't have a grandparent"); } grandparent.insertBefore(el, parent); parents.add(parent); } for (const parent of parents) { parent.remove(); } addToAddressPanel(1, addressPanel, ".panel-body"); addToAddressPanel(2, addressPanel, ".panel-body"); } })();