Angi Lead Alert + Smart Ticker (v3.63.5 - Top Lead Only + Initial Exclusion)

Alerts for uncontacted top lead (excluding "Initial"); requires per-load click to enable sound, repeats every 3m, plus mute/test/debug/ticker. DOM-resilient.

// ==UserScript==
// @name         Angi Lead Alert + Smart Ticker (v3.63.5 - Top Lead Only + Initial Exclusion)
// @namespace    http://your.namespace/
// @version      3.63.5
// @description  Alerts for uncontacted top lead (excluding "Initial"); requires per-load click to enable sound, repeats every 3m, plus mute/test/debug/ticker. DOM-resilient.
// @match        https://office.angi.com/app/h/*/leads/lead-board*
// @grant        none
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    console.log('✅ Angi Lead Alert v3.63.5 loaded');

    const AUDIO_URL       = 'https://raw.githubusercontent.com/JamiewSente/files-for-scripts/8e12652ba40c2081401ca35bff4f13d59f78bf28/Screen_Recording_20250714_135612_Perfect%20Piano%20(2).mp3';
    const RELOAD_INTERVAL = 45_000;
    const SCAN_DELAY      = 3_000;
    const REPEAT_INTERVAL = 3 * 60_000;

    const INTERACTION_STATUSES = [
        'Contact & qualify',
        'Selling',
        'In progress - won job'
    ];

    let muted = localStorage.getItem('angiLeadAlertMute') === 'true';
    let staleLeadKey = null;
    let staleLeadStart = 0;

    function isWithinESTWindow() {
        const nowLocal = new Date();
        const nowNY = new Date(nowLocal.toLocaleString('en-US', { timeZone: 'America/New_York' }));
        const h = nowNY.getHours();
        return h >= 7 && h < 20;
    }

    function audioUnlocked() {
        return localStorage.getItem('angiAudioUnlocked') === 'true';
    }

    function playAlert() {
        if (muted || !isWithinESTWindow() || !audioUnlocked()) {
            console.log('🔕 Alert suppressed');
            return;
        }
        const audio = new Audio(AUDIO_URL);
        audio.volume = 0.6;
        document.body.appendChild(audio);
        audio.play().then(() => {
            audio.pause();
            setTimeout(() => audio.play().catch(() => {}), 500);
        }).catch(() => {});
    }

    function hasInteractionStatus(btn) {
        const label = btn.querySelector('.lead-status-label')?.textContent?.trim() || '';
        return INTERACTION_STATUSES.includes(label);
    }

    function isUncontactedBanner(btn) {
        return btn.querySelector('.grey-banner')?.textContent?.trim()?.toLowerCase() === 'uncontacted';
    }

    function scanLeads() {
        const container = document.querySelector('#leads-board-details');
        if (!container) return;

        const topBtn = container.querySelector('div.border-leads-first');
        if (!topBtn) return;

        const key = topBtn.innerText.trim() || topBtn.dataset.id || topBtn.outerHTML;
        const uncontacted = isUncontactedBanner(topBtn);
        const statusLabel = topBtn.querySelector('.lead-status-label')?.textContent?.trim() || '';
        const statusExcluded = statusLabel === 'Initial';

        if (staleLeadKey !== key) {
            staleLeadKey = key;
            staleLeadStart = Date.now();
            if (uncontacted && !statusExcluded) {
                console.log('🔔 New uncontacted lead — alert');
                playAlert();
            }
        } else if (uncontacted && !statusExcluded) {
            const elapsed = Date.now() - staleLeadStart;
            if (isWithinESTWindow() && elapsed >= REPEAT_INTERVAL) {
                console.log('🔁 Repeating alert');
                playAlert();
                staleLeadStart = Date.now();
            }
        } else {
            staleLeadKey = null;
            staleLeadStart = 0;
        }

        updateTicker(topBtn);
    }

    function injectTicker() {
        const header = document.querySelector('header.site-header');
        const leadBoard = document.querySelector('#leads-center-with-data') ||
                          document.querySelector('#leads-board-details') ||
                          header;

        if (!leadBoard || !leadBoard.parentNode) {
            console.warn('⚠️ No valid injection target found');
            return;
        }

        const wrapper = document.createElement('div');
        wrapper.id = 'lead-ticker-wrapper';
        wrapper.style.marginBottom = '10px';

        const ticker = document.createElement('div');
        ticker.id = 'lead-ticker-status';
        ticker.textContent = 'Checking lead status...';
        Object.assign(ticker.style, {
            fontSize: '16px',
            fontWeight: 'bold',
            color: '#fff',
            backgroundColor: '#0078D4',
            padding: '8px',
            textAlign: 'center',
            borderBottom: '2px solid #004eaa',
            position: 'relative'
        });

        const debugBtn = document.createElement('button');
        debugBtn.textContent = 'Debug';
        Object.assign(debugBtn.style, {
            position: 'absolute',
            right: '10px',
            top: '4px',
            padding: '4px 8px',
            fontSize: '12px',
            background: '#222',
            color: '#fff',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
        });
        debugBtn.onclick = () => {
            const btn = document.querySelector('div.border-leads-first');
            if (!btn) return console.log('No lead found.');
            console.log('Label:', btn.querySelector('.lead-status-label')?.textContent);
            console.log('Banner:', btn.querySelector('.grey-banner')?.textContent);
            playAlert();
        };

        const controls = document.createElement('div');
        Object.assign(controls.style, {
            display: 'flex',
            gap: '10px',
            marginTop: '4px',
            justifyContent: 'center'
        });

        const testBox = document.createElement('div');
        testBox.textContent = 'Test sound';
        Object.assign(testBox.style, {
            padding: '8px',
            background: '#f3f3f3',
            border: '1px solid #ccc',
            borderRadius: '4px',
            cursor: 'pointer'
        });
        testBox.onclick = playAlert;

        const muteBox = document.createElement('div');
        muteBox.textContent = muted ? 'Mute: ON' : 'Mute: OFF';
        Object.assign(muteBox.style, {
            padding: '8px',
            background: '#eee',
            border: '1px solid #bbb',
            borderRadius: '4px',
            cursor: 'pointer'
        });
        muteBox.onclick = () => {
            muted = !muted;
            localStorage.setItem('angiLeadAlertMute', muted);
            muteBox.textContent = muted ? 'Mute: ON' : 'Mute: OFF';
        };

        const unlockBox = document.createElement('div');
        unlockBox.textContent = 'Click to enable sound';
        Object.assign(unlockBox.style, {
            padding: '8px',
            background: '#ddeeff',
            border: '1px solid #99c',
            borderRadius: '4px',
            cursor: 'pointer',
            display: audioUnlocked() ? 'none' : 'block'
        });
        unlockBox.onclick = () => {
            localStorage.setItem('angiAudioUnlocked', 'true');
            new Audio().play().catch(() => {});
            unlockBox.style.display = 'none';
        };

        ticker.appendChild(debugBtn);
        controls.appendChild(testBox);
        controls.appendChild(muteBox);
        controls.appendChild(unlockBox);
        wrapper.appendChild(ticker);
        wrapper.appendChild(controls);
        leadBoard.parentNode.insertBefore(wrapper, leadBoard);

        const style = document.createElement('style');
        style.textContent = `
            @keyframes blink {
                0% { opacity: 1; }
                50% { opacity: 0.4; }
                100% { opacity: 1; }
            }
        `;
        document.head.appendChild(style);
    }

    function updateTicker(btn) {
        const t = document.getElementById('lead-ticker-status');
        if (!t || !btn) return;

        const statusLabel = btn.querySelector('.lead-status-label')?.textContent?.trim() || '';
        const uncontacted = isUncontactedBanner(btn);
        const statusExcluded = statusLabel === 'Initial';

        if (hasInteractionStatus(btn) || !uncontacted || statusExcluded) {
            t.style.animation = 'none';
            t.style.backgroundColor = '#28a745';
            t.textContent = 'Latest lead contacted';
        } else {
            t.style.animation = 'blink 1s infinite';
            t.style.backgroundColor = '#d4003b';
            t.textContent = 'New lead pending...';
        }
    }

    document.addEventListener('click', () => {
        localStorage.setItem('angiAudioUnlocked', 'true');
        new Audio().play().catch(() => {});
    }, { once: true });

    function waitFor(selector, callback) {
        const el = document.querySelector(selector);
        if (el) return callback(el);
        setTimeout(() => waitFor(selector, callback), 300);
    }

    waitFor('#leads-board-details', () => {
        console.log('✅ DOM ready — injecting ticker and starting scan');
        injectTicker();
        setTimeout(scanLeads, SCAN_DELAY);
        setInterval(() => location.reload(), RELOAD_INTERVAL);
    });
})();