// ==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);
});
})();