1(function ($) { 2 'use strict'; 3 4 const RAW = window.GWD_CONFIG_BANNER || {}; 5 const CONFIG = RAW?.data?.data || {}; 6 const OPTIONS = window.GWD_CONFIG_OPTIONS || {}; 7 8 if (OPTIONS.theme_sport === false) return; 9 10 const SELECTORS_PRESET = [ 11 '#iframe_6in1 .match-list-box-repeated-sm', 12 '.match-list-box-repeated-sm', 13 '.result_table-row', 14 '.table-wrapper tr', 15 '.match_table-row', 16 '.bds-match-table-match', 17 '.w7m_score-table--row', 18 '.w7m_table-tr.is_match', 19 '.bongdawap_match-item', 20 '.table-row-mb', 21 '#iframe_6in1 .bet-match-item', 22 '#iframe_6in1 .bet-match-item-repeated', 23 '.new-home-tr', 24 '#table_live .tds', 25 '.w7m_odd-table--row', 26 '.table-match-box', 27 '.match-row', 28 '.gwd-livescore-mobile .item.tob', 29 // '.match-item', 30 '#teammain-6in1 .dataItem', 31 '#odds .dataItem', 32 '.page-schedule tr', 33 '#div_Table1 tr', 34 '#tableId tr', 35 '.item.tob', 36 '.elite_row', 37 '.reactRow', 38 '#played-matches-table-body tr', 39 '.group-table-wrapper', 40 '.livescore-tbl-groups>div', 41 '.utable tr', 42 '.world-ranking tr', 43 '.tdlink tr', 44 '#schedule-ap .as-competition-wrapper', 45 '.ball-analysis>div:nth-child(2)>div', 46 '.ball-analysis>div:nth-child(3)>div', 47 '.match-analysis-page-mobile>div', 48 '.bongdawap_table-ranking .bongdawap_row', 49 '.table_database_cup_schedules .row.header' 50 ]; 51 52 const WRAPPER_SELECTORS = [ 53 '#iframe_6in1', 54 '.w7m_table', 55 '.w7m_score-table', 56 '.bds-match-table', 57 '.match_table-tbody', 58 '.common-table', 59 '.result_table-tbody', 60 '.match-table-mb', 61 '.bet-match-list', 62 '#table_live', 63 '.match-list-sm', 64 '.w7m_odds', 65 '.wp-sport-app', 66 '#result_iframe', 67 '.w7m_schedule', 68 '#teammain-6in1', 69 '#odds', 70 '.page-schedule', 71 '#div_Table1', 72 '#tableId', 73 '.gwd-livescore-mobile', 74 '.elite_table-ranking', 75 '#resultcontainer', 76 '#played-matches-table-body', 77 '.groups-grid', 78 '.livescore-tbl-groups', 79 '.utable', 80 '.world-ranking', 81 '.tdlink', 82 '#schedule-app', 83 '.ball-analysis', 84 '.match-analysis-page-mobile', 85 '.bongdawap_table-ranking', 86 '.table_database_cup_schedules' 87 ]; 88 89 const MOBILE_REPEAT = 3; 90 91 let observer = null; 92 let discoveryObserver = null; 93 let discoveryTimer = null; 94 let debounce = null; 95 let resizeDebounce = null; 96 let lastIsMobile = window.innerWidth < 768; 97 let lastRenderSignature = ''; 98 let lastItemCount = 0; 99 let isRendering = false; 100 let isRebuilding = false; 101 let activeItemSelector = ''; 102 let activeBanners = []; 103 let nodeUid = 0; 104 const nodeIds = new WeakMap(); 105 106 const isMobile = () => window.innerWidth < 768; 107 108 function getIframeDocument(iframe) { 109 if (!iframe || iframe.tagName !== 'IFRAME') { 110 return null; 111 } 112 113 try { 114 return iframe.contentDocument || iframe.contentWindow?.document || null; 115 } catch (e) { 116 return null; 117 } 118 } 119 120 function getSameOriginIframeDocuments() { 121 return Array.from(document.querySelectorAll('iframe')) 122 .map(getIframeDocument) 123 .filter(Boolean); 124 } 125 126 function getFirstSelectorId(selector) { 127 const match = String(selector || '').match(/^#([A-Za-z][\w-]*)\s+(.+)$/); 128 129 if (!match) { 130 return null; 131 } 132 133 return { 134 id: match[1], 135 childSelector: match[2] 136 }; 137 } 138 139 function getSelectorQuery(selector) { 140 const scopedSelector = getFirstSelectorId(selector); 141 142 if (!scopedSelector) { 143 return { 144 selector, 145 context: document 146 }; 147 } 148 149 const iframe = document.getElementById(scopedSelector.id); 150 const iframeDocument = getIframeDocument(iframe); 151 152 if (!iframeDocument) { 153 return { 154 selector, 155 context: document 156 }; 157 } 158 159 return { 160 selector: scopedSelector.childSelector, 161 context: iframeDocument 162 }; 163 } 164 165 function getItemsBySelector(selector) { 166 const query = getSelectorQuery(selector); 167 168 return $(query.selector, query.context); 169 } 170 171 // ========================================== 172 // RULE ENGINE 173 // ========================================== 174 const LAYOUT_RULES = [ 175 { 176 match: (selector) => selector === '.bet-match-item', 177 start: 3, 178 interval: 3, 179 unified: true 180 }, 181 { 182 match: (selector) => selector.includes('match-list-box-repeated-sm'), 183 start: 3, 184 interval: 3, 185 unified: true 186 }, 187 { 188 match: (selector) => selector.includes('.elite_row'), 189 start: 10, 190 interval: 10, 191 unified: true 192 }, 193 { 194 match: (selector) => selector === '.dataItem', 195 start: 3, 196 interval: 3, 197 unified: true 198 }, 199 200 { 201 match: (selector) => selector === '.table_database_cup_schedules .row.header', 202 start: 3, 203 interval: 3, 204 unified: true 205 }, 206 207 { 208 match: (selector) => selector === '#div_Table1 tr', 209 start: 10, 210 interval: 10, 211 unified: true 212 }, 213 { 214 match: (selector) => selector === '.utable tr', 215 start: 10, 216 interval: 10, 217 unified: true 218 }, 219 { 220 match: (selector) => selector === '.tdlink tr', 221 start: 10, 222 interval: 10, 223 unified: true 224 }, 225 { 226 match: (selector) => selector === '#tableId tr', 227 start: 10, 228 interval: 10, 229 unified: true 230 }, 231 { 232 match: (selector) => selector === '.ball-analysis>div:nth-child(2)>div', 233 start: 1, 234 interval: 1, 235 unified: true 236 }, 237 { 238 match: (selector) => selector === '.ball-analysis>div:nth-child(3)>div', 239 start: 1, 240 interval: 1, 241 unified: true 242 }, 243 { 244 match: (selector) => selector === '.match-analysis-page-mobile>div', 245 start: 1, 246 interval: 1, 247 unified: true 248 }, 249 { 250 match: (selector) => selector === '.schedule-app .as-competition-wrapper', 251 start: 1, 252 interval: 1, 253 unified: true 254 }, 255 { 256 match: (selector) => selector === '#resultcontainer .reactRow', 257 start: 10, 258 interval: 10, 259 unified: true 260 }, 261 { 262 match: (selector) => selector === '.livescore-tbl-groups>div', 263 start: 10, 264 interval: 10, 265 unified: true 266 }, 267 { 268 match: (selector) => selector === '#played-matches-table-body tr', 269 start: 10, 270 interval: 10, 271 unified: true 272 }, 273 { 274 match: (selector) => selector === '.bongdawap_table-ranking .bongdawap_row', 275 start: 10, 276 interval: 10, 277 unified: true 278 }, 279 { 280 match: (selector) => selector === '.groups-grid .group-table-wrapper', 281 start: 2, 282 interval: 2, 283 unified: true 284 }, 285 { 286 match: () => true, 287 mobileStart: 3, 288 desktopStart: 6, 289 mobileInterval: 3, 290 desktopInterval: 6, 291 unified: false 292 } 293 ]; 294 295 function getLayoutRule(selector) { 296 const rule = LAYOUT_RULES.find(r => r.match(selector)); 297 if (!rule) return null; 298 299 if (rule.unified) { 300 return { start: rule.start, interval: rule.interval }; 301 } 302 303 return isMobile() 304 ? { start: rule.mobileStart, interval: rule.mobileInterval } 305 : { start: rule.desktopStart, interval: rule.desktopInterval }; 306 } 307 308 // ========================================== 309 // BANNERS 310 // ========================================== 311 function getBannerByKey(key) { 312 if (CONFIG[key]) { 313 return CONFIG[key]; 314 } 315 316 const normalizedKey = normalizeBannerKey(key); 317 const matchedKey = Object.keys(CONFIG).find(name => normalizeBannerKey(name) === normalizedKey); 318 319 return matchedKey ? CONFIG[matchedKey] : null; 320 } 321 322 function getBannersByKeys(keys) { 323 const usedKeys = []; 324 325 return keys 326 .map(key => { 327 const normalizedKey = normalizeBannerKey(key); 328 329 if (usedKeys.includes(normalizedKey)) { 330 return null; 331 } 332 333 usedKeys.push(normalizedKey); 334 335 return getBannerByKey(key); 336 }) 337 .filter(Boolean); 338 } 339 340 function getBannerValueByKeys(item, keys) { 341 if (!item || typeof item !== 'object') return ''; 342 343 const normalizedKeys = keys.map(normalizeBannerKey);
344 const key = Object.keys(item).find(name => normalizedKeys.includes(normalizeBannerKey(name))); 345 346 return key ? item[key] : ''; 347 } 348 349 function getBannerMarkup(item) { 350 const html = getBannerValueByKeys(item, ['html_code', 'html code', 'html-code', 'html']); 351 352 if (html) { 353 return html; 354 } 355 356 const zone = item && typeof item === 'object' 357 ? getBannerValueByKeys(item, ['zone', 'zone_id', 'zone id', 'id', 'data-z', 'data_z', 'data z']) 358 : item; 359 360 if (!zone) { 361 return ''; 362 } 363 364 return `<div style="text-align:center;width:100%"> 365 <ins data-z="${zone}" data-revive-id="1718ffff6aff14155bf9e84ffb3a29ee"></ins> 366 </div>`; 367 } 368 369 function normalizeBannerKey(key) { 370 return String(key || '').toLowerCase().replace(/[^a-z0-9]+/g, ''); 371 } 372 373 function isDefaultDesktopKey(key) { 374 const normalized = normalizeBannerKey(key); 375 376 return normalized.startsWith('content') || normalized.startsWith('incontent'); 377 } 378 379 function getDefaultDesktopKeys() { 380 const keys = []; 381 382 const patterns = [ 383 'content', 384 'in-content', 385 'In-content', 386 'In content', 387 ]; 388 389 patterns.forEach(pattern => { 390 keys.push(pattern); 391 392 [1, 2, 3].forEach(index => { 393 keys.push(`${pattern}-${index}`); 394 }); 395 }); 396 397 return keys; 398 } 399 400 function getConfiguredDesktopKeys() { 401 const customKey = String(OPTIONS.key || '').trim(); 402 403 if (!customKey) { 404 return []; 405 } 406 407 if (customKey.includes(',')) { 408 return customKey 409 .split(',') 410 .map(key => key.trim()) 411 .filter(Boolean); 412 } 413 414 return [1, 2, 3].map(index => `${customKey}-${index}`); 415 } 416 417 function getDesktopBanners() { 418 const banners = getBannersByKeys(getConfiguredDesktopKeys()); 419 420 if (banners.length) { 421 return banners; 422 } 423 424 return getContentBanners(); 425 } 426 427 function getContentBanners() { 428 return getBannersByKeys(getDefaultDesktopKeys()); 429 } 430 431 function getDefaultMobileKeys() { 432 const keys = []; 433 434 ['content', 'in-content', 'In-content', 'In content'].forEach(prefix => { 435 for (let i = 4; i <= 6; i++) { 436 keys.push(`${prefix}-${i}`); 437 } 438 }); 439 440 return keys; 441 } 442 443 function getConfiguredMobileKeys() { 444 const customKey = String(OPTIONS.key || '').trim(); 445 446 if (!customKey) { 447 return getDefaultMobileKeys(); 448 } 449 450 if (customKey.includes(',')) { 451 return customKey 452 .split(',') 453 .map(key => key.trim()) 454 .filter(Boolean); 455 } 456 457 return [4, 5, 6].map(index => `${customKey}-${index}`); 458 } 459 460 function getMobileBanner() { 461 const banners = getBannersByKeys(getConfiguredMobileKeys()); 462 463 if (banners.length) { 464 return banners; 465 } 466 467 return getBannersByKeys(getDefaultMobileKeys()); 468 } 469 470 function getBannersForSelector(selector) { 471 return isMobile() 472 ? getMobileBanner() 473 : getDesktopBanners(); 474 } 475 476 // ========================================== 477 // INIT 478 // ========================================== 479 $(document).ready(function () { 480 481 if (!CONFIG || Object.keys(CONFIG).length === 0) return; 482 483 bindIframeReload(); 484 startFirstAvailableSelector(); 485 }); 486 487 function startFirstAvailableSelector() { 488 findActiveSelector(SELECTORS_PRESET, function (selector) { 489 490 const banners = getBannersForSelector(selector); 491 492 if (!banners.length) return false; 493 494 startObserver(selector, banners); 495 496 return true; 497 }); 498 } 499 500 function restartObserver() { 501 if (observer) observer.disconnect(); 502 503 removeInjectedAds(); 504 lastRenderSignature = ''; 505 lastItemCount = 0; 506 507 startFirstAvailableSelector(); 508 } 509 510 function bindIframeReload() { 511 $('iframe').on('load.gwdSportAds', function () { 512 clearTimeout(debounce); 513 514 debounce = setTimeout(restartObserver, 300); 515 }); 516 } 517 518 // ========================================== 519 // FIND SELECTOR 520 // ========================================== 521 function findActiveSelector(list, cb) { 522 let isStarted = false; 523 524 if (discoveryTimer) { 525 clearInterval(discoveryTimer); 526 } 527 528 if (discoveryObserver) { 529 discoveryObserver.disconnect(); 530 } 531 532 const check = () => { 533 if (isStarted) return; 534 535 for (const sel of list) {
536 if (getItemsBySelector(sel).length) { 537 const started = cb(sel) === true; 538 539 if (started) { 540 isStarted = true; 541 clearInterval(discoveryTimer); 542 discoveryObserver.disconnect(); 543 return; 544 } 545 } 546 } 547 }; 548 549 discoveryTimer = setInterval(check, 200); 550 discoveryObserver = new MutationObserver(check); 551 552 discoveryObserver.observe(document.body, { 553 childList: true, 554 subtree: true 555 }); 556 557 check(); 558 } 559 560 // ========================================== 561 // MAIN OBSERVER 562 // ========================================== 563 function startObserver(itemSelector, banners) { 564 565 if (observer) observer.disconnect(); 566 lastRenderSignature = ''; 567 lastItemCount = 0; 568 activeItemSelector = itemSelector; 569 activeBanners = banners; 570 571 const container = getObserverContainer(itemSelector); 572 573 if (!container) return; 574 575 const rerender = (force = false) => { 576 577 renderAds(itemSelector, banners, force); 578 // ð¥ luôn render lại (không signature, không cache) 579 }; 580 581 const rebuildIfItemCountChanged = () => { 582 const currentItemCount = getRealItems(itemSelector).length; 583 584 if (currentItemCount === lastItemCount) { 585 return false; 586 } 587 588 rebuildAds(itemSelector, banners); 589 590 return true; 591 }; 592 rerender(true); 593 594 observer = new MutationObserver(() => { 595 596 if (isRendering || isRebuilding) return; 597 598 clearTimeout(debounce); 599 debounce = setTimeout(rebuildIfItemCountChanged, 200); 600 601 }); 602 603 observer.observe(container, { 604 childList: true, 605 subtree: true 606 }); 607 } 608 609 function getObserverContainer(itemSelector) { 610 const $firstItem = getItemsBySelector(itemSelector).first(); 611 const $closestWrapper = $firstItem.closest(WRAPPER_SELECTORS.join(',')).first(); 612 613 if ($closestWrapper.length) { 614 return $closestWrapper[0]; 615 } 616 617 const $parent = $firstItem.parent(); 618 619 if ($parent.length) { 620 return $parent[0]; 621 } 622 623 return $(WRAPPER_SELECTORS.join(',')).first()[0]; 624 } 625 626 function getRealItems(itemSelector) { 627 return getItemsBySelector(itemSelector) 628 .not('[data-gwd-banner="1"]') 629 .not('.sport-m') 630 .not('.w7m_score-table--title') 631 .not('.w7m_score-table--head') 632 .filter(function () { 633 return !$(this).closest('[data-gwd-banner="1"], .sport-m').length; 634 }); 635 } 636 637 function getNodeId(node) { 638 if (!nodeIds.has(node)) { 639 nodeIds.set(node, ++nodeUid); 640 } 641 642 return nodeIds.get(node); 643 } 644 645 function getItemAdId(node) { 646 return 'gwd-item-' + getNodeId(node); 647 } 648 649 function getRenderSignature($items) { 650 return [ 651 isMobile() ? 'm' : 'd', 652 $items.length, 653 $items.map(function () { 654 return getNodeId(this); 655 }).get().join(',') 656 ].join('|'); 657 } 658 659 function removeInjectedAds() { 660 $('.sport-m[data-gwd-banner="1"], [data-gwd-banner="1"].sport-m').remove(); 661 662 getSameOriginIframeDocuments().forEach(iframeDocument => { 663 $('.sport-m[data-gwd-banner="1"], [data-gwd-banner="1"].sport-m', iframeDocument).remove(); 664 }); 665 } 666 667 function getInjectedAdById(itemSelector, adId) { 668 const query = getSelectorQuery(itemSelector); 669 670 return $(`[data-gwd-after="${adId}"]`, query.context); 671 } 672 673 function rebuildAds(itemSelector, banners) { 674 if (!itemSelector || !banners.length) return; 675 676 isRebuilding = true; 677 removeInjectedAds(); 678 lastRenderSignature = ''; 679 lastItemCount = 0; 680 renderAds(itemSelector, banners, true); 681 triggerReviveRefresh();
682 683 setTimeout(() => { 684 isRebuilding = false; 685 }, 0); 686 } 687 688 function renderAds(itemSelector, banners, force = false) { 689 690 let $items = getRealItems(itemSelector); 691 if (!$items.length || !banners.length) return; 692 693 const signature = getRenderSignature($items); 694 if (!force && signature === lastRenderSignature) return; 695 696 isRendering = true; 697 698 try { 699 injectAds($items, banners, itemSelector); 700 lastRenderSignature = getRenderSignature($items); 701 lastItemCount = $items.length; 702 } finally { 703 704 setTimeout(() => { 705 isRendering = false; 706 }, 0); 707 } 708 } 709 710 // ========================================== 711 // INJECT ADS (NO DUPLICATE SAFE) 712 // ========================================== 713 function injectAds($items, banners, itemSelector) { 714 715 if (!$items.length || !banners.length) return; 716 717 const isTable = $items.first().is('tr'); 718 const rule = getLayoutRule(itemSelector); 719 720 if (!rule) return; 721 722 const shouldCheckHidden = SELECTORS_PRESET.includes(itemSelector); 723 724 if (shouldCheckHidden) { 725 726 $items.each(function () { 727 728 const $el = $(this); 729 730 if ($el.css('display') === 'none') { 731 $el.addClass('sport-hidden'); 732 } else { 733 $el.removeClass('sport-hidden'); 734 } 735 }); 736 } 737 738 let bannerIndex = 0; 739 const bannerLimit = Infinity; 740 741 $items.each(function (index) { 742 743 const $el = $(this); 744 const itemNumber = index + 1; 745 const adId = getItemAdId(this); 746 747 if (itemNumber < rule.start) return; 748 if ((itemNumber - rule.start) % rule.interval !== 0) return; 749 if (bannerIndex >= bannerLimit) return; 750 751 if (getInjectedAdById(itemSelector, adId).length) { 752 bannerIndex++; 753 return; 754 } 755 756 // â HARD GUARD: chá»ng duplicate tuyá»t Äá»i 757 const $next = $el.next(); 758 759 if ( 760 $next.hasClass('sport-m') || 761 $next.attr('data-gwd-banner') === '1' || 762 $next.attr('data-gwd-after') === adId 763 ) { 764 bannerIndex++; 765 return; 766 } 767 768 const banner = banners[bannerIndex % banners.length]; 769 770 if (!banner) return; 771 772 const getInner = (item) => getBannerMarkup(item); 773 774 if (itemSelector === '#live-score .tds') { 775 776 const leftBanner = banner; 777 const rightBanner = banners[(bannerIndex + 1) % banners.length] || banner; 778 const inner = `<div class="sport-m-grid-2" style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;width:100%"> 779 <div>${getInner(leftBanner)}</div> 780 <div>${getInner(rightBanner)}</div> 781 </div>`; 782 const html = isTable 783 ? `<tr class="sport-m" data-gwd-banner="1" data-gwd-after="${adId}"> 784 <td colspan="100" style="border:none;padding:0;text-align:center"> 785 ${inner} 786 </td> 787 </tr>` 788 : `<div class="sport-m" data-gwd-banner="1" data-gwd-after="${adId}">${inner}</div>`; 789 790 $el.after(html); 791 bannerIndex += 2; 792 return; 793 } 794 795 const inner = getInner(banner); 796 797 const html = isTable 798 ? `<tr class="sport-m" data-gwd-banner="1" data-gwd-after="${adId}"> 799 <td colspan="100" style="border:none;padding:0;text-align:center"> 800 ${inner} 801 </td> 802 </tr>` 803 : `<div class="sport-m" data-gwd-banner="1" data-gwd-after="${adId}">${inner}</div>`; 804 805 $el.after(html); 806 807 bannerIndex++; 808 }); 809 } 810 811 // ========================================== 812 // REVIVE REFRESH 813 // ========================================== 814 function triggerReviveRefresh() { 815 try { 816 const first = document.querySelector('ins[data-revive-id]'); 817 if (!first) return; 818 819 const id = first.getAttribute('data-revive-id'); 820 window.reviveAsync?.[id]?.dispatchEvent("refresh"); 821 } catch (e) {} 822 } 823 824 // ========================================== 825 // RESIZE HANDLER 826 // ========================================== 827 window.addEventListener('resize', () => { 828 clearTimeout(resizeDebounce); 829 830 resizeDebounce = setTimeout(() => { 831 lastIsMobile = isMobile(); 832 restartObserver(); 833 }, 250); 834 });
835 836 $(document).on('click', '[class*="load"], [id*="load"], [class*="more"], [id*="more"]', function () { 837 if (!activeItemSelector || !activeBanners.length) return; 838 839 clearTimeout(debounce); 840 841 debounce = setTimeout(() => { 842 rebuildAds(activeItemSelector, activeBanners); 843 844 }, 800); 845 }); 846 847 848 function syncSportState() { 849 850 $('.sport-hidden').each(function () { 851 852 const $trigger = $(this); 853 const $banner = $trigger.next('.sport-m'); 854 855 if (!$banner.length) return; 856 857 const triggerHidden = $trigger.css('display') === 'none'; 858 const bannerHidden = $banner.css('display') === 'none'; 859 860 // nếu trigger bá» hide â banner hide theo 861 if (triggerHidden && !bannerHidden) { 862 $banner.css('display', 'none'); 863 } 864 865 // nếu trigger show â banner show theo 866 if (!triggerHidden && bannerHidden) { 867 $banner.css('display', ''); 868 } 869 }); 870 } 871 872 let syncTimer = null; 873 874 const syncObserver = new MutationObserver(() => { 875 876 clearTimeout(syncTimer); 877 878 syncTimer = setTimeout(() => { 879 syncSportState(); 880 }, 100); 881 }); 882 883 syncObserver.observe(document.body, { 884 attributes: true, 885 subtree: true, 886 attributeFilter: ['style', 'class'] 887 }); 888 889})(jQuery);
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.