PageSourceSearch

https://lelu.io/wp-content/plugins/gwd-config-banner/assets/js/gwd-banner-theme-sport.js?ver=1789734494

js lelu.io collected 2026-10-10 02:44:29 UTC 25,729 bytes, 891 lines download raw bytes

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.