PageSourceSearch

https://toptransjute.com/template_static/view/913-bcpaiming-15/static/js/gwd-banner-theme-sport.js

js toptransjute.com collected 2026-09-29 11:46:38 UTC 25,672 bytes, 889 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        '.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.