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