Autodarts Plus (caller & other stuff)

Userscript for Autodarts

目前為 2024-03-03 提交的版本,檢視 最新版本

// ==UserScript==
// @id           autodarts-plus@https://github.com/sebudde/autodarts-plus
// @name         Autodarts Plus (caller & other stuff)
// @namespace    https://github.com/sebudde/autodarts-plus
// @version      0.6.3
// @description  Userscript for Autodarts
// @author       sebudde
// @match        https://play.autodarts.io/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=autodarts.io
// @license      MIT
// @grant        GM.getValue
// @grant        GM.setValue
// ==/UserScript==

(async function() {
    'use strict';

    //////////////// CONFIG END ////////////////////

    const readyClasses = {
        play: 'css-1lua7td',
        lobbies: 'css-1q0rlnk',
        table: 'css-p3eaf1', // matches & boards
        match: 'css-ul22ge',
        matchHistory: 'css-10z204m'
    };

    let firstLoad = true;

    let configPathName = '/config';
    const pageContainer = document.createElement('div');

    const observeDOM = (function() {
        const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

        return function(obj, callback) {
            if (!obj || obj.nodeType !== 1) return;
            const mutationObserver = new MutationObserver(callback);
            mutationObserver.observe(obj, {
                attributes: true,
                childList: true,
                subtree: true
            });
            return mutationObserver;
        };
    })();

    //////////////// CSS classes start ////////////////////
    const adp_style = document.createElement('style');
    adp_style.type = 'text/css';
    adp_style.innerHTML = `
        body {
          /* mobile viewport bug fix */
          min-height: -webkit-fill-available!important;
        }
        html {
          height: -webkit-fill-available;
        }
        #root {
            height: 100vh;
        }
        .adp_points-small { font-size: 3em!important; }
        .adp_config-header {
              font-weight: 700;
              align-self: flex-start;
        }
        h2.adp_config-header { font-size: 1.5em; }
        h3.adp_config-header { font-size: 1.2em; }
        button.adp_config-btn {
            border-radius: var(--chakra-radii-md);
            background: var(--chakra-colors-whiteAlpha-200);
            font-weight: var(--chakra-fontWeights-semibold);
            height: var(--chakra-sizes-8);
            min-width: var(--chakra-sizes-16);
            padding: 0 var(--chakra-space-4);
            font-size: var(--chakra-fontSizes-md);
        }
        button.adp_config-btn:active:not(:disabled), button.adp_config-btn.active:not(:disabled) {
            background: var(--chakra-colors-whiteAlpha-400);
        }
        button.adp_config-btn:disabled {
            cursor: default;
            opacity: 0.5;
            // background: var(--chakra-colors-whiteAlpha-50);
            // color:
        }
        .adp_config-btn--label {
            width: 350px;
        }
        .adp_cricket-rownumber {
            font-size: var(--chakra-fontSizes-xl);
            position: absolute;
            left: calc(var(--chakra-space-2)* -1);
            text-align: center;
            width: var(--chakra-sizes-10);
            border-radius: 3px;
            color: var(--chakra-colors-white);
            border: white;
            opacity: 1;
            background: var(--chakra-colors-gray-500);
        }
    `;
    document.getElementsByTagName('head')[0].appendChild(adp_style);

    let callerData = {};
    let winnerSoundData = {};
    let inactiveSmall;
    let showTotalDartsAtLegFinish;
    let showTotalDartsAtLegFinishLarge;

    let headerEl;
    let mainContainerEl;

    const setActiveAttr = (el, isActive) => {
        if (isActive) {
            el.setAttribute('data-active', '');
            el.classList.add('active');
        } else {
            el.removeAttribute('data-active');
            el.classList.remove('active');
        }
    };

    ////////////////   ////////////////////

    const setAdpData = async (name, value) => {

        const data = name.split('-');

        if (data[0].startsWith('caller')) {
            const gmCallerData = await GM.getValue('callerData') || {};
            const gmCallerValue = gmCallerData[data[0]] || {};
            const newCallerValue = {[data[1]]: value};
            callerData = {
                ...gmCallerData,
                [data[0]]: {...gmCallerValue, ...newCallerValue}
            };
            await GM.setValue('callerData', callerData);

        } else if (data[0].startsWith('winnerSound')) {
            const gmWinnerSoundData = await GM.getValue('winnerSoundData') || {};
            const gmWinnerSoundValue = gmWinnerSoundData[data[0]] || {};
            const newWinnerSoundValue = {[data[1]]: value};
            winnerSoundData = {
                ...gmWinnerSoundData,
                [data[0]]: {...gmWinnerSoundValue, ...newWinnerSoundValue}
            };
        }
        await GM.setValue('winnerSoundData', winnerSoundData);
    };

    const callerObj = {
        caller1: {
            folder: '0',
            name: 'Caller OFF'
        },
        caller2: {
            folder: '1_male_eng',
            name: 'Male eng',
            server: 'https://autodarts.x10.mx',
            fileExt: '.mp3'
        },
        caller3: {
            folder: 'google_eng',
            name: 'Google eng'
        },
        caller4: {
            folder: 'google_de',
            name: 'Google de'
        }
    };

    const getPlayerCount = () => {
        return document.querySelectorAll('.css-3dp02s').length;
    };

    const onDOMready = async () => {
        console.log('firstLoad', firstLoad);
        headerEl = document.querySelector('.css-gmuwbf');
        mainContainerEl = document.querySelectorAll('#root > div')[1];

        let hideHeaderGM = await GM.getValue('hideHeader');
        headerEl.style.display = hideHeaderGM ? 'none' : 'flex';
        mainContainerEl.style.height = hideHeaderGM ? '100%' : 'calc(-72px + 100%)';
        mainContainerEl.children[0].style.height = '100%';

        if (firstLoad) {
            firstLoad = false;

            const hideHeaderBtn = document.createElement('button');
            hideHeaderBtn.id = 'hideHeader';
            hideHeaderBtn.innerText = 'Header';
            hideHeaderBtn.classList.add('adp_config-btn');
            hideHeaderBtn.style.position = 'fixed';
            hideHeaderBtn.style.bottom = '20px';
            hideHeaderBtn.style.right = '20px';

            setActiveAttr(hideHeaderBtn, !hideHeaderGM);

            hideHeaderBtn.addEventListener('click', async (event) => {
                const isActive = event.target.classList.contains('active');
                setActiveAttr(hideHeaderBtn, !isActive);
                headerEl.style.display = isActive ? 'none' : 'flex';
                mainContainerEl.style.height = isActive ? '100%' : 'calc(-72px + 100%)';

                await GM.setValue('hideHeader', isActive);
            }, false);

            document.getElementById('root').appendChild(hideHeaderBtn);

            //////////////// winner sound data  ////////////////////

            winnerSoundData = await GM.getValue('winnerSoundData') || {};

            //////////////// caller data  ////////////////////

            const gmCallerData = await GM.getValue('callerData') || {};

            callerData = {
                ...gmCallerData, ...callerObj
            };

            await GM.setValue('callerData', callerData);

            const callerObjLength = Object.keys(callerObj).length;

            //////////////// add config page  ////////////////////

            inactiveSmall = (await GM.getValue('inactiveSmall')) ?? true;
            showTotalDartsAtLegFinish = (await GM.getValue('showTotalDartsAtLegFinish')) ?? true;
            showTotalDartsAtLegFinishLarge = (await GM.getValue('showTotalDartsAtLegFinishLarge')) ?? false;

            pageContainer.classList.add('css-gmuwbf');
            const configContainer = document.createElement('div');
            configContainer.classList.add('css-10z204m');
            pageContainer.appendChild(configContainer);
            pageContainer.style.display = 'none';

            const configHeader = document.createElement('h2');
            configHeader.classList.add('adp_config-header');
            configHeader.innerText = 'Config';
            configContainer.appendChild(configHeader);

            const configContentHeader = document.createElement('h3');
            configContentHeader.classList.add('adp_config-header');
            configContentHeader.innerText = 'Match';
            configContainer.appendChild(configContentHeader);

            const configContentRow1 = document.createElement('div');
            configContentRow1.classList.add('css-1p4eqnd');
            configContentRow1.style.gap = '2rem';

            configContentRow1.innerHTML = `
            <div class="adp_config-btn--label">Show points of inactive player</div>
            <button id="inactiveSmall" class="css-1xbmrf2 adp_config-btn${inactiveSmall ? ' active' : ''}">${inactiveSmall ? 'ON' : 'OFF'}</button>
            `;

            configContentRow1.querySelector('button#inactiveSmall').addEventListener('click', async (event) => {
                const isInactiveSmall = event.target.classList.contains('active');
                event.target.classList.toggle('active');
                inactiveSmall = !isInactiveSmall;
                await GM.setValue('inactiveSmall', !isInactiveSmall);
                event.target.innerText = !isInactiveSmall ? 'ON' : 'OFF';
            }, false);

            const configContentRow2 = document.createElement('div');
            configContentRow2.classList.add('css-1p4eqnd');
            configContentRow2.style.gap = '2rem';

            configContentRow2.innerHTML = `
            <div class="adp_config-btn--label">Show total darts thrown at end of leg</div>
            <button id="showTotalDartsAtLegFinish" class="css-1xbmrf2 adp_config-btn${showTotalDartsAtLegFinish ? ' active' : ''}">${showTotalDartsAtLegFinish ? 'ON' : 'OFF'}</button>
            <button id="showTotalDartsAtLegFinishLarge" ${showTotalDartsAtLegFinish ? '' : 'disabled'} class="css-1xbmrf2 adp_config-btn${showTotalDartsAtLegFinishLarge
                ? ' active'
                : ''}">${showTotalDartsAtLegFinishLarge ? 'LARGE' : 'SMALL'}</button>
            `;

            configContentRow2.querySelector('button#showTotalDartsAtLegFinish').addEventListener('click', async (event) => {
                const isShowTotalDartsAtLegFinish = event.target.classList.contains('active');
                event.target.classList.toggle('active');
                showTotalDartsAtLegFinish = !isShowTotalDartsAtLegFinish;
                await GM.setValue('showTotalDartsAtLegFinish', !isShowTotalDartsAtLegFinish);
                event.target.innerText = !isShowTotalDartsAtLegFinish ? 'ON' : 'OFF';
                configContentRow2.querySelector('button#showTotalDartsAtLegFinishLarge').toggleAttribute('disabled', isShowTotalDartsAtLegFinish);
            }, false);

            configContentRow2.querySelector('button#showTotalDartsAtLegFinishLarge').addEventListener('click', async (event) => {
                const isShowTotalDartsAtLegFinishLarge = event.target.classList.contains('active');
                event.target.classList.toggle('active');
                showTotalDartsAtLegFinishLarge = !isShowTotalDartsAtLegFinishLarge;
                await GM.setValue('showTotalDartsAtLegFinishLarge', !isShowTotalDartsAtLegFinishLarge);
                event.target.innerText = !isShowTotalDartsAtLegFinishLarge ? 'LARGE' : 'SMALL';
            }, false);

            configContainer.appendChild(configContentRow1);
            configContainer.appendChild(configContentRow2);

            const callerHeader = document.createElement('h3');
            callerHeader.classList.add('adp_config-header');
            callerHeader.innerText = 'Caller';
            configContainer.appendChild(callerHeader);

            for (let callerCount = callerObjLength + 1; callerCount <= callerObjLength + 5; callerCount++) {
                const callerContainer = document.createElement('div');
                callerContainer.classList.add('css-1p4eqnd');
                callerContainer.style.gap = '30px';
                const callerServer = callerData[`caller${callerCount}`]?.server || '';
                const callerName = callerData[`caller${callerCount}`]?.name || '';
                const callerFolder = callerData[`caller${callerCount}`]?.folder || '';
                const callerFileExt = callerData[`caller${callerCount}`]?.fileExt || '';

                callerContainer.innerHTML = `
                        <div class="css-1igwmid" style="width: 70px; margin-right: 2em">
                            <b>Caller ${callerCount - callerObjLength}</b>
                        </div>
                        <div class="css-1igwmid">
                            <div class="css-u4ybgy" style="width: 240px"><div class="css-1igwmid"><input placeholder="Server" class="adp_caller--server css-1ndqqtl" name="caller${callerCount}-server" value="${callerServer}"></div></div>
                        </div>
                        <div class="css-1igwmid">
                            <div class="css-u4ybgy"><div class="css-1igwmid"><input placeholder="Name" class="adp_caller--name css-1ndqqtl" name="caller${callerCount}-name" value="${callerName}"></div></div>
                        </div>
                        <div class="css-1igwmid">
                            <div class="css-u4ybgy" style="width: 180px"><div class="css-1igwmid"><input placeholder="Folder"  class="adp_caller--folder css-1ndqqtl" name="caller${callerCount}-folder" value="${callerFolder}"></div></div>
                        </div>
                        <div class="css-1igwmid">
                            <div class="css-u4ybgy" style="width: 100px"><div class="css-1igwmid"><input placeholder="File ext"  class="adp_caller--folder css-1ndqqtl" name="caller${callerCount}-fileExt" value="${callerFileExt}"></div></div>
                        </div>`;
                configContainer.appendChild(callerContainer);
            }

            const winnerSoundHeader = document.createElement('h3');
            winnerSoundHeader.classList.add('adp_config-header');
            winnerSoundHeader.innerText = 'Winner sound';
            configContainer.appendChild(winnerSoundHeader);

            const winnerSoundMax = 3;

            for (let winnerSoundCount = 1; winnerSoundCount <= winnerSoundMax; winnerSoundCount++) {
                const winnerSoundContainer = document.createElement('div');
                winnerSoundContainer.classList.add('css-1p4eqnd');
                winnerSoundContainer.style.gap = '30px';
                const winnerSoundPlayername = winnerSoundData[`winnerSound${winnerSoundCount}`]?.playername || '';
                const winnerSoundSoundUrl = winnerSoundData[`winnerSound${winnerSoundCount}`]?.soundurl || '';

                const isLast = winnerSoundCount === winnerSoundMax;

                winnerSoundContainer.innerHTML = `
                        <div class="css-1igwmid" style="width: 70px; margin-right: 2em">
                            <b>${isLast ? 'Fallback' : 'Player ' + winnerSoundCount}</b>
                        </div>
                        <div class="css-1igwmid">
                            <div class="css-u4ybgy" style="width: 240px"><div class="css-1igwmid"><input ${isLast
                    ? 'disabled'
                    : ''} placeholder="Player name" class="adp_winnerSound--playername css-1ndqqtl" name="winnerSound${winnerSoundCount}-playername" value="${isLast
                    ? 'Fallback'
                    : winnerSoundPlayername}"></div></div>
                        </div>
                        <div class="css-1igwmid">
                            <div class="css-u4ybgy" style="width: 500px"><div class="css-1igwmid"><input placeholder="Sound URL"  class="adp_winnerSound--soundurl css-1ndqqtl" name="winnerSound${winnerSoundCount}-soundurl" value="${winnerSoundSoundUrl}"></div></div>
                        </div>`;
                configContainer.appendChild(winnerSoundContainer);
            }

            const input = configContainer.querySelectorAll('input');
            [...input].forEach((el) => (el.addEventListener('blur', (e) => {
                setAdpData(e.target.name, e.target.value);
            })));

            document.getElementById('root').appendChild(pageContainer);

            //////////////// add menu  ////////////////////
            const menuBtn = document.createElement('a');
            menuBtn.classList.add('css-1nlwyv4');
            menuBtn.classList.add('adp_menu-btn');
            menuBtn.innerText = 'Config';
            menuBtn.style.cursor = 'pointer';
            const menuContainer = document.querySelector('.css-1igwmid');
            menuContainer.appendChild(menuBtn);

            [...document.querySelectorAll('.css-1nlwyv4')].forEach((el) => (el.addEventListener('click', async (event) => {
                document.querySelector('#root > div:nth-of-type(2)').style.display = 'flex';
                pageContainer.style.display = 'none';
                if (event.target.classList.contains('adp_menu-btn')) {
                    // switch to page "Matches History" because we need its CSS
                    menuContainer.querySelector('a:nth-of-type(4)').click();
                    window.history.pushState(null, '', configPathName);
                }

            }, false)));

        }

        console.log('DOM ready');
    };

    const showConfig = () => {
        console.log('showConfig');
    };

    ////////////////  end ////////////////////

    const handleConfigPage = () => {
        console.log('config ready');
        document.querySelector('#root > div:nth-of-type(2)').style.display = 'none';
        pageContainer.style.display = 'flex';
    };

    const handlePlay = () => {
        console.log('play ready');
    };

    const handleMatchHistory = () => {
        console.log('matchHistory ready');
        if (location.pathname === configPathName) {
            handleConfigPage();
        }
    };

    const handleMatches = () => {
        console.log('matches ready');
    };

    const handleMatch = async () => {
        console.log('match ready!');

        const isiOS = [
                'iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(navigator.platform) || // iPad on iOS 13 detection
            (navigator.userAgent.includes('Mac') && 'ontouchend' in document);

        const isSmallDisplay = window.innerHeight < 900;

        let playerCount = getPlayerCount();

        // iOS fix
        // https://stackoverflow.com/questions/31776548/why-cant-javascript-play-audio-files-on-iphone-safari
        const soundEffect1 = new Audio();
        soundEffect1.autoplay = true;
        const soundEffect2 = new Audio();
        soundEffect2.autoplay = true;

        const matchMenuRow = document.createElement('div');
        matchMenuRow.classList.add('css-k008qs');
        matchMenuRow.style.marginTop = 'calc(var(--chakra-space-2) * -1 - 4px)';

        const matchMenuContainer = document.createElement('div');
        matchMenuContainer.classList.add('css-a6m3v9');

        matchMenuRow.appendChild(matchMenuContainer);

        document.querySelector('.css-k008qs').after(matchMenuRow);

        // PR font-size larger
        [...document.querySelectorAll('.css-1n5vwgq .css-qqfgvy')].forEach((el) => (el.style.fontSize = 'var(--chakra-fontSizes-xl)'));

        //
        [...document.querySelectorAll('.css-1memit')].forEach((el) => {
            el.style.margin = '0';
            el.style.padding = '0.5rem 0';
            el.style.height = '100%';
            el.style.justifyContent = 'space-between';
        });
        [...document.querySelectorAll('.css-3dp02s .css-x3m75h')].forEach((el) => (el.style.lineHeight = '9rem'));

        const matchVariant = document.querySelector('.css-1xbroe7').innerText.split(' ')[0];
        // console.log('matchVariant', matchVariant);
        const matchVariantArr = ['X01', 'Cricket'];
        if (!matchVariantArr.includes(matchVariant)) return;

        let cricketClosedPoints = [];

        const setCricketClosedPoints = () => {
            const cricketPointTable = document.querySelector('.css-1gy113g').children[2];

            if (!cricketPointTable?.children) return;
            cricketClosedPoints = [];

            [...cricketPointTable.children].forEach((el, i) => {
                if (i % playerCount === 0) {
                    const rowCount = (i / playerCount) + 1;
                    const rowPoints = (rowCount === 7 ? 25 : 21 - rowCount).toString(); // Bulls fix
                    if (el.querySelector('.css-cogxfh')) {
                        cricketClosedPoints.push(rowPoints);
                    }

                    if (!el.children.length) {
                        const numberHolder = document.createElement('p');
                        numberHolder.classList.add('adp_cricket-rownumber');
                        numberHolder.innerText = (rowCount === 7 ? 'Bull' : 21 - rowCount);
                        el.appendChild(numberHolder);
                    }
                }
            });
        };

        if (matchVariant === 'Cricket') {
            document.querySelector('.css-1gy113g').style.minHeight = 0;
            if (isSmallDisplay) {
                const cricketPointContainer = document.querySelector('.css-1gy113g .css-99py2g');
                cricketPointContainer.style.minHeight = '195px';

                [...cricketPointContainer.querySelectorAll('.css-x3m75h')].forEach((el) => (el.style.lineHeight = '80pt'));
                [...cricketPointContainer.querySelectorAll('.css-x3m75h')].forEach((el) => (el.style.fontSize = '80pt'));
                [...cricketPointContainer.querySelectorAll('.css-1mxmf5a, .css-g6rh15')].forEach((el) => (el.style.fontSize = '1.25rem'));
            }

            setCricketClosedPoints();

            const buttons = [...document.querySelectorAll('button.css-1x1xjw8')];
            buttons.forEach((button) => {
                if (button.innerText === 'Undo') {
                    button.addEventListener('click', async (event) => {
                        setCricketClosedPoints();
                    }, false);
                }
            });
        }

        const counterContainer = document.querySelector('.css-oyptjf');

        let callerActive = (await GM.getValue('callerActive')) || '0';
        let triplesound = (await GM.getValue('triplesound')) || '0';
        let boosound = (await GM.getValue('boosound')) || false;
        let nextLegAfterSec = (await GM.getValue('nextLegAfterSec')) || 'OFF';

        const onSelectChange = (event) => {
            (async () => {
                eval(event.target.id + ' = event.target.value');
                await GM.setValue(event.target.id, event.target.value);
            })();
        };

        const tripleSoundArr = [
            {
                value: '0',
                name: 'Triple OFF'
            }, {
                value: '1',
                name: 'Beep'
            }, {
                value: '2',
                name: 'Löwen'
            }];

        const nextLegSecArr = [
            {
                value: 'OFF'
            }, {
                value: '0'
            }, {
                value: '5'
            }, {
                value: '10'
            }, {
                value: '20'
            }];

        const callerSelect = document.createElement('select');
        callerSelect.id = 'callerActive';
        callerSelect.classList.add('css-1xbroe7');
        callerSelect.style.padding = '0 5px';
        callerSelect.onchange = onSelectChange;

        matchMenuContainer.appendChild(callerSelect);

        for (const [caller, data] of Object.entries(callerData)) {
            if (!data.folder) continue;
            const optionEl = document.createElement('option');
            optionEl.value = caller;
            optionEl.text = data.name || data.folder;
            optionEl.style.backgroundColor = '#353d47';
            if (callerActive === caller) optionEl.setAttribute('selected', 'selected');
            callerSelect.appendChild(optionEl);
        }

        const tripleSoundSelect = document.createElement('select');
        tripleSoundSelect.id = 'triplesound';
        tripleSoundSelect.classList.add('css-1xbroe7');
        tripleSoundSelect.style.padding = '0 5px';
        tripleSoundSelect.onchange = onSelectChange;

        matchMenuContainer.appendChild(tripleSoundSelect);

        tripleSoundArr.forEach((triple) => {
            const optionEl = document.createElement('option');
            optionEl.value = triple.value;
            optionEl.text = triple.name;
            optionEl.style.backgroundColor = '#353d47';
            if (triplesound === triple.value) optionEl.setAttribute('selected', 'selected');
            tripleSoundSelect.appendChild(optionEl);
        });

        const booBtn = document.createElement('button');
        booBtn.id = 'boosound';
        booBtn.innerText = 'BOO';
        booBtn.classList.add('adp_config-btn');
        setActiveAttr(booBtn, boosound);
        matchMenuContainer.appendChild(booBtn);

        booBtn.addEventListener('click', async (event) => {
            const isActive = event.target.hasAttribute('data-active');
            setActiveAttr(booBtn, !isActive);
            boosound = !isActive;
            await GM.setValue('boosound', !isActive);
        }, false);

        const nextLegSecSelect = document.createElement('select');
        nextLegSecSelect.id = 'nextLegAfterSec';
        nextLegSecSelect.classList.add('css-1xbroe7');
        nextLegSecSelect.style.padding = '0 5px';
        nextLegSecSelect.onchange = onSelectChange;

        matchMenuContainer.appendChild(nextLegSecSelect);

        nextLegSecArr.forEach((sec) => {
            const optionEl = document.createElement('option');
            optionEl.value = sec.value;
            optionEl.text = `Next Leg ${sec.value}${sec.value === 'OFF' ? '' : ' sec'}`;
            optionEl.style.backgroundColor = '#353d47';
            if (nextLegAfterSec === sec.value) optionEl.setAttribute('selected', 'selected');
            nextLegSecSelect.appendChild(optionEl);
        });

        // ######### start iOS fix #########
        // https://stackoverflow.com/questions/31776548/why-cant-javascript-play-audio-files-on-iphone-safari

        if (isiOS) {
            const startBtnContainer = document.createElement('div');
            startBtnContainer.style.position = 'absolute';
            startBtnContainer.style.height = '100%';
            startBtnContainer.style.width = '100%';
            startBtnContainer.style.top = '0';
            startBtnContainer.style.left = '0';
            startBtnContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
            startBtnContainer.style.display = 'flex';
            startBtnContainer.style.justifyContent = 'center';
            startBtnContainer.style.alignItems = 'center';

            document.querySelector('#root').appendChild(startBtnContainer);

            const startBtn = document.createElement('button');
            startBtn.id = 'startBtn';
            startBtn.innerText = 'START';
            startBtn.classList.add('css-1xbmrf2');
            startBtn.style.background = '#ffffff';
            startBtn.style.color = '#646464';
            startBtn.style.fontSize = '36px';
            startBtn.style.padding = '36px 24px';
            startBtnContainer.appendChild(startBtn);

            startBtn.addEventListener('click', async (event) => {
                soundEffect1.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA';
                startBtnContainer.remove();
            }, false);

            // ######### end iOS fix #########
        }

        function playSound1(fileName) {
            if (!fileName) return;
            // console.log('fileName1', fileName);
            soundEffect1.src = fileName;
        }

        function playSound2(fileName) {
            if (!fileName) return;
            // console.log('fileName2', fileName);
            soundEffect2.src = fileName;
        }

        const caller = async () => {
            const soundServerUrl = 'https://autodarts-plus.x10.mx';

            const callerFolder = callerData[callerActive]?.folder || '';
            const callerServerUrl = callerData[callerActive]?.server || '';
            const fileExt = callerData[callerActive]?.fileExt || '.mp3';

            const turnPoints = counterContainer.firstChild.innerText.trim();
            const throwPointsArr = [...counterContainer.querySelectorAll('.css-dfewu8, .css-rzdgh7, .css-ucdbhl')].map((el) => el.innerText);

            const curThrowPointsName = throwPointsArr.slice(-1)[0];

            const playPointsSound = () => {
                if (callerFolder.startsWith('google')) {
                    playSound1('https://autodarts.de.cool/mp3_helper.php?language=' + callerFolder.substring(7, 9) + '&text=' + turnPoints);
                } else {
                    if (callerFolder.length && callerServerUrl.length) playSound1(callerServerUrl + '/' + callerFolder + '/' + turnPoints + '.mp3');
                }
            };

            const winnerContainer = document.querySelector('.css-e9w8hh');

            let curThrowPointsNumber = -1;
            let curThrowPointsBed = '';
            let curThrowPointsMultiplier = 1;

            if (curThrowPointsName) {
                if (curThrowPointsName.startsWith('M')) {
                    curThrowPointsNumber = 0;
                    curThrowPointsBed = 'Outside';
                } else if (curThrowPointsName === 'Bull') {
                    curThrowPointsNumber = 25;
                    curThrowPointsBed = 'D';
                } else if (curThrowPointsName === '25') {
                    curThrowPointsNumber = 25;
                    curThrowPointsBed = 'S';
                } else {
                    curThrowPointsNumber = curThrowPointsName.slice(1);
                    curThrowPointsBed = curThrowPointsName.charAt(0);
                }

                if (curThrowPointsBed === 'D') curThrowPointsMultiplier = 2;
                if (curThrowPointsBed === 'T') curThrowPointsMultiplier = 3;
            }

            if (turnPoints === 'BUST') {
                if (callerFolder.length && callerServerUrl.length) playSound1(callerServerUrl + '/' + callerFolder + '/' + '0' + fileExt);
            } else {
                if (curThrowPointsName === 'BULL') {
                    if (triplesound === '1') {
                        playSound1(soundServerUrl + '/' + 'beep_1.mp3');
                    }
                    if (triplesound === '2') {
                        playSound1(soundServerUrl + '/' + 'beep_2_bullseye.mp3');
                    }
                } else if (curThrowPointsBed === 'Outside') {
                    if (boosound === true) {
                        const randomMissCount = Math.floor(Math.random() * 3) + 1;
                        playSound1(soundServerUrl + '/' + 'miss_' + randomMissCount + '.mp3');
                    }
                } else {
                    if (matchVariant === 'X01' || (matchVariant === 'Cricket' && curThrowPointsNumber >= 15)) {
                        if (curThrowPointsMultiplier === 3) {
                            if (triplesound === '1') {
                                playSound2(soundServerUrl + '/' + 'beep_1.mp3');
                            }
                            if (triplesound === '2' && curThrowPointsNumber >= 17) {
                                playSound2(soundServerUrl + '/' + 'beep_2_' + curThrowPointsNumber + '.wav');
                            }
                        }
                    }
                }
                //////////////// Cricket ////////////////////
                if (matchVariant === 'Cricket') {
                    if (curThrowPointsNumber >= 0) {
                        if (curThrowPointsNumber >= 15 && !cricketClosedPoints.includes(curThrowPointsNumber)) {
                            setCricketClosedPoints();
                            playSound2(soundServerUrl + '/' + 'bonus-points.mp3');
                        } else {
                            playSound2(soundServerUrl + '/' + 'sound_double_windart.wav');
                        }
                    }
                }
                //////////////// play Sound ////////////////////
                if (matchVariant === 'X01' || (matchVariant === 'Cricket' && turnPoints > 0)) {
                    if (throwPointsArr.length === 3 && callerFolder.length) {
                        playPointsSound();
                    }
                }
                //////////////// ATC ////////////////////

                ////////////////  ////////////////////

                if (winnerContainer) {
                    const waitForSumCalling = throwPointsArr.length === 3 ? 2500 : 0;
                    const winnerPlayer = winnerContainer.querySelector('span.css-1mxmf5a')?.innerText;

                    document.querySelector('.game-shot-animation .css-x3m75h').style.lineHeight = '1';
                    document.querySelector('.game-shot-animation .css-x3m75h').style.marginTop = '0.5rem';

                    setTimeout(() => {
                        const buttons = [...document.querySelectorAll('button.css-1x1xjw8, button.css-1vfwxw0')];
                        buttons.forEach((button) => {
                            // --- Leg finished ---
                            if (button.innerText === 'Next Leg') {
                                if (callerFolder.length && callerServerUrl.length) playSound1(callerServerUrl + '/' + callerFolder + '/' + 'gameshot.mp3');
                            }
                            // --- Match finished ---
                            if (button.innerText === 'Finish') {
                                console.log('finish');
                                if (callerFolder.length && callerServerUrl.length) playSound1(callerServerUrl + '/' + callerFolder + '/' + 'gameshot and the match.mp3');
                                setTimeout(() => {
                                    const winnerSoundDataValues = Object.values(winnerSoundData);
                                    const winnerSoundurl = winnerSoundDataValues.find(winnersound => winnersound?.playername.toLowerCase() === winnerPlayer.toLowerCase())?.soundurl;
                                    const winnerFallbackSoundurl = winnerSoundDataValues[winnerSoundDataValues.length - 1]?.soundurl;

                                    playSound2(winnerSoundurl || winnerFallbackSoundurl);

                                }, 1000);
                            }
                        });
                    }, waitForSumCalling);
                }
            }
        };

        const onCounterChange = async () => {
            caller();

            inactiveSmall = (await GM.getValue('inactiveSmall')) ?? true;

            if (inactiveSmall) {
                [...document.querySelectorAll('.css-x3m75h')].forEach((el) => (el.classList.remove('adp_points-small')));
                [...document.querySelectorAll('.css-1a28glk .css-x3m75h')].forEach((el) => (el.classList.add('adp_points-small')));
            }

            if (showTotalDartsAtLegFinish || nextLegAfterSec !== 'OFF') {
                const winnerContainer = document.querySelector('.css-e9w8hh');

                if (winnerContainer) {
                    // --- Leg finished ---
                    console.log('Leg finished');

                    if (showTotalDartsAtLegFinish && matchVariant === 'X01') {
                        const throwRound = document.querySelector('.css-1tw9fat')?.innerText?.split('/')[0]?.substring(1);
                        const throwThisRound = document.querySelectorAll('.css-1chp9v4, .css-ucdbhl').length;

                        const throwsSum = (throwRound - 1) * 3 + throwThisRound;

                        const throwsSumEl = document.createElement('div');
                        if (!showTotalDartsAtLegFinishLarge) throwsSumEl.style.fontSize = '0.5em';
                        throwsSumEl.innerHTML = throwsSum + ' Darts';

                        const sumContainerEl = winnerContainer.querySelector('.css-x3m75h');
                        sumContainerEl.replaceChildren(throwsSumEl);
                    }

                    if (nextLegAfterSec !== 'OFF') {
                        const buttons = [...document.querySelectorAll('button.css-1vfwxw0')];
                        buttons.forEach((button) => {
                            if (button.innerText === 'Next Leg') {
                                if (!parseInt(nextLegAfterSec)) return;
                                setTimeout(() => {
                                    button.click();
                                }, parseInt(nextLegAfterSec) * 1000);
                            }
                        });
                    }
                }
            }
        };

        onCounterChange();

        observeDOM(counterContainer, async function(m) {
            onCounterChange();
        });

    };

    const handleBoards = () => {
        console.log('boards ready');
    };

    const readyClassesValues = Object.values(readyClasses);

    observeDOM(document.getElementById('root'), function(mutationrecords) {
        mutationrecords.some((record) => {
            if (record.addedNodes.length && record.addedNodes[0].classList?.length) {
                const elemetClassList = [...record.addedNodes[0].classList];
                return elemetClassList.some((className) => {
                    if (className.startsWith('css-')) {
                        // console.log('className', className);
                        if (!readyClassesValues.includes(className)) return false;
                        const key = Object.keys(readyClasses).find((key) => readyClasses[key] === className);
                        if (key) {
                            onDOMready();
                            switch (key) {
                                case 'play':
                                    handlePlay();
                                    break;
                                case 'table':
                                    document.querySelector('.' + className).children[0].classList.contains('css-12pccnb') && handleBoards();
                                    document.querySelector('.' + className).children[0].classList.contains('css-5605sr') && handleMatches();
                                    break;
                                case 'match':
                                    handleMatch();
                                    break;
                                case 'boards':
                                    handleBoards();
                                    break;
                                case 'matchHistory':
                                    handleMatchHistory();
                                    break;
                            }
                            return true;
                        }
                    }
                });
            }
        });
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址