1//document.querySelectorAll(`svg,div:not([class*="background"]`) 2 3class BDN_Paywall { 4 5 debugOutput = false; 6 debugTrace = false; 7 metadata = { }; 8 pixels = [ ]; 9 uuid = ""; 10 urlParams = new URLSearchParams(window.location.search); 11 12 13 14 failSafeTimer = 0; 15 16 // List of target classes to check for the Please dont subscribe twice notification 17 targetClasses = [ 18 'pelcro-payment-container' 19 ]; 20 viafouraToggle = true; // disabled for ads testing 21 isGamePage = false; 22 23 // STN video player (Voltax) â set false, or comment out the 24 // this.injectSTNPlayer() call in decision(), to disable during testing. 25 stnPlayerToggle = true; 26 stnPlayerFired = false; 27 28 constructor(metadata) { 29 30 this.hasFiredAlert = false; 31 32 document.addEventListener('DOMContentLoaded', () => { 33 34 35 const observer = new MutationObserver((mutations) => { 36 mutations.forEach((mutation) => { 37 console.log("DUPE TEST", this.metadata.subscriberFlag, !this.urlParams.has('view','upgrade-flow')); 38 if ( 39 (document.querySelector('.pelcro-payment-container') || 40 document.querySelector('.pelcro-subscription-create-view')) && 41 this.metadata.subscriberFlag && 42 !this.hasFiredAlert && 43 !this.urlParams.has('view','upgrade-flow') 44 ) { 45 console.log("Payment or subscription create modal detected, showing alert"); 46 this.showDupeAlert(); 47 this.hasFiredAlert = true; // Set flag to prevent repeats 48 observer.disconnect(); // Stop observing 49 } 50 }); 51 }); 52 53 // Start observing the DOM 54 observer.observe(document.body, { 55 childList: true, 56 subtree: true, 57 }); 58 59 // Load Trinity Audio player (always, to ensure it's ready before paywall decision) 60 //this.loadTrinity(); 61 62 //this.loadGistAIWidget(); 63 }); 64 65 // Add smooth scroll CSS for Engagement Starter 66 const style = document.createElement('style'); 67 style.textContent = ` 68 html { 69 scroll-behavior: smooth; 70 } 71 `; 72 document.head.appendChild(style); 73 if (this.debugOutput) console.log('Injected smooth scroll CSS'); 74 75 76 const isPost = document.querySelector('article[id^="post-"]'); 77 if (isPost && this.viafouraToggle) { 78 this.injectSingleCommentCountWidget(); 79 } 80 81 //this.debugOutput = this.urlParams.has("bdnpaywalldebug"); 82 this.debugOutput = true; 83 this.debugTrace = this.urlParams.has("bdnpaywalltrace"); 84 85 this.metadata = metadata; 86 this.uuid = crypto.randomUUID().split("-")[0]; 87 88 89 // Check serverUser for valid subscriber data 90 if (this.debugOutput) console.log("serverUser", this.metadata); 91 if (this.metadata.serverUser && Object.keys(this.metadata.serverUser).length > 0) { 92 if (this.debugOutput) console.log("Using server-provided user data:", this.metadata.serverUser); 93 this.metadata.userObj = this.metadata.serverUser; 94 95 // Check adlite entitlement immediately 96 this.checkAdliteEntitlement(); 97 // Check for server-provided subscriber status 98 if (this.metadata.serverUser.isServerSubscriber) { 99 if (this.debugOutput) console.log("Server user is a subscriber, setting subscriberFlag"); 100 this.metadata.subscriberFlag = true; 101 // Trigger decision immediately to skip paywall 102 this.decision("server-user"); 103 } 104 } 105 106 107 if(this.debugTrace) { 108 console.log("BDN Paywall Stack Trace:"); 109 console.trace(); 110 } 111 112 if(this.debugOutput) { 113 console.log("BDN Paywall Init!"); 114 console.log(`Window reference set to: ${this.uuid}`); 115 } 116 117 //this line just stores the class instance in a variable in the window object 118 //that is obsure and not obvious so the garbage collector doesn't free it 119 //and moves around on the end user for obfuscation 120 window[this.uuid] = [ [ { "stash" : this } ] ]; 121 122 //check to see if the paywall function has not run, if it has not run then stand up the listener event 123 if(this.metadata.paywallRunCheck == "0") { 124 //load the event listner of we need to add an event for PelcroSiteLoaded 125 document.addEventListener("PelcroSiteLoaded", this, false); 126 document.addEventListener("PelcroUserLoaded", this, false); 127 document.addEventListener("PelcroUserLoggedIn", this, false); 128 } 129 130 // Trigger Viafoura logout on Pelcro user logout 131 document.addEventListener("PelcroUserLogout", () => { 132 this.logout(); 133 if (window.vf && window.vf.session && window.vf.session.logout) { 134 if (this.debugOutput) console.log('Triggering Viafoura logout on PelcroUserLogout'); 135 let viafouraDivs = document.querySelectorAll('.viafoura');
136 viafouraDivs.forEach(div => { 137 div.style.display = 'none'; 138 if (this.debugOutput) console.log('Hid Viafoura widget:', div); 139 }); 140 window.vf.session.logout(); 141 } else { 142 if (this.debugOutput) console.error('Cannot trigger Viafoura logout: window.vf.session.logout unavailable'); 143 } 144 }); 145 146 //failsave to make sure that the decision gets call if all else fails 147 this.failSafeTimer = setTimeout(() => { 148 if(this.debugOutput) console.log("Calling Decision From Failsafe Timer!"); 149 this.failSafeTimer = 0; 150 this.decision("failsafe-timer"); 151 }, 10000); 152 } 153 154 injectAdliteMetaTags() { 155 156 if (this.debugOutput) console.log('NMRAIL'); 157 const target = document.querySelector('#top-posts-3'); 158 if (target) { 159 const newDiv = document.createElement('div'); 160 newDiv.id = 'nmrail'; 161 target.parentNode.insertBefore(newDiv, target.nextSibling); 162 } 163 164 165 166 if (this.debugOutput) console.log("Injecting adlite meta tags"); 167 const head = document.head || document.getElementsByTagName('head')[0]; 168 169 const existingMeta = document.querySelector('meta[name="adexperience"][content="lite"]'); 170 if (existingMeta) { 171 if (this.debugOutput) console.log("Meta tag with name=adexperience, content=lite already exists, skipping injection"); 172 return; 173 } 174 175 const meta1 = document.createElement('meta'); 176 meta1.name = "adexperience"; 177 meta1.content = "adlite"; 178 //head.appendChild(meta1); 179 //if (this.debugOutput) console.log("Injected meta tag: name=adexperience, content=adlite"); 180 181 const meta2 = document.createElement('meta'); 182 meta2.name = "adexperience"; 183 meta2.content = "lite"; 184 head.appendChild(meta2); 185 if (this.debugOutput) console.log("Injected meta tag: name=adexperience, content=lite"); 186 187 document.cookie = "bdn_subscriber=1; path=/; SameSite=Strict; Secure"; 188 } 189 190 showDupeAlert() { 191 console.log("Dupe Event FIREDDDDDDDDDDXD!!!!!!!"); 192 // Create overlay 193 const overlay = document.createElement('div'); 194 overlay.id = 'bdn-paywall-overlay'; 195 overlay.style.cssText = ` 196 position: fixed; 197 top: 0; 198 left: 0; 199 width: 100%; 200 height: 100%; 201 background: rgba(0, 0, 0, 0.5); /* Semi-transparent gray */ 202 z-index: 9998; /* Below modal, above everything else */ 203 `; 204 205 // Create modal 206 const modal = document.createElement('div'); 207 modal.id = 'bdn-paywall-modal'; 208 modal.style.cssText = ` 209 position: fixed; 210 top: 45%; 211 left: 50%; 212 transform: translate(-50%, -50%); 213 width: 300px; 214 background: #fff; 215 padding: 20px; 216 border-radius: 8px; 217 box-shadow: 0 8px 38px rgba(0, 0, 0, 0.5); 218 z-index: 9999999999; /* Above overlay */ 219 `; 220 221 modal.innerHTML = ` 222 <div class="test-modal"> 223 <h2>Your current digital subscription is already active.</h2> 224 <h2> Thank you for supporting Bangor Daily News!</h2> 225 <div style="text-align: center; margin-top: 20px;"> 226 <button id="bdn-proceed-anyway" style="background: #28b45e; color: #fff; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px;">Proceed anyway</button> 227 </div> 228 <p><a href="https://www.bangordailynews.com/" style="color: #28b45e; text-decoration: underline;">Click Here</a> to return to the BDN homepage.</p> 229 </div> 230 `; 231 232 // Append both to body 233 document.body.appendChild(overlay); 234 document.body.appendChild(modal); 235 236 // Close our custom modal and overlay 237 const proceedButton = document.getElementById('bdn-proceed-anyway'); 238 proceedButton.addEventListener('click', () => { 239 console.log("Proceed anyway clicked, closing custom modal"); 240 modal.remove(); 241 overlay.remove(); 242 }); 243 } 244 245 checkTargetClasses() { 246 console.log("check target class check") 247 for (const targetClass of this.targetClasses) { 248 // Check for buttons only (button element or elements with type="button") 249 const buttons = document.querySelectorAll(`button.${targetClass}, [type="button"].${targetClass}`); 250 251 if (buttons.length >
251 0 && !this.urlParams.has('has','upgrade-flow')) { 252 console.log(`Pelcro Subscribe button found: ${targetClass}`); 253 this.showDupeAlert(); // Trigger the modal if any matching button is found 254 break; // Stop after the first match 255 } 256 } 257 } 258 259 260 handleEvent(event) { 261 262 if(this.debugTrace) { 263 console.log("BDN Paywall Stack Trace:"); 264 console.trace(); 265 } 266 267 if(this.debugOutput) { 268 console.log("Event Data:"); 269 console.log(event); 270 } 271 272 switch(event.type) { 273 274 case "PelcroSiteLoaded": 275 276 if(this.debugOutput) console.log("Pelcro Site Loaded Event: "); 277 278 const isAuthenticated = window.Pelcro.user.isAuthenticated(); 279 const user = window.Pelcro.user.read(); 280 281 if (!isAuthenticated || !user) { 282 if(this.debugOutput) console.log("===================>Pelcro is fully loaded, and the user is not authenticated."); 283 this.decision("site-loaded-notloggedin"); 284 } else { 285 if(this.debugOutput) console.log("===================>User is authenticated:"); 286 if(this.debugOutput) console.log(user); 287 288 this.metadata.isAuthenticated = isAuthenticated; 289 290 if(Object.keys(user).length > 0) { 291 //user and site are ready 292 if(this.debugOutput) console.log("Pelcro User Available On Site Load!"); 293 294 this.metadata.userObj = user; 295 clearTimeout(this.failSafeTimer); 296 this.failSafeTimer = 0; 297 298 this.decision("site-loaded-loggedin"); 299 } else { 300 //the user may not be null, but it isn't ready yet 301 //additional functionality will be called on the PelcroUserLoaded event 302 if(this.debugOutput) console.log("Pelcro User NOT Available On Site Load!"); 303 } 304 } 305 break; 306 307 case "PelcroUserLoaded": 308 309 310 311 if(this.debugOutput) console.log("Pelcro User Loaded Event: user/vf "); 312 this.metadata.userObj = event.detail.data; 313 314 this.checkAdliteEntitlement(); 315 316 // Inject Viafoura div, load script, and hide default comments 317 // VF init 318 319 if (window.Pelcro.user.auth && this.metadata.isCommenting !== 'closed' && this.viafouraToggle) { 320 if (this.debugOutput) { 321 console.log('user/vf check', window.Pelcro.user.auth); 322 console.log('user/vf check', (window.Pelcro.user.auth && this.metadata.isCommenting !== 'closed')); 323 console.log('Page URL:', window.location.href); 324 } 325 326 let postId = null; 327 const postElement = document.querySelector('article[id^="post-"]'); 328 if (postElement && postElement.id) { 329 postId = postElement.id.replace('post-', ''); 330 } 331 if (!postId) { 332 if (this.debugOutput) console.error('Could not find WordPress post ID for Viafoura container'); 333 postId = 'default'; 334 } 335 336 if (postElement && postElement.id && postId !== 'default' && postElement.classList.contains('type-post') && this.metadata.isCommenting === 'open') { 337 const mainContentDiv = document.querySelector('.main-content'); 338 if (mainContentDiv) { 339 // Skip conversation widget injection 340 if (!document.querySelector(`#viafoura-comments-${postId}`)) { 341 const viafouraDiv = document.createElement('div'); 342 viafouraDiv.className = 'viafoura'; 343 viafouraDiv.id = `viafoura-comments-${postId}`; 344 viafouraDiv.innerHTML = ` 345 <vf-livequestions vf-container-id="viafoura-comments-${postId}"></vf-livequestions> 346 <vf-conversations-count></vf-conversations-count> 347 <vf-tray-trigger floating="true"></vf-tray-trigger> 348 <vf-conversations vf-container-id="viafoura-comments-${postId}"></vf-conversations> 349 `; 350 mainContentDiv.appendChild(viafouraDiv); 351 if (this.debugOutput) console.log(`Injected Viafoura conversation widget with vf-container-id="viafoura-comments-${postId}" into .main-content`); 352 } else { 353 if (this.debugOutput) console.log(`Skipping Viafoura conversation injection: already present with id="viafoura-comments-${postId}"`); 354 } 355 356 357 // Inject Vertical Trending Articles in Sidebar 358 const sidebarBlock = document.querySelector('#block-18.widget.widget_block'); 359 360 if (sidebarBlock && postId !== 'default') { 361 const sectionWrapper = document.createElement('section'); 362 sectionWrapper.id = 'bdn-trending-sidebar'; 363 sectionWrapper.className = 'widget widget_block bdn-sidebar-trending'; // Add custom classes for styling if needed 364 365 const trendingDiv = document.createElement('div'); 366 trendingDiv.className = 'viafoura'; 367 trendingDiv.innerHTML = ` 368 <vf-trending-articles 369 title="Join The Conversation" 370 limit="5" 371 days-published="7" 372 trend-window="1" 373 sort="comments" 374 view="full" 375 vf-container-id="viafoura-comments-${postId}" 376 ></vf-trending-articles> 377 `; 378 sectionWrapper.appendChild(trendingDiv); 379 380 sidebarBlock.insertAdjacentElement('beforebegin', sectionWrapper); 381 if (this.debugOutput) console.log(`Injected vertical Trending Articles section above #block-18 with vf-container-id="viafoura-comments-${postId}"`); 382 } else { 383 if (this.debugOutput) console.warn('Sidebar block (#block-18) not found or invalid post ID, skipping vertical Trending Articles injection'); 384 } 385 386 387 // Check for og:image or vf:image meta tag 388 const ogImage = document.querySelector('meta[property="og:image"]'); 389 const vfImage = document.querySelector('meta[name="vf:image"]'); 390 if (!ogImage && !vfImage) { 391 if (this.debugOutput) console.warn('No og:image or vf:image meta tag found; Trending Conversations images may not display'); 392 } 393 394 // Load Viafoura script if not already loaded 395 if (!document.querySelector('script[src="//cdn.viafoura.net/entry/index.js"]')) { 396 const script = document.createElement('script'); 397 script.src = '//cdn.viafoura.net/entry/index.js'; 398 script.async = true; 399 script.onload = () => { 400 if (this.debugOutput) console.log('Viafoura script loaded successfully'); 401 console.log('Post-load window.vfQ state:', window.vfQ); 402 console.log('Post-load window.viafoura state:', window.viafoura); 403 window.addEventListener('vfContentRecirculationAd', (event) => { 404 if (this.debugOutput) console.log('vfContentRecirculationAd event triggered:', event.detail); 405 }); 406 window.addEventListener('requestConversationStarter', (event) => { 407 if (this.debugOutput) console.log('requestConversationStarter event triggered:', event.detail); 408 }); 409 }; 410 script.onerror = () => { 411 if (this.debugOutput) console.error('Viafoura script failed to load'); 412 }; 413 document.body.appendChild(script); 414 if (this.debugOutput) console.log('Viafoura script injection initiated'); 415 } 416 } else { 417 if (this.debugOutput) console.error('Main content div (.main-content) not found'); 418 } 419 420 // Inject notification tray and bell 421 const body = document.body; 422 if (body && postElement.classList.contains('type-post')) { 423 const trayDiv = document.createElement('div'); 424 trayDiv.className = 'viafoura viafoura-tray'; 425 trayDiv.innerHTML = `<vf-notifications></vf-notifications>`; 426 body.appendChild(trayDiv); 427 if (this.debugOutput) console.log('Injected Viafoura notification tray'); 428 429 const bellDiv = document.createElement('div'); 430 bellDiv.className = 'viafoura viafoura-bell'; 431 bellDiv.innerHTML = `<vf-notification-bell></vf-notification-bell>`; 432 body.appendChild(bellDiv); 433 if (this.debugOutput) console.log('Injected Viafoura notification bell'); 434 } else { 435 if (this.debugOutput) console.error('Document body not found for notification tray/bell injection'); 436 } 437 438 // Hide default WordPress comment section 439 const commentsDiv = document.querySelector('#comments.comments-area'); 440 if (commentsDiv) { 441 commentsDiv.style.display = 'none'; 442 if (this.debugOutput) console.log('Hid default WordPress comment section (#comments.comments-area)'); 443 } else { 444 if (this.debugOutput) console.warn('Default comment section (#comments.comments-area) not found'); 445 } 446 447 // Trigger Viafoura login 448 window.vfQ = window.vfQ || []; 449 window.vfQ.push(() => { 450 if (window.Pelcro.user.auth?.token) { 451 const sessionToken = window.Pelcro.user.auth.token; 452 if (this.debugOutput) console.log('Triggering Viafoura custom login with loggedInCookie_sid:', sessionToken.substring(0, 20) + '...'); 453 window.vf.session.login.cookie(sessionToken); 454 } else { 455 if (this.debugOutput) console.log('No session token available for Viafoura login'); 456 } 457 }); 458 } else { 459 if (this.debugOutput) console.log('Skipping Viafoura div injection: Not a post page or invalid post ID'); 460 } 461 } 462 // End Viafoura inject block 463 // End VF init 464 465 if(this.failSafeTimer > 0) { 466 if(this.debugOutput) console.log(" - terminating wait timer: "); 467 468 clearTimeout(this.failSafeTimer); 469 this.failSafeTimer = 0; 470 } 471 472 const sessionToken = window.Pelcro.user.auth?.token; 473 document.cookie = `loggedInCookie_sid=${encodeURIComponent(sessionToken)}; Secure; SameSite=Strict; Path=/; Max-Age=86400`; 474 if (this.debugOutput) console.log(" - Set Viafoura session cookie:", sessionToken); 475 476 this.decision("user-loaded"); 477 break; 478 479 case "PelcroUserLoggedIn": 480 if (this.debugOutput) console.log("Pelcro User Login Event: "); 481 482 location.reload(); 483 break; 484 } 485 } 486 487 displayWall() { 488 if (this.debugOutput) console.log(" - in display paywall!"); 489 const selectorForPWBlur = '#bdn_paywall_content > p, #bdn_paywall_content > h1, #bdn_paywall_content > h2, #bdn_paywall_content > h3, #bdn_paywall_content > h4, #bdn_paywall_content >h5, #bdn_paywall_content > h6, #bdn_paywall_content > .wp-block-columns > .wp-block-column > p, .flourish-embed, .flourish-chart, .embed-documentcloud'; 490 const selectorForPWMixup = '#bdn_paywall_content > p'; 491 const pTags = document.querySelectorAll(selectorForPWBlur);
492 const crawlerTest = this.urlParams.has('crawler'); 493 let cutoff = 200; 494 495 //Remove Everlit 496 const audioDiv = document.querySelector('.wp-block-custom-everlit-iframe-embed'); 497 if (audioDiv) audioDiv.remove(); 498 499 // Inject Viafoura conversation widget for paywalled content 500 // VF init 501 502 let postId = this.metadata.postId || null; 503 if (!postId && this.viafouraToggle) { 504 const postElement = document.querySelector('article[id^="post-"]'); 505 if (postElement && postElement.id) { 506 postId = postElement.id.replace('post-', ''); 507 } 508 if (!postId) { 509 if (this.debugOutput) console.error('Could not find WordPress post ID for Viafoura container in paywall, using articleId'); 510 postId = this.metadata.articleId || 'default'; 511 } 512 this.metadata.postId = postId; 513 } 514 if (this.debugOutput) console.log('Using postId for paywall Viafoura:', postId); 515 516 // End VF init 517 518 if (pTags.length > 0) { 519 if (pTags[0].textContent.length > cutoff) { 520 pTags[0].style.webkitMaskImage = 'linear-gradient(to bottom, black 10%, transparent 80%)'; 521 pTags[0].style.maskImage = 'linear-gradient(to bottom, black 10%, transparent 80%)'; 522 } 523 524 for (let i = 1; i < pTags.length; i++) { 525 pTags[i].style.filter = 'blur(10px)'; 526 pTags[i].style.userSelect = 'none'; 527 pTags[i].style.webkitUserSelect = 'none'; 528 pTags[i].style.pointerEvents = 'none'; 529 } 530 531 const copy = this.getPaywallCopy(); 532 let html = this.metadata.inline_html; 533 534 if (copy) { 535 if (!document.getElementById('pw2026-styles')) { 536 const pw2026Style = document.createElement('style'); 537 pw2026Style.id = 'pw2026-styles'; 538 pw2026Style.textContent = `#bdn-paywall-view{text-align:unset!important;background-color:transparent!important}#bdn-paywall{background:#c0c0c0;border:1px solid #ddd;max-width:680px;margin:32px auto;font-family:'Bebas Neue',sans-serif;box-shadow:0 2px 12px rgba(0,0,0,.10)}.pw2026-header{background-color:#00723e;display:flex;align-items:center;gap:14px;padding:10px 18px}.pw2026-header-logo{color:#fff;font-family:'Bebas Neue',sans-serif;font-size:28px;letter-spacing:1px;line-height:1;white-space:nowrap}.pw2026-header-cta{color:#fff;font-family:'Bebas Neue',sans-serif;font-size:22px;letter-spacing:.5px;line-height:1}.pw2026-subhead{text-align:center;padding:16px 24px 8px;font-family:'Bebas Neue',sans-serif;font-size:20px;letter-spacing:.3px;line-height:1.3;color:#222}.pw2026-offers{display:flex;gap:16px;padding:12px 20px 20px}.pw2026-card{flex:1;background:#fff;border:1px solid #ccc;display:flex;flex-direction:column;align-items:center;padding:14px 12px 16px;position:relative;text-align:center}.pw2026-badge{position:absolute;top:-12px;left:50%;transform:translateX(-50%);background:#e03030;color:#fff;font-family:'Bebas Neue',sans-serif;font-size:13px;letter-spacing:1px;padding:2px 12px;white-space:nowrap}.pw2026-card-img{width:100%;height:auto;display:block;margin:8px 0 10px}.pw2026-card-label{font-family:'Bebas Neue',sans-serif;font-size:20px;letter-spacing:.3px;color:#111;margin:0 0 4px;line-height:1.2}.pw2026-card-price{font-family:'Bebas Neue',sans-serif;font-size:16px;letter-spacing:.3px;color:#444;margin:0 0 12px;line-height:1.3}.pw2026-btn{display:block;width:100%;background:#e03030;color:#fff!important;font-family:'Bebas Neue',sans-serif;font-size:16px;letter-spacing:1px;text-align:center;padding:9px 0;text-decoration:none!important;border:none;cursor:pointer;margin-top:auto}.pw2026-btn:hover{background:#b52424;color:#fff!important;text-decoration:none!important}.pw2026-footer{background:#fff;text-align:center;padding:10px 20px 16px;border-top:1px solid #ddd}.pw2026-footer-tagline{font-family:'Bebas Neue',sans-serif;font-size:20px;letter-spacing:.3px;color:#111;margin:0 0 4px}.pw2026-footer-login{font-family:'Bebas Neue',sans-serif;font-size:16px;letter-spacing:.3px;color:#444;margin:0}.pw2026-footer-login a{color:#00723e;text-decoration:underline}@media(max-width:540px){.pw2026-offers{flex-direction:column}.pw2026-card{margin-top:16px}.pw2026-header-cta{font-size:17px}}`; 539 document.head.appendChild(pw2026Style); 540 } 541 html = ` 542 <div id="bdn-paywall"> 543 <div class="bdn-root"> 544 <div id="bdn-paywall-view" class="bdn-text-center bdn-w-full bdn-mx-auto bdn-bg-[#00723]"> 545 ${copy.body} 546 </div> 547 </div> 548 ${copy.custom_css ? `<style>${copy.custom_css}</style>` : ''} 549 </div> 550 `; 551 } 552 553 554 pTags[0].insertAdjacentHTML('afterend', html); 555 if (this.debugOutput) console.log('Paywall HTML injected after first paragraph'); 556 557 // Remove Trinity Audio player completely 558 //this.killTrinity(); 559 560 // Hide all Everlit wrappers 561 document.querySelectorAll('.wp-block-custom-everlit-iframe-embed').forEach(el => el.style.display = 'none'); 562 // Inject Viafoura conversation widget after #comments or at end of .main-content 563 // VF init 3 564 565 if (postId !== 'default' && this.viafouraToggle) { 566 const commentsDiv = document.querySelector('#comments.comments-area'); 567 const mainContentDiv = document.querySelector('.main-content'); 568 const viafouraDiv = document.createElement('div'); 569 viafouraDiv.className = 'viafoura'; 570 viafouraDiv.id = `bdn-paywall-viafoura-${postId}`; 571 viafouraDiv.innerHTML = ` 572 <vf-conversations-count></vf-conversations-count> 573 <vf-conversations vf-container-id="viafoura-comments-${postId}"></vf-conversations> 574 `; 575 if (commentsDiv) { 576 commentsDiv.insertAdjacentElement('afterend', viafouraDiv); 577 if (this.debugOutput) console.log(`Injected Viafoura conversation widget after #comments with vf-container-id="viafoura-comments-${postId}"`); 578 } else if (mainContentDiv) { 579 mainContentDiv.appendChild(viafouraDiv); 580 if (this.debugOutput) console.log(`Injected Viafoura conversation widget at end of .main-content with vf-container-id="viafoura-comments-${postId}"`); 581 } else { 582 if (this.debugOutput) console.error('Could not find #comments or .main-content for Viafoura injection in paywall'); 583 } 584 585 // Load Viafoura script if not already loaded 586 if (!document.querySelector('script[src="//cdn.viafoura.net/entry/index.js"]')) { 587 const script = document.createElement('script'); 588 script.src = '//cdn.viafoura.net/entry/index.js'; 589 script.async = true; 590 script.onload = () => { 591 if (this.debugOutput) console.log('Viafoura script loaded successfully for paywall comments'); 592 console.log('Post-load window.vfQ state:', window.vfQ); 593 console.log('Post-load window.viafoura state:', window.viafoura); 594 }; 595 script.onerror = () => { 596 if (this.debugOutput) console.error('Viafoura script failed to load for paywall comments'); 597 }; 598 document.body.appendChild(script); 599 if (this.debugOutput) console.log('Viafoura script injection initiated for paywall comments'); 600 } else { 601 if (this.debugOutput) console.log('Viafoura script already loaded, skipping script injection'); 602 } 603 } else { 604 if (this.debugOutput) console.log('Skipping Viafoura injection in paywall: invalid postId'); 605 } 606 // end VF init 3 607 } else { 608 if (this.debugOutput) console.error('No pTags found for paywall injection'); 609 } 610 611 612 if (!(this.isCrawler(this.debugOutput, this.debugTrace) || crawlerTest)) { 613 this.encode(selectorForPWMixup, 2, this.pixels); 614 } 615 616 document.addEventListener("PelcroUserLoggedIn", this, false); 617 } 618 619 620 injectSingleCommentCountWidget() { 621 let postId = null; 622 const postElement = document.querySelector('article[id^="post-"]'); 623 if (postElement && postElement.id) { 624 postId = postElement.id.replace('post-', ''); 625 } 626 if (!postId) { 627 if (this.debugOutput) console.error('Could not find WordPress post ID for Viafoura comment count widget'); 628 postId = this.metadata.articleId || 'default'; 629 } 630 631 const entryMeta = document.querySelector('.entry-subhead .entry-meta'); 632 if (entryMeta && postId !== 'default') { 633 const countDiv = document.createElement('div'); 634 countDiv.className = 'viafoura viafoura-comment-count'; 635 countDiv.style.minHeight = '0px'; 636 countDiv.innerHTML = ` 637 <a class="mr-3 flex hover:underline" href="#viafoura-comments-${postId}" title="Join the Conversation"> 638 <svg class="vf-comment-thing" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" class="mr-1 mt-[3px] h-5 w-5" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"> 639 <path d="M7 7h10v2H7zm0 4h7v2H7z"></path> 640 <path d="M20 2H4c-1.103 0-2 .897-2 2v18l5.333-4H20c1.103 0 2-.897 2-2V4c0-1.103-.897-2-2-2zm0 14H6.
640667L4 18V4h16v12z"></path> 641 </svg> 642 <vf-conversations-count vf-container-id="viafoura-comments-${postId}"></vf-conversations-count> 643 </a> 644 `; 645 entryMeta.prepend(countDiv); 646 if (this.debugOutput) console.log(`Prepended Viafoura comment count widget with vf-container-id="viafoura-comments-${postId}" to .entry-meta`); 647 } else { 648 if (this.debugOutput) console.error('Entry meta (.entry-subhead .entry-meta) not found or invalid post ID, skipping Viafoura comment count injection'); 649 } 650 } 651 652 653 injectCommentCountWidgets() { 654 const articleElements = document.querySelectorAll('article[data-post-id]'); 655 if (this.debugOutput) console.log(`Found ${articleElements.length} article elements with data-post-id for comment count injection`); 656 articleElements.forEach(article => { 657 const articlePostId = article.getAttribute('data-post-id'); 658 if (articlePostId) { 659 const countDiv = document.createElement('div'); 660 countDiv.className = 'viafoura viafoura-comment-count'; 661 countDiv.innerHTML = `<vf-conversations-count vf-container-id="viafoura-comments-${articlePostId}"></vf-conversations-count>`; 662 article.appendChild(countDiv); 663 if (this.debugOutput) console.log(`Injected Viafoura comment count widget for article with data-post-id="${articlePostId}"`); 664 } else { 665 if (this.debugOutput) console.warn('Skipping comment count injection: article missing data-post-id'); 666 } 667 }); 668 } 669 670 getPaywallCopy() { 671 const copyOptions = window.bdnPaywallData?.paywallCopy || []; 672 const currentUrl = window.location.href.toLowerCase(); 673 674 // Collect all matching active copies 675 const matches = []; 676 for (const copy of copyOptions) { 677 if (!copy.is_active) continue; 678 if (copy.url_regex) { 679 try { 680 const regex = new RegExp(copy.url_regex, 'i'); 681 if (regex.test(currentUrl)) { 682 matches.push(copy); 683 } 684 } catch (e) { 685 // Invalid regex, skip 686 continue; 687 } 688 } 689 } 690 691 if (matches.length === 0) { 692 return null; 693 } 694 695 if (matches.length === 1) { 696 return matches[0]; 697 } 698 699 // Multiple matches: weighted random selection 700 const totalWeight = matches.reduce((sum, copy) => sum + copy.weight, 0); 701 let random = Math.random() * totalWeight; 702 703 for (const copy of matches) { 704 random -= copy.weight; 705 if (random <= 0) { 706 return copy; 707 } 708 } 709 710 // Fallback to last match if rounding error 711 return matches[matches.length - 1]; 712 } 713 714 getUserArticleCount() { 715 // Placeholder: Replace with server-side tracking 716 const count = parseInt(localStorage.getItem('bdn_article_count') || '0'); 717 localStorage.setItem('bdn_article_count', count + 1); 718 return count; 719 } 720 721 removeWall() { 722 if (this.debugOutput) console.log(" - in remove paywall!"); 723 724 const selectorForPWBlur = '#bdn_paywall_content > p, #bdn_paywall_content > h1, #bdn_paywall_content > h2, #bdn_paywall_content > h3, #bdn_paywall_content > h4, #bdn_paywall_content >h5, #bdn_paywall_content > h6, #bdn_paywall_content > .wp-block-columns > .wp-block-column > p, .flourish-embed, .flourish-chart, .embed-documentcloud'; 725 const selectorForPWMixup = '#bdn_paywall_content > p'; 726 const pTags = document.querySelectorAll(selectorForPWBlur); 727 728 if (this.debugOutput) { 729 console.log(" - ptags for removal"); 730 console.log(pTags); 731 } 732 733 if (pTags.length > 0) { 734 // Remove gradient mask from first paragraph 735 pTags[0].style.webkitMaskImage = ''; 736 pTags[0].style.maskImage = ''; 737 738 // Remove blur from subsequent elements 739 for (let i = 1; i < pTags.length; i++) { 740 pTags[i].style.filter = ''; 741 } 742 743 // Remove inserted paywall HTML (assuming itâs the next sibling of pTags[0]) 744 const nextSibling = pTags[0].nextElementSibling; 745 if (nextSibling && nextSibling.matches('.pelcro-container')) { 746 nextSibling.remove(); 747 } else if (this.debugOutput) { 748 console.log(" - no paywall HTML found to remove"); 749 } 750 } 751
752 const crawlerTest = this.urlParams.has('crawler'); 753 if (!(isCrawler(this.debugOutput, this.debugTrace) || crawlerTest)) { 754 decode(selectorForPWMixup, 2); 755 } 756 757 // Remove PelcroUserLoggedIn event listener 758 document.removeEventListener("PelcroUserLoggedIn", this, false); 759 } 760 761 isCrawler() { 762 763 if(this.debugTrace) { 764 console.log("BDN Paywall Stack Trace:"); 765 console.trace(); 766 } 767 768 if(this.debugOutput) console.log(" - in isCrawler logic!"); 769 770 var botPattern = "(googlebot\\/|bot|Googlebot-Mobile|Googlebot-Image|Google favicon|Mediapartners-Google|bingbot|slurp|java|wget|curl|Commons-HttpClient|Python-urllib|libwww|httpunit|nutch|phpcrawl|msnbot|jyxobot|FAST-WebCrawler|FAST Enterprise Crawler|biglotron|teoma|convera|seekbot|gigablast|exabot|ngbot|ia_archiver|GingerCrawler|webmon |httrack|webcrawler|grub.org|UsineNouvelleCrawler|antibot|netresearchserver|speedy|fluffy|bibnum.bnf|findlink|msrbot|panscient|yacybot|AISearchBot|IOI|ips-agent|tagoobot|MJ12bot|dotbot|woriobot|yanga|buzzbot|mlbot|yandexbot|purebot|Linguee Bot|Voyager|CyberPatrol|voilabot|baiduspider|citeseerxbot|spbot|twengabot|postrank|turnitinbot|scribdbot|page2rss|sitebot|linkdex|Adidxbot|blekkobot|ezooms|dotbot|Mail.RU_Bot|discobot|heritrix|findthatfile|europarchive.org|NerdByNature.Bot|sistrix crawler|ahrefsbot|Aboundex|domaincrawler|wbsearchbot|summify|ccbot|edisterbot|seznambot|ec2linkfinder|gslfbot|aihitbot|intelium_bot|facebookexternalhit|yeti|RetrevoPageAnalyzer|lb-spider|sogou|lssbot|careerbot|wotbox|wocbot|ichiro|DuckDuckBot|lssrocketcrawler|drupact|webcompanycrawler|acoonbot|openindexspider|gnam gnam spider|web-archive-net.com.bot|backlinkcrawler|coccoc|integromedb|content crawler spider|toplistbot|seokicks-robot|it2media-domain-crawler|ip-web-crawler.com|siteexplorer.info|elisabot|proximic|changedetection|blexbot|arabot|WeSEE:Search|niki-bot|CrystalSemanticsBot|rogerbot|360Spider|psbot|InterfaxScanBot|Lipperhey SEO Service|CC Metadata Scaper|g00g1e.net|GrapeshotCrawler|urlappendbot|brainobot|fr-crawler|binlar|SimpleCrawler|Livelapbot|Twitterbot|cXensebot|smtbot|bnf.fr_bot|A6-Indexer|ADmantX|Facebot|Twitterbot|OrangeBot|memorybot|AdvBot|MegaIndex|SemanticScholarBot|ltx71|nerdybot|xovibot|BUbiNG|Qwantify|archive.org_bot|Applebot|TweetmemeBot|crawler4j|findxbot|SemrushBot|yoozBot|lipperhey|y!j-asr|Domain Re-Animator Bot|AddThis)"; 771 var re = new RegExp(botPattern, 'i'); 772 var userAgent = navigator.userAgent; 773 var rVal = re.test(userAgent); 774 775 if(this.debugOutput) { 776 console.log(" - is crawler: "); 777 console.log(rVal); 778 } 779 780 return rVal; 781 } 782 783 shuffle(array) { 784 785 for (let i = array.length - 1; i > 0; i--) { 786 787 const j = Math.floor(Math.random() * (i + 1)); 788 [array[i], array[j]] = [array[j], array[i]]; 789 } 790 791 return array; 792 } 793 794 encode(selector, startP) { 795 console.log('encode called with', selector, startP); 796 797 if (document.getElementById('coral_thread')) { 798 const hasProp = Object.hasOwn(document.getElementById('coral_thread'), 'style'); 799 if (hasProp) { 800 document.getElementById('coral_thread').style.display = 'none'; 801 } 802 } 803 804 // Verify wrapper exists 805 const wrapper = document.querySelector('#bdn_paywall_content'); 806 //console.log('Wrapper element:', wrapper ? wrapper.outerHTML : 'Not found'); 807 808 const pe = document.querySelectorAll(selector); 809 console.log('Selected <p> elements:', pe.length, Array.from(pe).map(el => el.outerHTML.slice(0, 50) + '...')); 810 811 this.pixels = new Array(pe.length); 812 813 if (pe.length === 0) { 814 console.warn('No <p> elements found for selector:', selector); 815 return; 816 } 817 818 if (startP - 1 >= pe.length) { 819 console.warn(`startP (${startP}) is too large; only ${pe.length} elements found`); 820 return; 821 } 822 823 for (let i = startP - 1; i < pe.length; i++) { 824 const element = pe[i]; 825 console.log('Processing element', i, element.outerHTML.slice(0, 50) + '...'); 826 827 // Collect text nodes and their content 828 const textNodes = []; 829 const walk = document.createTreeWalker(element, Node.TEXT_NODE, null, false); 830 let node; 831 while ((node = walk.nextNode())) { 832 const text = node.textContent.trim(); 833 if (text) { 834 textNodes.push({ node, text: text.split(/\s+/).filter(word => word) }); 835 } 836 } 837 console.log('Text nodes:', textNodes.map(tn => tn.text)); 838 839 // Flatten all words and shuffle them 840 const allWords = textNodes.flatMap(tn => tn.text); 841 console.log('Words to shuffle:', allWords); 842 843 if (allWords.length === 0) { 844 console.log('No words to shuffle for element', i); 845 this.pixels[i] = []; 846 continue; 847 } 848 849 const encodeVal = new Array(allWords.length); 850 for (let z = 0; z < encodeVal.length; z++) { 851 encodeVal[z] = z; 852 } 853 console.log('Shuffle indices before:', encodeVal.slice()); 854 this.shuffle(encodeVal); 855 console.log('Shuffle indices after:', encodeVal.slice()); 856 console.log('Is shuffled:', encodeVal.some((val, idx) => val !== idx)); 857 858 // Distribute shuffled words back to text nodes 859 let wordIndex = 0; 860 for (const { node, text } of textNodes) { 861 const newText = []; 862 for (let j = 0; j < text.length && wordIndex < allWords.length; j++) { 863 newText.push(allWords[encodeVal[wordIndex++]]); 864 } 865 node.textContent = newText.join(' '); 866 console.log('Updated text node:', node.textContent); 867 } 868 869 this.pixels[i] = encodeVal; 870 element.classList.add('bdn_paywall_prevent-select'); 871 } 872 } 873 874 decode(selector, startP) { 875 console.log('decode called with', selector, startP); 876 877 if (document.getElementById('coral_thread')) { 878 document.getElementById('coral_thread').style.display = 'block'; 879 } 880 881 const pe = document.querySelectorAll(selector); 882 console.log('Decode selected <p> elements:', pe.length); 883 884 for (let i = startP - 1; i < pe.length; i++) { 885 const element = pe[i]; 886 console.log('Decoding element', i, element.outerHTML.slice(0, 50) + '...'); 887 const encodeVal = this.pixels[i] || []; 888 console.log('Pixels:', encodeVal); 889 890 if (!encodeVal.length) { 891 console.log('No pixels for element', i); 892 continue; 893 } 894 895 // Collect text nodes and their content 896 const textNodes = []; 897 const walk = document.createTreeWalker(element, Node.TEXT_NODE, null, false); 898 let node; 899 while ((node = walk.nextNode())) { 900 const text = node.textContent.trim(); 901 if (text) { 902 textNodes.push({ node, text: text.split(/\s+/).filter(word => word) }); 903 } 904 } 905 console.log('Decode text nodes:', textNodes.map(tn => tn.text)); 906 907 // Flatten all words 908 const allWords = textNodes.flatMap(tn => tn.text); 909 console.log('Decode words:', allWords); 910 911 if (allWords.length === 0) { 912 console.log('No words to decode for element', i); 913 continue; 914 } 915 916 // Reconstruct original order 917 const origWords = new Array(allWords.length); 918 for (let y = 0; y < encodeVal.length; y++) { 919 if (encodeVal[y] < allWords.length) { 920 origWords[encodeVal[y]] = allWords[y]; 921 } 922 } 923 console.log('Restored words:', origWords); 924 925 // Distribute words back 926 let wordIndex = 0; 927 for (const { node, text } of textNodes) { 928 const newText = []; 929 for (let j = 0; j < text.length && wordIndex < origWords.length; j++) { 930 newText.push(origWords[wordIndex++] || ''); 931 } 932 node.textContent = newText.join(' '); 933 console.log('Decoded text node:', node.textContent); 934 } 935 936 element.classList.remove('bdn_paywall_prevent-select'); 937 } 938 } 939 940 shuffle(array) { 941 for (let i = array.length - 1; i > 0; i--) { 942 const j = Math.floor(Math.random() * (i + 1)); 943 [array[i], array[j]] = [array[j], array[i]]; 944 } 945 return array; 946 } 947 948 nonLiteExperience() { 949 950 if(this.debugOutput) console.log("Non add lite experience called!"); 951 952 /*opscobid.cmd.push(() => { 953 opscobid.newPage().then(() => { 954 opscobid.reloadSlot('bottom-anchor') 955 }); 956 });*/ 957 958 //Next Millenium inject 959 /*const nextMilleniumscript = document.createElement('script'); 960 nextMilleniumscript.src = 'https://powerad.ai/268591500480275/script.
960js'; 961 nextMilleniumscript.async = true; 962 nextMilleniumscript.type = 'text/javascript'; 963 document.head.appendChild(nextMilleniumscript);*/ 964 965 if (!document.getElementById('rev2exit')) { 966 const rev2exitScript = document.createElement('script'); 967 rev2exitScript.id = 'rev2exit'; 968 rev2exitScript.src = 'https://labs-cdn.revcontent.com/build/revexit.min.js?w=291897&p=74369&k=1fe26b5e39e0160e2384b62c7f5a97bd1ef37429&d=bangordailynews.com&x=both'; 969 rev2exitScript.type = 'text/javascript'; 970 document.body.appendChild(rev2exitScript); 971 } 972 973 if(typeof loadAd === 'function') { 974 975 if(this.debugOutput) console.log("Pushing Yield Lift Ad To Bottom Anchor!"); 976 977 loadAd("adhesionAdSlot", "/21682974628/bangordailynews/bottom-anchor", 1, 1); 978 979 } else { 980 if(this.debugOutput) console.log("loadad function is not defined."); 981 } 982 } 983 984 async logout() { 985 986 document.cookie = "bdn_subscriber=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Strict; Secure"; 987 if (this.debugOutput) console.log("Entering logout method"); 988 989 // Get the loggedInCookie_sid cookie 990 const getCookie = (name) => { 991 const value = `; ${document.cookie}`; 992 const parts = value.split(`; ${name}=`); 993 if (parts.length === 2) return parts.pop().split(';').shift(); 994 return null; 995 }; 996 const token = getCookie('loggedInCookie_sid'); 997 998 if (!token) { 999 if (this.debugOutput) console.log("No loggedInCookie_sid cookie found"); 1000 // Clear cookie anyway to ensure clean state 1001 document.cookie = "loggedInCookie_sid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; 1002 // Refresh page to reset paywall state 1003 window.location.reload(); 1004 return; 1005 } 1006 1007 try { 1008 // Call /logout endpoint 1009 const response = await fetch('https://apitest.bangordailynews.com/logout', { 1010 method: 'POST', 1011 headers: { 1012 'Content-Type': 'application/json' 1013 }, 1014 body: JSON.stringify({ 1015 key: '2e2d687e-90cb-4b11-8870-4a9f51e83226', 1016 token: token 1017 }) 1018 }); 1019 1020 if (this.debugOutput) console.log("Logout API response status:", response.status); 1021 1022 if (response.ok) { 1023 // Delete the cookie 1024 document.cookie = "loggedInCookie_sid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; 1025 if (this.debugOutput) console.log("loggedInCookie_sid cookie deleted"); 1026 // Reset subscriberFlag 1027 this.metadata.subscriberFlag = false; 1028 this.metadata.userObj = {}; 1029 // Refresh page to reset paywall state 1030 window.location.reload(); 1031 } else { 1032 if (this.debugOutput) console.log("Logout API failed:", response.status, response.statusText); 1033 // Clear cookie anyway to ensure client-side logout 1034 document.cookie = "loggedInCookie_sid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; 1035 window.location.reload(); 1036 } 1037 } catch (error) { 1038 if (this.debugOutput) console.log("Logout API error:", error.message); 1039 // Clear cookie on error to ensure client-side logout 1040 document.cookie = "loggedInCookie_sid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; 1041 window.location.reload(); 1042 } 1043 } 1044 1045 async decision(mode) { 1046 1047 if(this.debugTrace) { 1048 console.log("BDN Paywall Stack Trace:"); 1049 console.trace(); 1050 } 1051 1052 if(this.debugOutput) console.log("Entering Decision!"); 1053 1054 1055 1056 if(this.metadata.paywallRunCheck == "0" && (this.metadata.goGoGadgetPaywall === "1")) { 1057 1058 if (this.metadata.subscriberFlag) { 1059 console.log("Skipping paywall: serverUser indicates active subscriber"); 1060 demeter("pageview", { args: { article: this.isArticle || false, section: this.metadata.section || null } }); 1061 this.injectSTNPlayer(true); 1062 return; // Exit early for subscribers 1063 } 1064 1065 if(this.debugOutput) console.log(" - decision mode: " + mode); 1066 1067 this.metadata.paywallRunCheck = "1"; 1068 1069 if(this.debugOutput) { 1070 console.log("Metadata:"); 1071 console.log(this.metadata); 1072 } 1073 1074 const isArticle = this.isArticle; 1075 if(this.debugOutput) console.log(" - isArticle: " + isArticle); 1076 1077 const siteFromUrl = this.siteFromUrl; 1078 if(this.debugOutput) console.log(" - siteFromUrl: " + siteFromUrl); 1079 1080 const isSubscriber = await this.isSubscriber(); 1081 if(this.debugOutput) console.log(" - isSubscriber: " + isSubscriber); 1082 1083 const userType = this.userType; 1084 window.userState.userType = userType; 1085 window.userState.isSubscriber = isSubscriber; 1086 1087 if(!isSubscriber) this.nonLiteExperience(); 1088 this.injectSTNPlayer(isSubscriber); 1089 1090 if(!isSubscriber) { 1091 1092 console.log("GAME COPY STUFF",window.bdnPaywallData.gamePaths,window.bdnPaywallData.gameHeader,window.bdnPaywallData.gameCopy); 1093 const currentPath = window.location.pathname.toLowerCase(); 1094 const gamePaths = window.bdnPaywallData.gamePaths; 1095 this.isGamePage = gamePaths.some(path => currentPath.startsWith(path)); 1096 1097 if (this.isGamePage) { 1098 if (this.debugOutput) console.log("Detected game page, triggering paywall."); 1099 this.displayGameWall(); 1100 return; 1101 } 1102 } 1103 //end game wall 1104 1105 1106 1107 if(isArticle === true) { 1108 1109 let argVal = { args: { article: isArticle, section: this.metadata.section } }; 1110 1111 if(this.debugOutput) { 1112 console.log(" - in, isArticle logic!"); 1113 console.log("Submitting Demeter (pageview) view data:"); 1114 console.log(argVal); 1115 } 1116 1117 demeter("pageview", argVal); 1118 1119 if(this.debugOutput) console.log(" - called demeter pageview!"); 1120 1121 if(!isSubscriber) { 1122 1123 //this.nonLiteExperience(); 1124 1125 if(this.debugOutput) console.log(" - in, not(isSubscriber) logic!"); 1126 1127 if(this.debugOutput) { 1128 console.log(" - userType: " + userType); 1129 console.log(window.Pelcro); 1130 } 1131 1132 let isFreeContent = this.isFreeContent; 1133 if(this.debugOutput) console.log(" - isFreeContent: " + isFreeContent); 1134 1135 let isAlwaysWalledContent = this.isAlwaysWalledContent; 1136 if(this.debugOutput) console.log(" - isAlwaysWalledContent: " + isAlwaysWalledContent); 1137 1138 if(!isFreeContent) { 1139 1140 if(this.debugOutput) console.log(" - in, !isFreContent logic!"); 1141 1142 if(isAlwaysWalledContent) { 1143 1144 argVal = { args: "paywall" }; 1145 1146 if(this.debugOutput) { 1147 console.log(" - in, isAlwaysWalledContent logic!"); 1148 console.log("Submitting Demeter (pageview) view data:"); 1149 } 1150 1151 this.displayWall(); 1152 demeter("wall", argVal ); 1153 } else { 1154 1155 if(this.debugOutput) console.log(" - in, !isAlwaysWalledContent logic!"); 1156 if(this.debugOutput) console.log(" - calling demeter - getDecision"); 1157 1158 demeter("getDecision", { 1159 1160 args: { visitor: userType }, 1161 onSuccess: (decision) => { 1162 1163 let sophiDecision = (decision.outcome.wallVisibility === "always"); 1164 1165 if(this.debugTrace) { 1166 1167 console.log("BDN Paywall Stack Trace:") 1168 console.trace(); 1169 } 1170 1171 if(this.debugOutput){ 1172 1173 console.log(" - decision returned!"); 1174 console.log(sophiDecision); 1175 console.log(decision.outcome); 1176 } 1177
1178 if(this.urlParams.get('sophisaysyes') != null) { 1179 if(this.debugOutput) console.log(" - overriding sophi decision to yes because of query string!"); 1180 sophiDecision = true; 1181 } 1182 1183 if(sophiDecision) { 1184 1185 //add final subscriber check to address race condition edge case 1186 // if not isSubscriber proceed 1187 //if (!this.metadata.subscriberFlag) { 1188 1189 if(this.debugOutput) console.log(" - calling paywall!"); 1190 this.displayWall(); 1191 1192 if(this.debugOutput) console.log(" - demeter - wall!"); 1193 demeter("wall", { args: "paywall" } ); 1194 1195 if(this.debugOutput) console.log(" - listener - wall!"); 1196 (window._matherq = window._matherq || []).push(['paywallEvent', { 1197 type: 'paywall', 1198 category: 'block', 1199 action: 'stop', 1200 }]); 1201 1202 //} else { 1203 // if (this.debugOutput) console.log(" - Sophi Ignored; user is subscribed. Race condition"); 1204 //} 1205 1206 } 1207 1208 //do nothing if sophi return false; 1209 }, 1210 1211 onError: (err) => { 1212 if(this.debugOutput){ 1213 console.log("BDN Paywall Callstack:") 1214 console.trace(); 1215 console.log(" - sophi error:"); 1216 console.log(err); 1217 } 1218 } 1219 }); 1220 } 1221 } else { 1222 1223 if(this.debugOutput) console.log("Skipping because content is free!"); 1224 } 1225 } else { 1226 1227 if(this.debugOutput) console.log("Skipping because user is signined in with valid subscription!"); 1228 } 1229 } else { 1230 1231 const argVal = { args: { article: isArticle, section: this.metadata.section } }; 1232 1233 if(this.debugOutput) { 1234 console.log("Skipping because is not article!"); 1235 console.log("Submitting Demeter (pageview) view data:"); 1236 console.log(argVal); 1237 } 1238 1239 demeter("pageview", argVal); 1240 } 1241 } 1242 else { 1243 1244 if(this.debugOutput) console.log("Decision skipped, process has already run or paywall set not run!") 1245 } 1246 } 1247 1248 // Helper function to wait for user.subscriber 1249 async waitForProperty(obj, prop) { 1250 const maxWait = 20000; // 1 second timeout, since the delay is brief 1251 const interval = 50; // Check every 50ms 1252 let elapsed = 0; 1253 1254 while (!Array.isArray(obj[prop]) && elapsed < maxWait) { 1255 await new Promise(resolve => setTimeout(resolve, interval)); 1256 elapsed += interval; 1257 } 1258 1259 if (!Array.isArray(obj[prop])) { 1260 console.warn(`Property ${prop} did not load within ${maxWait}ms`); 1261 } 1262 } 1263 1264 get isFreeContent() { 1265 if (this.debugTrace) { 1266 console.log("BDN Paywall Stack Trace:"); 1267 console.trace(); 1268 } 1269 1270 if (this.debugOutput) console.log("Processing isFreeContent Logic!"); 1271 1272 // Check if URL contains /bdn-maine/ or metadata.isFreeContent is true 1273 const isFree = Boolean(parseInt(this.metadata.isFreeContent)) || /\/bdn-maine\//.test(window.location.pathname) || /\/special-sections\//.test(window.location.pathname); 1274 1275 if (this.debugOutput) { 1276 console.log(" - isFree:", isFree); 1277 console.log(" - URL check:", /\/bdn-maine\//.test(window.location.pathname)); 1278 } 1279 1280 return isFree; 1281 } 1282 1283 get isAlwaysWalledContent() { 1284 1285 if(this.debugTrace) { 1286 console.log("BDN Paywall Stack Trace:"); 1287 console.trace(); 1288 } 1289 1290 if(this.debugOutput) console.log("Processing isAlwaysWalledContent Logic!"); 1291 1292 const isWalled = Boolean(parseInt(this.metadata.isAlwaysWalledContent)); 1293 1294 if(this.debugOutput) console.log(isWalled); 1295 return isWalled; 1296 } 1297 1298 get userType() { 1299 1300 if(this.debugTrace) { 1301 console.log("BDN Paywall Stack Trace:"); 1302 console.trace(); 1303 } 1304 1305 if(this.debugOutput) console.log("Processing userType Logic!"); 1306 1307 let isUser = this.isUser; 1308 let rVal = ""; 1309 1310 if(isUser) 1311 rVal = "registered"; 1312 else rVal = "anonymous"; 1313 1314 if(this.debugOutput) console.log(` - ${isUser} :: ${rVal}`); 1315 return rVal; 1316 } 1317 1318 async isIPAuthed() { 1319 1320 const ipAuthPromise = new Promise((resolve, reject) => { 1321 1322 window.Pelcro.membership.getByIP((error, response) => { 1323 1324 if(error != null) 1325 resolve([]); 1326 else { 1327 resolve(response); 1328 } 1329 }); 1330 }); 1331 1332 let ipAuthArray = await ipAuthPromise; 1333 window.ipAuthArray = ipAuthArray; 1334 1335 if(this.debugOutput) console.log(" - ip auth array recieved!"); 1336 1337 //console.log(ipAuthArray); 1338 1339 const rVal = ipAuthArray.some(s => s.status === "active"); 1340 1341 //console.log(rVal); 1342 return rVal; 1343 } 1344 1345 async isSubscriber() { 1346 if (this.debugTrace) { 1347 console.log("BDN Paywall Stack Trace:"); 1348 console.trace(); 1349 } 1350 1351 if (this.debugOutput) { 1352 console.log("Processing isSubscriber Logic!"); 1353 console.log(" - reading metadata - user object:", this.metadata.userObj); 1354 } 1355 1356 let rVal = false;
1357 let hasAdlite = false; 1358 const user = this.metadata.userObj; 1359 const currentSite = this.siteFromUrl.toLowerCase(); 1360 1361 if (this.debugOutput) console.log(" - current site from siteFromUrl:", currentSite); 1362 1363 // Check serverUser or Pelcro user data 1364 if (user && Object.keys(user).length > 0) { 1365 if (user === this.metadata.serverUser && user.isServerSubscriber) { 1366 if (this.debugOutput) console.log(" - serverUser with isServerSubscriber true, performing entitlement check"); 1367 if (Array.isArray(user.subscriptions)) { 1368 rVal = user.subscriptions.some(subscription => { 1369 if (subscription.status !== "active") return false; 1370 const entitlements = (subscription.plan?.product?.entitlements || []).map(e => e.toLowerCase()); 1371 if (this.debugOutput) console.log(" - serverUser subscription entitlements:", entitlements); 1372 if (entitlements.includes('adlite')) { 1373 hasAdlite = true; 1374 if (this.debugOutput) console.log(" - adlite entitlement found in serverUser subscriptions"); 1375 } 1376 return entitlements.includes(currentSite); 1377 }); 1378 if (this.debugOutput) console.log(" - serverUser subscriptions entitlement check result:", rVal); 1379 } else { 1380 if (this.debugOutput) console.log(" - serverUser has no subscriptions, trusting isServerSubscriber"); 1381 rVal = true; 1382 } 1383 } else if (Array.isArray(user.subscriptions)) { 1384 if (this.debugOutput) console.log(" - user object contains subscriptions!");
1385 rVal = user.subscriptions.some(subscription => { 1386 if (subscription.status !== "active") return false; 1387 const entitlements = (subscription.plan?.product?.entitlements || []).map(e => e.toLowerCase()); 1388 if (this.debugOutput) console.log(" - subscription entitlements:", entitlements); 1389 if (entitlements.includes('adlite')) { 1390 hasAdlite = true; 1391 if (this.debugOutput) console.log(" - adlite entitlement found in subscriptions"); 1392 } 1393 return entitlements.includes(currentSite); 1394 }); 1395 if (this.debugOutput) console.log(" - evaluated subscriptions:", rVal); 1396 } else { 1397 if (this.debugOutput) console.log(" - no subscriptions in user object"); 1398 } 1399 1400 if (!rVal && Array.isArray(user.memberships)) { 1401 if (this.debugOutput) console.log(" - user object contains memberships!"); 1402 rVal = user.memberships.some(membership => { 1403 if (membership.subscription?.status !== "active") return false; 1404 const entitlements = (membership.subscription?.plan?.product?.entitlements || []).map(e => e.toLowerCase()); 1405 if (this.debugOutput) console.log(" - membership entitlements:", entitlements); 1406 if (entitlements.includes('adlite')) { 1407 hasAdlite = true; 1408 if (this.debugOutput) console.log(" - adlite entitlement found in memberships"); 1409 } 1410 return entitlements.includes(currentSite); 1411 }); 1412 if (this.debugOutput) console.log(" - evaluated memberships:", rVal); 1413 } else { 1414 if (this.debugOutput) console.log(" - no memberships in user object"); 1415 } 1416 } else { 1417 if (this.debugOutput) console.log(" - no user data, checking IP auth"); 1418 } 1419 1420 // Fallback to IP auth if no subscriber status 1421 if (!rVal) { 1422 const ipAuthed = await this.isIPAuthed(); 1423 if (ipAuthed) { 1424 if (this.debugOutput) console.log(" - evaluated subscription via IP auth!"); 1425 rVal = true; 1426 // Note: IP auth typically doesn't provide entitlement details, so we assume no adlite for IP-authenticated users 1427 } else { 1428 if (this.debugOutput) console.log(" - not IP authenticated"); 1429 } 1430 } 1431 1432 if (hasAdlite) { 1433 this.injectAdliteMetaTags(); 1434 } 1435 1436 if (rVal) { 1437 this.metadata.subscriberFlag = true; 1438 } 1439 if (this.debugOutput) console.log("FINAL SUBSCRIBER STATUS:", rVal, "Has adlite:", hasAdlite); 1440 return rVal; 1441 } 1442 1443 1444 // Assuming waitForProperty remains unchanged 1445 async waitForProperty(obj, prop) { 1446 const maxWait = 1000; // 1 second timeout 1447 const interval = 50; // Check every 50ms 1448 let elapsed = 0; 1449 1450 while (!Array.isArray(obj[prop]) && elapsed < maxWait) { 1451 await new Promise(resolve => setTimeout(resolve, interval)); 1452 elapsed += interval; 1453 } 1454 1455 if (!Array.isArray(obj[prop])) { 1456 console.warn(`Property ${prop} did not load within ${maxWait}ms`); 1457 } 1458 } 1459 1460 async checkAdliteEntitlement() { 1461 const user = this.metadata.userObj; 1462 let hasAdlite = false; 1463 if (Array.isArray(user.subscriptions)) { 1464 hasAdlite = user.subscriptions.some(sub => sub.status === "active" && (sub.plan?.product?.entitlements || []).includes('adlite')); 1465 } else if (Array.isArray(user.memberships)) { 1466 hasAdlite = user.memberships.some(m => m.subscription?.status === "active" && (m.subscription.plan?.product?.entitlements || []).includes('adlite')); 1467 } 1468 if (hasAdlite) this.injectAdliteMetaTags(); 1469 } 1470 1471 get isUser() { 1472 1473 if(this.debugTrace) { 1474 console.log("BDN Paywall Stack Trace:"); 1475 console.trace(); 1476 } 1477 1478 if(this.debugOutput) console.log("Entering isUser!"); 1479 1480 let rVal = false;
1481 1482 if(this.metadata.userObj != undefined) 1483 if (this.metadata.userObj.auth_token !== undefined) 1484 rVal = true; 1485 return rVal; 1486 } 1487 1488 /* ============================================================ 1489 * STN VIDEO PLAYER (Voltax) â minimal live-test inject. 1490 * Replaces the old Ad Inserter script (embed.sendtonews.com/player3), 1491 * which manually forced s2nApi.float and is the suspected cause of 1492 * the Google Ad Manager "sticky video" policy restriction. 1493 * Deliberately does NOT touch float/sticky state â STN says the new 1494 * player self-manages the inline->sticky transition + dismiss control, 1495 * so we just mount it and get out of the way. 1496 * Shown to subscribers and non-subscribers alike (per team, 2026-09): 1497 * non-subscribers get the Floating variant, subscribers get the 1498 * Non-Floating variant. News only, no sports handling yet â smallest 1499 * surface to validate the fix before expanding scope. 1500 * Disable: stnPlayerToggle = false above, or comment out the 1501 * this.injectSTNPlayer(...) calls in decision(). Quick per-request 1502 * override: append ?stnoff to the URL. 1503 * ============================================================ */ 1504 injectSTNPlayer(isSubscriber) { 1505 1506 if (!this.stnPlayerToggle) return; 1507 if (this.stnPlayerFired) return; 1508 if (this.urlParams.has('stnoff')) { 1509 if (this.debugOutput) console.log("[STN] suppressed via ?stnoff"); 1510 return; 1511 } 1512 1513 const anchor = document.querySelector('#bdn_paywall_content'); 1514 if (!anchor) { 1515 if (this.debugOutput) console.log("[STN] anchor #bdn_paywall_content not found, skipping"); 1516 return; 1517 } 1518 1519 this.stnPlayerFired = true; 1520 1521 // mm-subId: 4 = News Floating (non-subscribers), 5 = News Non-Floating (subscribers) 1522 const subId = isSubscriber ? 5 : 4; 1523 1524 const playerDiv = document.createElement('div'); 1525 playerDiv.className = 's2nPlayer'; 1526 anchor.insertAdjacentElement('afterend', playerDiv); 1527 1528 const script = document.createElement('script'); 1529 script.src = "https://players.voltaxservices.io/players/bangor-daily-news/bangor_daily_news/code?mm-subId=" + subId; 1530 document.body.appendChild(script); 1531 1532 if (this.debugOutput) console.log("[STN] player injected", { subId: subId, isSubscriber: isSubscriber }); 1533 } 1534 1535 get isArticle() { 1536 1537 if(this.debugTrace) { 1538 console.log("BDN Paywall Stack Trace:"); 1539 console.trace(); 1540 } 1541 1542 if(this.debugOutput) console.log("Entering isArticle!"); 1543 1544 return (this.metadata.pageType == "post"); 1545 } 1546 1547 get siteFromUrl() { 1548 const fullUrl = window.location.href.toLowerCase(); 1549 let site = "unknown"; 1550 1551 if (this.debugOutput) console.log("Processing siteFromUrl Logic!", fullUrl); 1552 1553 // Define site mappings based on URL patterns 1554 if (fullUrl.includes("bangordailynews")) { 1555 site = "ebdn"; 1556 } else if (fullUrl.includes("thecounty")) { 1557 site = "ecou"; 1558 } else if (fullUrl.includes("observer-me")) { 1559 site = "etpo"; 1560 } // Add more mappings as needed 1561 1562 if (this.debugOutput) console.log(" - Detected site: " + site); 1563 1564 if (this.debugTrace) { 1565 console.log("BDN Paywall Stack Trace:"); 1566 console.trace(); 1567 } 1568 1569 return site; 1570 } 1571 1572 displayGameWall() { 1573 if (this.debugOutput) console.log("Displaying game paywall..."); 1574 1575 // Check if paywall already exists to prevent duplicates 1576 if (document.querySelector('.bdn-game-paywall')) { 1577 if (this.debugOutput) console.log("Game paywall already present, skipping render."); 1578 return; 1579 } 1580 1581 // Find article and entry content 1582 const article = document.querySelector('article[id^="post-"]'); 1583 const entryContent = document.querySelector('.entry-content'); 1584 1585 // Hide the game wrapper (entry-content) 1586 if (entryContent) { 1587 entryContent.style.display = 'none'; 1588 if (this.debugOutput) console.log('Entry content (game wrapper) hidden'); 1589 } 1590 1591 // Basic paywall div 1592 const paywallDiv = document.createElement('div'); 1593 paywallDiv.className = 'bdn-game-paywall'; 1594 paywallDiv.style.cssText = `
1595 margin: 20px 0; 1596 padding: 15px; 1597 border: 1px solid #ccc; 1598 text-align: center; 1599 `; 1600 1601 const gameHeader = window.bdnPaywallData?.gameHeader || 'Subscribe to Play'; 1602 const gameCopy = window.bdnPaywallData?.gameCopy || 'This puzzle is available only to subscribers. Please log in or subscribe to continue.'; 1603 paywallDiv.innerHTML = ` 1604 <h2>${gameHeader}</h2> 1605 <p>${gameCopy}</p> 1606 <button id="bdn-subscribe-btn" class="undefined !bdn-btn bdn-rounded-sm bdn-bg-white bdn-text-black bdn-uppercase hover:bdn-bg-black hover:bdn-text-white hover:bdn-border-white hover:bdn-border bdn-mb-6 bdn-w-1/4">Subscribe Now</button> 1607 `; 1608 1609 // Inject at the top of the article 1610 if (article) { 1611 article.insertBefore(paywallDiv, article.firstChild); 1612 if (this.debugOutput) console.log('Game paywall div injected at the top of article'); 1613 } else { 1614 document.body.appendChild(paywallDiv); 1615 if (this.debugOutput) console.warn('No article found, game paywall div appended to body'); 1616 } 1617 1618 // Optional: Add click handler for subscribe button 1619 document.getElementById('bdn-subscribe-btn')?.addEventListener('click', () => { 1620 if (this.debugOutput) console.log("Subscribe button clicked."); 1621 window.location.href = "https://www.bangordailynews.com/subscribe/"; 1622 }); 1623 } 1624 1625 loadGistAIWidget() { 1626 const paywallContent = document.getElementById('bdn_paywall_content'); 1627 if (!paywallContent) { 1628 console.warn('Element with id="bdn_paywall_content" not found. Cannot load Gist AI widget.'); 1629 return; 1630 } 1631 1632 // Check if widget already exists to prevent duplicates 1633 if (document.querySelector('gist-chat-widget')) { 1634 console.log('Gist AI widget already loaded. Skipping.'); 1635 return; 1636 } 1637 1638 // Load the Gist AI script in the head (type="module" is required) 1639 const existingScript = document.querySelector('script[src="https://cdn.gist.ai/chatWidget.js"]'); 1640 if (!existingScript) { 1641 const script = document.createElement('script'); 1642 script.type = 'module'; 1643 script.src = 'https://cdn.gist.ai/chatWidget.js'; // Production URL; for sandbox, use 'https://cdn.sandbox.gist.ai/chatWidget.js' 1644 script.async = true; 1645 script.onload = () => { 1646 console.log('Gist AI widget script loaded successfully.'); 1647 }; 1648 script.onerror = () => { 1649 console.error('Failed to load Gist AI widget script.'); 1650 }; 1651 document.head.appendChild(script); 1652 console.log('Gist AI script injected into <head>.'); 1653 } else { 1654 console.log('Gist AI script already loaded, skipping script injection.'); 1655 } 1656 1657 // Create the widget container div for inline styling 1658 const widgetDiv = document.createElement('div'); 1659 widgetDiv.id = 'gist-ai-widget'; 1660 widgetDiv.style.cssText = ` 1661 border-radius: 8px; 1662 text-align: center; 1663 max-width: 100%; 1664 box-sizing: border-box; 1665 `; // Inline-friendly styling: centered, bordered container 1666 1667 // Create the custom element for the widget (omit 'floating' for inline behavior) 1668 const widgetElement = document.createElement('gist-chat-widget'); 1669 widgetElement.setAttribute('api-key', 'pk_b5f65225378ae6f24e9908c7d9f1e683f1950f5d03e35e99acd5d0427ae354c4'); // Replace with your actual API key 1670 widgetElement.setAttribute('user-id', 'bangordailynews.com'); // Replace with your domain or organization ID, e.g., 'bangordailynews.com' 1671 // Removed 'floating' attribute to enable inline placement 1672 // Optionally add 'sticky-prompt' if you want the prompt sticky on results page 1673 widgetElement.setAttribute('sticky-prompt', ''); 1674 widgetElement.setAttribute('default-placeholder', 'Search with AIâ¦'); // Optional: custom placeholder 1675 1676 // Style the widget element itself for better inline fit (adjust as needed) 1677 widgetElement.style.cssText = `
1678 display: block; 1679 margin: 0 auto; 1680 max-width: 600px; /* Limit width for article readability */ 1681 `; 1682 1683 // Append the widget element to the container div 1684 widgetDiv.appendChild(widgetElement); 1685 1686 // Append the container div after the paywall content 1687 paywallContent.insertAdjacentElement('beforebegin', widgetDiv); 1688 console.log('Gist AI widget appended inline after #bdn_paywall_content (floating disabled).'); 1689 } 1690} 1691 1692 1693 1694window.xxx = new BDN_Paywall(bdn_paywall_metadata); 1695window.userState = { 1696 userType: null, 1697 isSubscriber: null 1698};
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.