1/* 2 * WIRE Miami Bot Form Guard (wire-botguard.js) 3 * Stage 1 anti spam for the lead capture forms. Self contained. 4 * Add AFTER the kit scripts, before the closing body tag. 5 * It does NOT modify the kit. It intercepts the form submit in the capture 6 * phase, so it covers both the legacy client path and the Option B handler 7 * path without editing wire-tracking.js, wire-lead-capture.js, or 8 * wire-contact-form.js. 9 * 10 * Three independent checks. A submission is blocked if ANY fires: 11 * 1. Honeypot: a hidden field humans never see. If filled, it is a bot. 12 * 2. Timing: a submit under MIN_SECONDS from first interaction is a bot. 13 * No interaction at all does NOT block by itself (autofill safety). 14 * 3. Gibberish name: triggers ONLY on an absurdly long name token 15 * (over MAX_TOKEN_LEN chars) plus one corroborating signal. Conservative 16 * by design. Every block is logged so false positives surface. 17 * 18 * A blocked submission is stopped before the kit handler runs, so it reaches 19 * neither FUB, nor the CRM, nor the Google Ads conversion, and unlocks nothing. 20 * Blocks are counted on window.__wireBotBlocks and logged to console for audit. 21 */ 22(function(){ 23 'use strict'; 24 var MIN_SECONDS = 3; 25 var MAX_TOKEN_LEN = 18; 26 var FORM_SELECTOR = '#inquiry-form, #wire-gate-form'; 27 var HP_NAME = 'wire_url'; 28 var HP_CLASS = 'wire-hp'; 29 window.__wireBotBlocks = window.__wireBotBlocks || 0; 30 function ensureHoneypot(form){ 31 if(!form || form.querySelector('.' + HP_CLASS)) return; 32 var hp = document.createElement('input'); 33 hp.type = 'text'; 34 hp.name = HP_NAME; 35 hp.className = HP_CLASS; 36 hp.tabIndex = -1; 37 hp.setAttribute('autocomplete', 'off'); 38 hp.setAttribute('aria-hidden', 'true'); 39 hp.style.cssText = 'position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;opacity:0;pointer-events:none;'; 40 form.appendChild(hp); 41 } 42 function onInteract(e){ 43 var el = e.target; 44 var form = el && el.closest ? el.closest(FORM_SELECTOR) : null; 45 if(!form) return; 46 ensureHoneypot(form); 47 if(!form.dataset.wireFi) form.dataset.wireFi = String(Date.now()); 48 } 49 function getName(form){ 50 var single = form.querySelector('#wire-g-name, [name="name"]'); 51 if(single && single.value) return single.value; 52 var fn = form.querySelector('#first-name, [name="firstName"]'); 53 var ln = form.querySelector('#last-name, [name="lastName"]'); 54 return (((fn && fn.value) || '') + ' ' + ((ln && ln.value) || '')).trim(); 55 } 56 function isGibberish(name){ 57 if(!name) return false; 58 var clean = name.trim(); 59 if(clean.length < 6) return false; 60 var tokens = clean.split(/\s+/); 61 var hasLongToken = tokens.some(function(t){ return t.length > MAX_TOKEN_LEN; }); 62 if(!hasLongToken) return false; 63 var corroborating = 0; 64 if(/[bcdfghjklmnpqrstvwxz]{5,}/i.test(clean)) corroborating++; 65 var nonInitialUpper = 0; 66 tokens.forEach(function(t){ 67 for(var i = 1; i < t.length; i++){ 68 var c = t.charAt(i); 69 if(c >= 'A' && c <= 'Z') nonInitialUpper++; 70 } 71 }); 72 if(nonInitialUpper >= 3) corroborating++; 73 var letters = clean.replace(/[^a-z]/gi, ''); 74 if(letters.length){ 75 var vowels = (letters.match(/[aeiou]/gi) || []).length; 76 if(vowels / letters.length < 0.20) corroborating++; 77 } 78 return corroborating >= 1; 79 } 80 function blockReason(form){ 81 var hp = form.querySelector('.' + HP_CLASS); 82 if(hp && hp.value.trim() !== '') return 'honeypot'; 83 var fi = form.dataset.wireFi ? Number(form.dataset.wireFi) : null; 84 if(fi !== null && (Date.now() - fi) < (MIN_SECONDS * 1000)) return 'too_fast'; 85 if(isGibberish(getName(form))) return 'gibberish_name'; 86 return null; 87 } 88 document.addEventListener('focusin', onInteract, true); 89 document.addEventListener('input', onInteract, true); 90 document.addEventListener('submit', function(e){ 91 var form = e.target; 92 if(!form || !form.matches || !form.matches(FORM_SELECTOR)) return; 93 var reason = blockReason(form); 94 if(!reason) return; 95 e.preventDefault(); 96 e.stopImmediatePropagation(); 97 window.__wireBotBlocks++; 98 try{ 99 var em = form.querySelector('#email, #wire-g-email, [name="email"]'); 100 console.warn('[WIRE botguard] blocked submission', { 101 reason: reason, 102 form: form.id || '(unnamed)', 103 name: getName(form), 104 email: em ? em.value : '', 105 blocks: window.__wireBotBlocks 106 }); 107 }catch(err){} 108 }, true);
109 function init(){ 110 var forms = document.querySelectorAll(FORM_SELECTOR); 111 for(var i = 0; i < forms.length; i++) ensureHoneypot(forms[i]); 112 } 113 if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); 114 else init(); 115})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.