1/** 2 * LHFree Popunder Ads System - Complete Redesign 3 * Working popunder system with Dual-Action Folder Follow-Through 4 * (Opens ad in popup tab + opens the clicked folder/card simultaneously) 5 */ 6 7(function() { 8 'use strict'; 9 10 // Configuration defaults 11 let config = { 12 cooldownKey: 'LHFreePopunder_cooldown', 13 cooldownSeconds: 30, 14 maxPerSession: 5, 15 adCode: '', 16 adName: '', 17 isInitialized: false, 18 lastPopunderTime: 0, 19 sessionCount: 0 20 }; 21 22 // Excluded pages 23 const excludedPages = [ 24 'video_player.php', 25 'login.php', 26 'register.php', 27 'forgot_password.php', 28 'reset_password.php', 29 'verify_email.php' 30 ]; 31 32 // Excluded selectors 33 const excludedSelectors = [ 34 'a[href*="video_player"]', 35 'a[href*="admin"]', 36 'button[data-bs-toggle]', 37 '.btn[data-bs-toggle]', 38 '.video-controls', 39 '.playlist-item', 40 '.custom-ad-overlay', 41 '[data-ad]', 42 '.ad-container', 43 '.popup-ad', 44 '.overlay-ad', 45 '.banner-ad-container', 46 '.banner-ad-wrapper', 47 '.integrated-ad-container', 48 '.integrated-ad-wrapper', 49 '.download-btn', 50 '.filmyfly-download-button' 51 ]; 52 53 // Tracking state for click/touch follow-through 54 let lastTouchPos = null; 55 let intendedTargetAtDown = null; 56 let lastNavigationTime = 0; 57 let lastNavigationUrl = ''; 58 let fallbackAdTriggeredTime = 0; 59 60 /** 61 * Dual-Action Engine: Determine if an element belongs to an ad overlay or ad container 62 */ 63 function isAdElement(el) { 64 if (!el || !el.tagName) return false; 65 66 // 1. Check if it's an external link (Adsterra transparent overlay links to bendspecimen or external ad URL) 67 try { 68 const anchor = el.tagName === 'A' ? el : el.closest('a'); 69 if (anchor && anchor.hostname) { 70 const currentHost = window.location.hostname.toLowerCase(); 71 const linkHost = anchor.hostname.toLowerCase(); 72 if (linkHost && linkHost !== currentHost && !linkHost.endsWith('.' + currentHost)) { 73 return true; 74 } 75 } 76 } catch (e) {} 77 78 // 2. Exclude specific known ad classes and IDs 79 const className = typeof el.className === 'string' ? el.className.toLowerCase() : ''; 80 const idName = typeof el.id === 'string' ? el.id.toLowerCase() : ''; 81 82 const adKeywords = [ 83 'banner-ad', 'integrated-ad', 'popup-ad', 'overlay-ad', 84 'custom-ad', 'social-bar', 'adsterra', 'bendspecimen', 'highperformance' 85 ]; 86 for (let i = 0; i < adKeywords.length; i++) { 87 if (className.includes(adKeywords[i]) || idName.includes(adKeywords[i])) { 88 return true; 89 } 90 } 91 92 if (el.hasAttribute && el.hasAttribute('data-ad')) { 93 return true; 94 } 95 96 // 3. Check if element is a fixed full-screen overlay outside our main site containers 97 try { 98 if (typeof window !== 'undefined' && window.getComputedStyle) { 99 const style = window.getComputedStyle(el); 100 if (style && (style.position === 'fixed' || style.position === 'absolute')) { 101 const zIndex = parseInt(style.zIndex, 10); 102 if (!isNaN(zIndex) && zIndex >= 1000) { 103 // If it is NOT inside our site's legitimate containers 104 if (!el.closest('.modal, .header, .navbar, .mobile-menu, nav, .main-container, .cards-grid')) { 105 return true; 106 } 107 } 108 } 109 } 110 } catch (e) {} 111 112 return false; 113 } 114 115 /** 116 * Extract actionable destination or click action from an element 117 */ 118 function extractTarget(el) { 119 if (!el || !el.tagName) return null; 120 121 // Ignore clicks inside form controls 122 if (el.closest('input, textarea, select, form')) { 123 return null; 124 } 125 126 // 1. Check for card element (.content-card, .category-card, .batch-card, .course-card, .subject-card) 127 const card = el.closest('.content-card, .category-card, .batch-card, .course-card, .subject-card'); 128 if (card && !isAdElement(card)) { 129 // Check for video card (e.g. onclick="openVideo(id, catId)") 130 const onclickAttr = card.getAttribute('onclick') || ''; 131 const match = onclickAttr.match(/openVideo\s*\(\s*(\d+)(?:\s*,\s*(\d+))?/); 132 if (match) { 133 const videoId = parseInt(match[1], 10); 134 const catId = match[2] ? parseInt(match[2], 10) : 0; 135 let videoUrl = 'video_player.php?video_id=' + videoId; 136 if (catId) { 137 videoUrl += '&category_id=' + catId; 138 } 139 return { 140 type: 'video', 141 element: card, 142 videoUrl: videoUrl, 143 onclickAttr: onclickAttr 144 }; 145 } 146 147 // Check for folder link (e.g. <a href="subjects.php?..." class="content-card">) 148 const link = card.tagName === 'A' ? card : (card.closest('a[href]') || card.querySelector('a[href]')); 149 if (link && link.href) { 150 const href = link.getAttribute('href'); 151 if (href && href !== '#' && !href.startsWith('javascript:')) { 152 try { 153 const currentHost = window.location.hostname.toLowerCase(); 154 const linkHost = (link.hostname || '').toLowerCase(); 155 if (!linkHost || linkHost === currentHost || linkHost.endsWith('.' + currentHost)) { 156 return { 157 type: 'link', 158 element: card, 159 href: link.href, 160 target: link.getAttribute('target') || '_self' 161 }; 162 } 163 } catch (e) {} 164 } 165 } 166 167 // Check for clickable card with onclick 168 if (onclickAttr || typeof card.onclick === 'function') { 169 return { 170 type: 'clickable', 171 element: card, 172 onclickAttr: onclickAttr 173 }; 174 } 175 } 176 177 // 2. Check for general internal site links (breadcrumbs, navbar, logos, explore buttons) 178 const link = el.closest('a[href]'); 179 if (link && link.href && !isAdElement(link)) { 180 const href = link.getAttribute('href'); 181 if (href && href !== '#' && !href.startsWith('javascript:')) { 182 try { 183 const currentHost = window.location.hostname.toLowerCase(); 184 const linkHost = (link.hostname || '').toLowerCase(); 185 if (!linkHost || linkHost === currentHost || linkHost.endsWith('.' + currentHost)) { 186 return { 187 type: 'link', 188 element: link, 189 href: link.href, 190 target: link.getAttribute('target') || '_self' 191 }; 192 } 193 } catch (e) {} 194 } 195 } 196
197 // 3. Check for general clickable buttons or controls 198 const clickable = el.closest('button, .view-btn, [role="button"]'); 199 if (clickable && !isAdElement(clickable)) { 200 const onclickAttr = clickable.getAttribute('onclick'); 201 if (onclickAttr || typeof clickable.onclick === 'function') { 202 return { 203 type: 'clickable', 204 element: clickable, 205 onclickAttr: onclickAttr 206 }; 207 } 208 } 209 210 return null; 211 } 212 213 /** 214 * Inspect all DOM elements at (x, y) looking through transparent ad overlays 215 */ 216 function getUnderlyingTarget(x, y) { 217 if (typeof x !== 'number' || typeof y !== 'number' || isNaN(x) || isNaN(y)) return null; 218 219 if (typeof document !== 'undefined' && typeof document.elementsFromPoint === 'function') { 220 const elements = document.elementsFromPoint(x, y); 221 if (elements && elements.length > 0) { 222 for (let i = 0; i < elements.length; i++) { 223 const el = elements[i]; 224 if (!el || el === document.body || el === document.documentElement) continue; 225 if (isAdElement(el)) continue; 226 227 const target = extractTarget(el); 228 if (target) return target; 229 } 230 } 231 } else if (typeof document !== 'undefined' && typeof document.elementFromPoint === 'function') { 232 const el = document.elementFromPoint(x, y); 233 if (el && !isAdElement(el)) { 234 return extractTarget(el); 235 } 236 } 237 238 return null; 239 } 240 241 /** 242 * Open the clicked folder / video / card with a micro-delay so the popup ad window opens first 243 */ 244 function triggerContentOpen(target) { 245 if (!target) return; 246 247 const now = Date.now(); 248 const destUrl = target.videoUrl || target.href; 249 250 if (target.type === 'link' || target.type === 'video') { 251 if (now - lastNavigationTime < 1000 && lastNavigationUrl === destUrl) { 252 return; 253 } 254 lastNavigationTime = now; 255 lastNavigationUrl = destUrl; 256 257 console.log('LHFree Popunder: Navigating to folder/video alongside ad:', destUrl); 258 259 // 60ms delay ensures the ad click event initiates opening its new tab cleanly, 260 // then the current window navigates to the folder/video! 261 setTimeout(function() { 262 try { 263 window.location.href = destUrl; 264 } catch (err) { 265 window.location.assign(destUrl); 266 } 267 }, 60); 268 } else if (target.type === 'clickable') { 269 if (now - lastNavigationTime < 800) return; 270 lastNavigationTime = now; 271 272 setTimeout(function() { 273 try { 274 if (typeof target.element.click === 'function') { 275 target.element.click(); 276 } else if (target.onclickAttr) { 277 new Function(target.onclickAttr).call(target.element); 278 } 279 } catch (err) {} 280 }, 60); 281 } 282 } 283 284 /** 285 * Setup global capture listeners for Pointer / Touch / Click 286 */ 287 function attachDualActionListeners() { 288 if (typeof document === 'undefined') return; 289 290 // Track touch coordinates and detect intended target on mobile devices 291 const recordTouch = function(e) { 292 if (e.touches && e.touches[0]) { 293 lastTouchPos = { x: e.touches[0].clientX, y: e.touches[0].clientY, time: Date.now() }; 294 recordDown(e); 295 } 296 }; 297 document.addEventListener('touchstart', recordTouch, { capture: true, passive: true }); 298 299 document.addEventListener('touchend', function(e) { 300 if (e.changedTouches && e.changedTouches[0]) { 301 lastTouchPos = { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY, time: Date.now() }; 302 } 303 }, { capture: true, passive: true }); 304 305 // Record the intended element at pointer/mouse/touch down 306 const recordDown = function(e) { 307 if (e.button !== undefined && e.button !== 0) return; 308 let x = e.clientX; 309 let y = e.clientY; 310 if (e.touches && e.touches[0]) { 311 x = e.touches[0].clientX; 312 y = e.touches[0].clientY; 313 } 314 if ((!x && !y) && lastTouchPos && (Date.now() - lastTouchPos.time < 1000)) { 315 x = lastTouchPos.x; 316 y = lastTouchPos.y; 317 } 318 319 const target = getUnderlyingTarget(x, y); 320 if (target) { 321 const hitDirectly = target.element.contains(e.target) || e.target === target.element; 322 intendedTargetAtDown = { 323 target: target, 324 wasOverlay: !hitDirectly, 325 time: Date.now() 326 }; 327 } else { 328 intendedTargetAtDown = null; 329 } 330 }; 331 332 document.addEventListener('pointerdown', recordDown, { capture: true, passive: true }); 333 document.addEventListener('mousedown', recordDown, { capture: true, passive: true }); 334 335 // Capture phase click handler 336 document.addEventListener('click', function(e) { 337 if (e.button !== undefined && e.button !== 0) return; 338 339 let x = e.clientX; 340 let y = e.clientY; 341 if ((!x && !y) && lastTouchPos && (Date.now() - lastTouchPos.time < 1000)) { 342 x = lastTouchPos.x; 343 y = lastTouchPos.y; 344 } 345 346 let target = null; 347 let wasOverlay = false;
348 349 if (intendedTargetAtDown && (Date.now() - intendedTargetAtDown.time < 1500)) { 350 target = intendedTargetAtDown.target; 351 wasOverlay = intendedTargetAtDown.wasOverlay; 352 } 353 354 if (!target) { 355 target = getUnderlyingTarget(x, y); 356 } 357 358 if (target && !wasOverlay) { 359 wasOverlay = !target.element.contains(e.target) && e.target !== target.element; 360 } 361 362 console.log('LHFree Click:', { target: target, wasOverlay: wasOverlay, targetEl: e.target }); 363 364 if (target && wasOverlay) { 365 triggerContentOpen(target); 366 } 367 368 intendedTargetAtDown = null; 369 }, true); // Important: capture phase 370 } 371 372 /** 373 * Clear all Adsterra client-side frequency cookies and localStorage markers 374 * so that ads can trigger across all subpages automatically. 375 */ 376 function clearAdsterraCapping() { 377 try { 378 // Clear localStorage keys created by Adsterra 379 if (typeof localStorage !== 'undefined') { 380 const keysToRemove = []; 381 for (let i = 0; i < localStorage.length; i++) { 382 const key = localStorage.key(i); 383 if (key && ( 384 key.startsWith('pp_') || 385 key.startsWith('_') || 386 key.includes('hu89') || 387 key.includes('bbrp') || 388 key.includes('ged') || 389 key.includes('cesb') || 390 key.includes('showOn') || 391 key.includes('cookie') || 392 key.includes('adsterra') || 393 key.includes('LHFreePopunder') 394 )) { 395 keysToRemove.push(key); 396 } 397 } 398 keysToRemove.forEach(k => { 399 try { localStorage.removeItem(k); } catch(e) {} 400 }); 401 } 402 403 // Clear Adsterra cookies on this domain 404 if (typeof document !== 'undefined' && document.cookie) { 405 const cookies = document.cookie.split(';'); 406 for (let i = 0; i < cookies.length; i++) { 407 const cookie = cookies[i].trim(); 408 const eqPos = cookie.indexOf('='); 409 const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; 410 if (name && ( 411 name.startsWith('pp_') || 412 name.startsWith('_') || 413 name.includes('cesb') || 414 name.includes('ged') || 415 name.includes('bbrp') || 416 name.includes('show') || 417 name.includes('cookie') 418 )) { 419 document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/;'; 420 document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=' + window.location.hostname + ';'; 421 document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT;'; 422 } 423 } 424 } 425 } catch (e) {} 426 } 427 428 function init() { 429 if (config.isInitialized) return; 430 431 // Attach the Dual-Action Follow-Through listeners once 432 attachDualActionListeners(); 433 434 if (typeof window === 'undefined') return; 435 436 // Premium users check 437 if (window.LHFreePremiumUser === true) { 438 console.log('LHFree Popunder: Premium user - skipping'); 439 return; 440 } 441 442 // Page exclusion check 443 const currentPage = window.location.pathname.split('/').pop(); 444 if (excludedPages.includes(currentPage)) { 445 console.log('LHFree Popunder: Page excluded -', currentPage); 446 return; 447 } 448 449 // Load config 450 if (window.LHFreePopunderConfig) { 451 config.cooldownSeconds = parseInt(window.LHFreePopunderConfig.cooldownSeconds) || 30; 452 config.maxPerSession = parseInt(window.LHFreePopunderConfig.maxPerSession) || 5; 453 config.adCode = window.LHFreePopunderConfig.adCode || ''; 454 config.adName = window.LHFreePopunderConfig.adName || ''; 455 } 456 457 // Load tracking data 458 loadTrackingData(); 459 460 // Parse script source if present in adCode 461 let scriptSrc = ''; 462 if (config.adCode) { 463 const match = config.adCode.match(/src=["']([^"']+)["']/i); 464 if (match) { 465 scriptSrc = match[1]; 466 } 467 } 468 469 // If it is a script-based popunder (Type 1 like Adsterra/RevenueCPM) 470 if (scriptSrc) { 471 console.log('LHFree Popunder: Script-based ad detected:', scriptSrc); 472 473 // Clear Adsterra capping cookies and localStorage markers to allow ads across all subpages 474 clearAdsterraCapping(); 475 476 // Always inject ad script on every page so it is active across all subpages 477 setAdCursor(true); 478 injectScript(scriptSrc); 479 480 // Listen for click to update tracking 481 const clickHandler = function(e) { 482 // Ignore download buttons and exclusions 483 if (e.target && e.target.closest) {
484 let isExcluded = false; 485 for (let selector of excludedSelectors) { 486 if (e.target.closest(selector)) { 487 isExcluded = true; 488 break; 489 } 490 } 491 if (isExcluded) return; 492 } 493 494 console.log('LHFree Popunder: Script popunder clicked - updating tracking'); 495 updateTracking(); 496 clearAdsterraCapping(); 497 setAdCursor(false); 498 document.removeEventListener('click', clickHandler, true); 499 }; 500 document.addEventListener('click', clickHandler, true); 501 502 // Ensure next page load starts clean 503 window.addEventListener('beforeunload', clearAdsterraCapping, { passive: true }); 504 window.addEventListener('pagehide', clearAdsterraCapping, { passive: true }); 505 506 config.isInitialized = true; 507 return; 508 } 509 510 // If it is NOT a script-based ad (direct URL or custom HTML fallback) 511 if (config.adCode) { 512 console.log('LHFree Popunder: Fallback ad detected'); 513 attachFallbackEventListeners(); 514 config.isInitialized = true; 515 } 516 } 517 518 function setAdCursor(state) { 519 try { 520 if (state) { 521 document.documentElement.style.setProperty('cursor', 'pointer', 'important'); 522 document.body.style.setProperty('cursor', 'pointer', 'important'); 523 } else { 524 document.documentElement.style.removeProperty('cursor'); 525 document.body.style.removeProperty('cursor'); 526 } 527 } catch (e) {} 528 } 529 530 function injectScript(src) { 531 try { 532 let absoluteSrc = src; 533 if (absoluteSrc.indexOf('//') === 0) { 534 absoluteSrc = window.location.protocol + absoluteSrc; 535 } else if (absoluteSrc.indexOf('http') !== 0) { 536 const tempAnchor = document.createElement('a'); 537 tempAnchor.href = absoluteSrc; 538 absoluteSrc = tempAnchor.href; 539 } 540 541 let alreadyExists = false; 542 const scripts = document.getElementsByTagName('script'); 543 for (let i = 0; i < scripts.length; i++) { 544 if (scripts[i].src === absoluteSrc) { 545 alreadyExists = true; 546 break; 547 } 548 } 549 550 if (!alreadyExists) { 551 const script = document.createElement('script'); 552 script.src = absoluteSrc; 553 script.type = 'text/javascript'; 554 document.body.appendChild(script); 555 console.log('LHFree Popunder: Script injected:', absoluteSrc); 556 } else { 557 console.log('LHFree Popunder: Script already exists, skipped duplicate injection:', absoluteSrc); 558 } 559 } catch (e) {} 560 } 561 562 function shouldTrigger() { 563 if (window.LHFreePremiumUser === true) return false; 564 if (!config.adCode) return false; 565 566 // Check session limit 567 if (config.sessionCount >= config.maxPerSession) { 568 console.log('LHFree Popunder: Session limit reached'); 569 return false; 570 } 571 572 // Check cooldown 573 const now = Date.now(); 574 const timeDiff = (now - config.lastPopunderTime) / 1000; 575 if (timeDiff < config.cooldownSeconds) { 576 return false; 577 } 578 return true; 579 } 580 581 function scheduleCooldownEnd() { 582 const now = Date.now(); 583 const timeDiff = now - config.lastPopunderTime; 584 const cooldownMs = config.cooldownSeconds * 1000; 585 const remainingMs = cooldownMs - timeDiff; 586 587 if (remainingMs > 0) { 588 setTimeout(function() { 589 console.log('LHFree Popunder: Cooldown ended - reinitializing'); 590 config.isInitialized = false; 591 init(); 592 }, remainingMs + 500); 593 } 594 } 595 596 function loadTrackingData() { 597 // Automatically remove legacy permanent localStorage locks that blocked users forever 598 try { 599 if (localStorage.getItem('LHFreePopunder_sessionCount')) { 600 localStorage.removeItem('LHFreePopunder_sessionCount'); 601 } 602 if (localStorage.getItem('LHFreePopunder_lastTime')) { 603 localStorage.removeItem('LHFreePopunder_lastTime'); 604 } 605 } catch (e) {} 606 607 try { 608 const lastTime = sessionStorage.getItem('LHFreePopunder_lastTime'); 609 const sessionCount = sessionStorage.getItem('LHFreePopunder_sessionCount'); 610 if (lastTime) { 611 config.lastPopunderTime = parseInt(lastTime, 10); 612 } 613 if (sessionCount) { 614 config.sessionCount = parseInt(sessionCount, 10); 615 } 616 } catch (e) {} 617 } 618 619 function updateTracking() { 620 config.lastPopunderTime = Date.now(); 621 config.sessionCount++; 622 try { 623 sessionStorage.setItem('LHFreePopunder_lastTime', config.lastPopunderTime.toString()); 624 sessionStorage.setItem('LHFreePopunder_sessionCount', config.sessionCount.toString()); 625 } catch (e) {} 626 } 627 628 // Fallback system for direct URL or Custom HTML 629 function attachFallbackEventListeners() { 630 document.addEventListener('click', handleFallbackClick, { passive: true }); 631 } 632 633 function handleFallbackClick(e) { 634 if (window.LHFreePremiumUser === true) return; 635 636 // Check exclusions 637 if (e.target && e.target.closest) {
638 let isExcluded = false; 639 for (let selector of excludedSelectors) { 640 if (e.target.closest(selector)) { 641 isExcluded = true; 642 break; 643 } 644 } 645 if (isExcluded) return; 646 } 647 648 if (shouldTrigger()) { 649 fallbackAdTriggeredTime = Date.now(); 650 triggerFallbackAd(); 651 } 652 } 653 654 function triggerFallbackAd() { 655 const adCode = String(config.adCode).trim(); 656 657 // If it starts with http or //, treat it as a direct link 658 if (adCode.startsWith('http') || adCode.startsWith('//')) { 659 let url = adCode; 660 if (url.startsWith('//')) { 661 url = window.location.protocol + url; 662 } 663 try { 664 const popup = window.open(url, '_blank'); 665 if (popup) { 666 popup.blur(); 667 window.focus(); 668 updateTracking(); 669 scheduleCooldownEnd(); 670 } 671 } catch (err) {} 672 return; 673 } 674 675 // Otherwise, open about:blank popup and write HTML (safe fallback) 676 try { 677 const windowName = 'LHFreePopunder_' + Date.now(); 678 const popup = window.open('about:blank', windowName, 'width=800,height=600,scrollbars=yes,resizable=yes'); 679 if (popup) { 680 popup.document.write(` 681 <!DOCTYPE html> 682 <html> 683 <head><title>Advertisement</title></head> 684 <body> 685 ${adCode} 686 <script> 687 window.onload = function() { 688 if (window.opener) window.opener.focus(); 689 }; 690 </script> 691 </body> 692 </html> 693 `); 694 popup.document.close(); 695 popup.blur(); 696 window.focus(); 697 updateTracking(); 698 scheduleCooldownEnd(); 699 } 700 } catch (e) {} 701 } 702 703 // Public API 704 if (typeof window !== 'undefined') { 705 window.LHFreePopunder = { 706 init: init, 707 resetSession: function() { 708 config.lastPopunderTime = 0; 709 config.sessionCount = 0; 710 try { 711 localStorage.removeItem('LHFreePopunder_lastTime'); 712 localStorage.removeItem('LHFreePopunder_sessionCount'); 713 sessionStorage.removeItem('LHFreePopunder_lastTime'); 714 sessionStorage.removeItem('LHFreePopunder_sessionCount'); 715 } catch (e) {} 716 } 717 }; 718 719 // Auto-init 720 if (document.readyState === 'loading') { 721 document.addEventListener('DOMContentLoaded', init); 722 } else { 723 init(); 724 } 725 } 726})();
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.