1/** 2 * Observe how the user enters content into the comment form in order to determine whether it's a bot or not. 3 * 4 * Note that no actual input is being saved here, only counts and timings between events. 5 */ 6 7( function() { 8 // Passive event listeners are guaranteed to never call e.preventDefault(), 9 // but they're not supported in all browsers. Use this feature detection 10 // to determine whether they're available for use. 11 var supportsPassive = false; 12 13 try { 14 var opts = Object.defineProperty( {}, 'passive', { 15 get : function() { 16 supportsPassive = true; 17 } 18 } ); 19 20 window.addEventListener( 'testPassive', null, opts ); 21 window.removeEventListener( 'testPassive', null, opts ); 22 } catch ( e ) {} 23 24 function init() { 25 var input_begin = ''; 26 27 var keydowns = {}; 28 var lastKeyup = null; 29 var lastKeydown = null; 30 var keypresses = []; 31 32 var modifierKeys = []; 33 var correctionKeys = []; 34 35 var lastMouseup = null; 36 var lastMousedown = null; 37 var mouseclicks = []; 38 39 var mousemoveTimer = null; 40 var lastMousemoveX = null; 41 var lastMousemoveY = null; 42 var mousemoveStart = null; 43 var mousemoves = []; 44 45 var touchmoveCountTimer = null; 46 var touchmoveCount = 0; 47 48 var lastTouchEnd = null; 49 var lastTouchStart = null; 50 var touchEvents = []; 51 52 var scrollCountTimer = null; 53 var scrollCount = 0; 54 55 var correctionKeyCodes = [ 'Backspace', 'Delete', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown' ]; 56 var modifierKeyCodes = [ 'Shift', 'CapsLock' ]; 57 58 var forms = document.querySelectorAll( 'form[method=post]' ); 59 60 for ( var i = 0; i < forms.length; i++ ) { 61 var form = forms[i]; 62 63 var formAction = form.getAttribute( 'action' ); 64 65 // Ignore forms that POST directly to other domains; these could be things like payment forms. 66 if ( formAction ) { 67 // Check that the form is posting to an external URL, not a path. 68 if ( formAction.indexOf( 'http://' ) == 0 || formAction.indexOf( 'https://' ) == 0 ) { 69 if ( formAction.indexOf( 'http://' + window.location.hostname + '/' ) != 0 && formAction.indexOf( 'https://' + window.location.hostname + '/' ) != 0 ) { 70 continue; 71 } 72 } 73 } 74 75 form.addEventListener( 'submit', function () { 76 var ak_bkp = prepare_timestamp_array_for_request( keypresses ); 77 var ak_bmc = prepare_timestamp_array_for_request( mouseclicks ); 78 var ak_bte = prepare_timestamp_array_for_request( touchEvents ); 79 var ak_bmm = prepare_timestamp_array_for_request( mousemoves ); 80 81 var input_fields = { 82 // When did the user begin entering any input? 83 'ak_bib': input_begin, 84 85 // When was the form submitted? 86 'ak_bfs': Date.now(), 87 88 // How many keypresses did they make? 89 'ak_bkpc': keypresses.length, 90 91 // How quickly did they press a sample of keys, and how long between them? 92 'ak_bkp': ak_bkp, 93 94 // How quickly did they click the mouse, and how long between clicks? 95 'ak_bmc': ak_bmc, 96 97 // How many mouseclicks did they make? 98 'ak_bmcc': mouseclicks.length, 99 100 // When did they press modifier keys (like Shift or Capslock)? 101 'ak_bmk': modifierKeys.join( ';' ), 102 103 // When did they correct themselves? e.g., press Backspace, or use the arrow keys to move the cursor back 104 'ak_bck': correctionKeys.join( ';' ), 105 106 // How many times did they move the mouse? 107 'ak_bmmc': mousemoves.length, 108 109 // How many times did they move around using a touchscreen? 110 'ak_btmc': touchmoveCount, 111 112 // How many times did they scroll? 113 'ak_bsc': scrollCount, 114 115 // How quickly did they perform touch events, and how long between them? 116 'ak_bte': ak_bte, 117 118 // How many touch events were there? 119 'ak_btec' : touchEvents.length, 120 121 // How quickly did they move the mouse, and how long between moves? 122 'ak_bmm' : ak_bmm 123 }; 124 125 for ( var field_name in input_fields ) { 126 var field = document.createElement( 'input' ); 127 field.setAttribute( 'type', 'hidden' ); 128 field.setAttribute( 'name', field_name ); 129 field.setAttribute( 'value', input_fields[ field_name ] ); 130 this.appendChild( field ); 131 } 132 }, supportsPassive ? { passive: true } : false ); 133 134 form.addEventListener( 'keydown', function ( e ) { 135 // If you hold a key down, some browsers send multiple keydown events in a row. 136 // Ignore any keydown events for a key that hasn't come back up yet. 137 if ( e.key in keydowns ) { 138 return; 139 } 140
141 var keydownTime = ( new Date() ).getTime(); 142 keydowns[ e.key ] = [ keydownTime ]; 143 144 if ( ! input_begin ) { 145 input_begin = keydownTime; 146 } 147 148 // In some situations, we don't want to record an interval since the last keypress -- for example, 149 // on the first keypress, or on a keypress after focus has changed to another element. Normally, 150 // we want to record the time between the last keyup and this keydown. But if they press a 151 // key while already pressing a key, we want to record the time between the two keydowns. 152 153 var lastKeyEvent = Math.max( lastKeydown, lastKeyup ); 154 155 if ( lastKeyEvent ) { 156 keydowns[ e.key ].push( keydownTime - lastKeyEvent ); 157 } 158 159 lastKeydown = keydownTime; 160 }, supportsPassive ? { passive: true } : false ); 161 162 form.addEventListener( 'keyup', function ( e ) { 163 if ( ! ( e.key in keydowns ) ) { 164 // This key was pressed before this script was loaded, or a mouseclick happened during the keypress, or... 165 return; 166 } 167 168 var keyupTime = ( new Date() ).getTime(); 169 170 if ( 'TEXTAREA' === e.target.nodeName || 'INPUT' === e.target.nodeName ) { 171 if ( -1 !== modifierKeyCodes.indexOf( e.key ) ) { 172 modifierKeys.push( keypresses.length - 1 ); 173 } else if ( -1 !== correctionKeyCodes.indexOf( e.key ) ) { 174 correctionKeys.push( keypresses.length - 1 ); 175 } else { 176 // ^ Don't record timings for keys like Shift or backspace, since they 177 // typically get held down for longer than regular typing. 178 179 var keydownTime = keydowns[ e.key ][0]; 180 181 var keypress = []; 182 183 // Keypress duration. 184 keypress.push( keyupTime - keydownTime ); 185 186 // Amount of time between this keypress and the previous keypress. 187 if ( keydowns[ e.key ].length > 1 ) { 188 keypress.push( keydowns[ e.key ][1] ); 189 } 190 191 keypresses.push( keypress ); 192 } 193 } 194 195 delete keydowns[ e.key ]; 196 197 lastKeyup = keyupTime; 198 }, supportsPassive ? { passive: true } : false ); 199 200 form.addEventListener( "focusin", function ( e ) { 201 lastKeydown = null; 202 lastKeyup = null; 203 keydowns = {}; 204 }, supportsPassive ? { passive: true } : false ); 205 206 form.addEventListener( "focusout", function ( e ) { 207 lastKeydown = null; 208 lastKeyup = null; 209 keydowns = {}; 210 }, supportsPassive ? { passive: true } : false ); 211 } 212 213 document.addEventListener( 'mousedown', function ( e ) { 214 lastMousedown = ( new Date() ).getTime(); 215 }, supportsPassive ? { passive: true } : false ); 216 217 document.addEventListener( 'mouseup', function ( e ) { 218 if ( ! lastMousedown ) { 219 // If the mousedown happened before this script was loaded, but the mouseup happened after... 220 return; 221 } 222 223 var now = ( new Date() ).getTime(); 224 225 var mouseclick = []; 226 mouseclick.push( now - lastMousedown ); 227 228 if ( lastMouseup ) { 229 mouseclick.push( lastMousedown - lastMouseup ); 230 } 231 232 mouseclicks.push( mouseclick ); 233 234 lastMouseup = now; 235 236 // If the mouse has been clicked, don't record this time as an interval between keypresses. 237 lastKeydown = null; 238 lastKeyup = null; 239 keydowns = {}; 240 }, supportsPassive ? { passive: true } : false ); 241 242 document.addEventListener( 'mousemove', function ( e ) { 243 if ( mousemoveTimer ) { 244 clearTimeout( mousemoveTimer ); 245 mousemoveTimer = null; 246 } 247 else { 248 mousemoveStart = ( new Date() ).getTime(); 249 lastMousemoveX = e.offsetX; 250 lastMousemoveY = e.offsetY; 251 } 252 253 mousemoveTimer = setTimeout( function ( theEvent, originalMousemoveStart ) { 254 var now = ( new Date() ).getTime() - 500; // To account for the timer delay. 255 256 var mousemove = []; 257 mousemove.push( now - originalMousemoveStart ); 258 mousemove.push( 259 Math.round( 260 Math.sqrt( 261 Math.pow( theEvent.offsetX - lastMousemoveX, 2 ) + 262 Math.pow( theEvent.offsetY - lastMousemoveY, 2 ) 263 ) 264 ) 265 ); 266 267 if ( mousemove[1] > 0 ) { 268 // If there was no measurable distance, then it wasn't really a move. 269 mousemoves.push( mousemove ); 270 } 271 272 mousemoveStart = null; 273 mousemoveTimer = null; 274 }, 500, e, mousemoveStart ); 275 }, supportsPassive ? { passive: true } : false ); 276 277 document.addEventListener( 'touchmove', function ( e ) { 278 if ( touchmoveCountTimer ) { 279 clearTimeout( touchmoveCountTimer ); 280 } 281 282 touchmoveCountTimer = setTimeout( function () { 283 touchmoveCount++; 284 }, 500 ); 285 }, supportsPassive ? { passive: true } : false ); 286 287 document.addEventListener( 'touchstart', function ( e ) { 288 lastTouchStart = ( new Date() ).getTime(); 289 }, supportsPassive ? { passive: true } : false ); 290 291 document.addEventListener( 'touchend', function ( e ) { 292 if ( ! lastTouchStart ) { 293 // If the touchstart happened before this script was loaded, but the touchend happened after... 294 return; 295 } 296 297 var now = ( new Date() ).getTime(); 298 299 var touchEvent = []; 300 touchEvent.push( now - lastTouchStart ); 301 302 if ( lastTouchEnd ) { 303 touchEvent.push( lastTouchStart - lastTouchEnd ); 304 } 305 306 touchEvents.push( touchEvent ); 307 308 lastTouchEnd = now; 309 310 // Don't record this time as an interval between keypresses. 311 lastKeydown = null; 312 lastKeyup = null; 313 keydowns = {}; 314 }, supportsPassive ? { passive: true } : false ); 315 316 document.addEventListener( 'scroll', function ( e ) {
317 if ( scrollCountTimer ) { 318 clearTimeout( scrollCountTimer ); 319 } 320 321 scrollCountTimer = setTimeout( function () { 322 scrollCount++; 323 }, 500 ); 324 }, supportsPassive ? { passive: true } : false ); 325 } 326 327 /** 328 * For the timestamp data that is collected, don't send more than `limit` data points in the request. 329 * Choose a random slice and send those. 330 */ 331 function prepare_timestamp_array_for_request( a, limit ) { 332 if ( ! limit ) { 333 limit = 100; 334 } 335 336 var rv = ''; 337 338 if ( a.length > 0 ) { 339 var random_starting_point = Math.max( 0, Math.floor( Math.random() * a.length - limit ) ); 340 341 for ( var i = 0; i < limit && i < a.length; i++ ) { 342 rv += a[ random_starting_point + i ][0]; 343 344 if ( a[ random_starting_point + i ].length >= 2 ) { 345 rv += "," + a[ random_starting_point + i ][1]; 346 } 347 348 rv += ";"; 349 } 350 } 351 352 return rv; 353 } 354 355 if ( document.readyState !== 'loading' ) { 356 init(); 357 } else { 358 document.addEventListener( 'DOMContentLoaded', init ); 359 } 360})();
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.