Angi HA Lead Copier (F6 + F7 with City and State for AHK)

Adds buttons to copy HA lead info: F6 (JSON w/ city,state), F7 (formatted) - infers VA/DC from address/zip

// ==UserScript==
// @name         Angi HA Lead Copier (F6 + F7 with City and State for AHK)
// @namespace    http://tampermonkey.net/
// @version      2.4
// @description  Adds buttons to copy HA lead info: F6 (JSON w/ city,state), F7 (formatted) - infers VA/DC from address/zip
// @match        https://office.angi.com/app/h/*/leads/*
// @grant        GM_setClipboard
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const clean = text => text?.replace(/\s+/g, ' ').trim() || '';

    // simple city/state/zip parse helper
    const parseCityStateZip = (s) => {
        if (!s) return { city: '', state: '', zip: '' };
        const t = s.replace(/\bUNCONTACTED\b/i, '').trim();
        // patterns like "Ashburn VA, 20110" or "Ashburn, VA 20110" or "Ashburn VA 20110"
        let m = t.match(/^(.+?)[,\s]+([A-Za-z]{2})[,\s]+(\d{5}(?:-\d{4})?)$/);
        if (m) return { city: clean(m[1]), state: m[2].toUpperCase(), zip: m[3] };
        m = t.match(/^(.+?)[,\s]+(\d{5}(?:-\d{4})?)$/);
        if (m) return { city: clean(m[1]), state: '', zip: m[2] };
        m = t.match(/^(.+?)\s+([A-Za-z]{2})\s+(\d{5}(?:-\d{4})?)$/);
        if (m) return { city: clean(m[1]), state: m[2].toUpperCase(), zip: m[3] };
        // fallback: try extract zip
        const zipMatch = t.match(/\b(\d{5})(?:-\d{4})?\b/);
        const zip = zipMatch ? zipMatch[1] : '';
        const cityOnly = t.replace(/\b\d{5}(?:-\d{4})?\b/, '').replace(/\b[A-Za-z]{2}\b/,'').replace(/[,]/g,'').trim();
        return { city: clean(cityOnly), state: '', zip };
    };

    // infer state from zip prefixes (small map; extend as needed)
    const inferStateFromZip = (zip) => {
        if (!zip) return '';
        const p = ('' + zip).replace(/\D/g, '').slice(0,3);
        const ZIP_STATE_MAP = { '200':'DC','201':'VA','220':'VA','221':'VA','222':'VA','223':'VA','208':'MD','207':'MD','234':'VA' };
        return ZIP_STATE_MAP[p] || '';
    };

    const extractData = () => {
        const fullName = clean(document.querySelector('h4#chosen-lead-name')?.innerText || '');
        const [firstName, ...rest] = fullName.split(' ');
        const lastName = rest.join(' ') || '';

        const address = clean(document.querySelector('span.address-label')?.innerText || '');
        // split by first comma to try separate street and city/state/zip
        const parts = address.split(',');
        const streetAddress = parts[0]?.trim() || '';
        const cityStateZipRaw = parts.slice(1).join(',').trim() || '';

        // parse city/state/zip robustly
        const csz = parseCityStateZip(cityStateZipRaw);
        let city = csz.city || '';
        let state = csz.state || '';
        let zip = csz.zip || '';

        // If state missing but zip present, infer
        if (!state && zip) state = inferStateFromZip(zip);

        // If still missing state and city contains a trailing state token like "Ashburn VA", try quick hack
        if (!state && city) {
            const trail = city.match(/\b([A-Za-z]{2})$/);
            if (trail) {
                state = trail[1].toUpperCase();
                city = city.replace(/\b([A-Za-z]{2})$/,'').trim();
            }
        }

        const email = clean(document.querySelector('div.email-address')?.innerText || '');
        const phone = clean(document.querySelector('#customer-phone-number')?.innerText || '');

        return {
            firstName,
            lastName,
            fullName: `${firstName} ${lastName}`,
            streetAddress,
            city,
            state,
            zip,
            email,
            phone
        };
    };

    const copyForF6 = () => {
        const d = extractData();
        const payload = {
            firstName: d.firstName,
            lastName: d.lastName,
            streetAddress: d.streetAddress,
            zip: d.zip,
            email: d.email,
            city: d.city,
            state: d.state // added state for AHK/tidewater logic
        };
        GM_setClipboard(JSON.stringify(payload));
        alert(`✅ Copied for F6:\n${d.firstName} ${d.lastName}\n${d.streetAddress}, ${d.city} ${d.state} ${d.zip}`);
    };

    const copyForF7 = () => {
        const d = extractData();
        const digits = (d.phone || '').replace(/\D/g, '');
        const formattedPhone = digits.length === 11 && digits.charAt(0) === '1'
            ? `${digits.slice(1,4)}-${digits.slice(4,7)}-${digits.slice(7)}`
            : (digits.length === 10 ? `${digits.slice(0,3)}-${digits.slice(3,6)}-${digits.slice(6)}` : d.phone);

        // include state in the F7 tab-separated output for easier parsing downstream
        const output = `${d.fullName}\t${formattedPhone}\t${d.city}\t${d.state}`;
        GM_setClipboard(output);
        alert(`✅ Copied for F7:\n${output}`);
    };

    const injectButtons = () => {
        const card = document.querySelector('div.customer-details-card.first-card');
        if (!card || document.querySelector('#haCopyF6')) return;

        const btnContainer = document.createElement('div');
        btnContainer.style.cssText = `
            margin-top: 10px;
            display: flex;
            flex-direction: row;
            gap: 12px;
            flex-wrap: wrap;
        `;

        const baseStyle = `
            background-color: #FFDF03;
            color: black;
            font-weight: bold;
            padding: 10px 0;
            border: none;
            border-radius: 6px;
            font-size: 14px;
            cursor: pointer;
            width: 200px;
            text-align: center;
        `;

        const btnF6 = document.createElement('button');
        btnF6.id = 'haCopyF6';
        btnF6.textContent = 'New Customer Copy';
        btnF6.style.cssText = baseStyle;
        btnF6.onclick = copyForF6;

        const btnF7 = document.createElement('button');
        btnF7.id = 'haCopyF7';
        btnF7.textContent = 'HA Log info copy';
        btnF7.style.cssText = baseStyle;
        btnF7.onclick = copyForF7;

        btnContainer.appendChild(btnF6);
        btnContainer.appendChild(btnF7);
        card.appendChild(btnContainer);
    };

    window.addEventListener('load', () => setTimeout(injectButtons, 1000));
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址