您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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); }); })();