Geoguessr Team Duels Advanced Options

Adds extra options to team duel settings.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Geoguessr Team Duels Advanced Options
// @description  Adds extra options to team duel settings.
// @version      0.2.3
// @author       macca#8949
// @license      MIT
// @match        https://www.geoguessr.com/*
// @run-at       document-start
// @grant        none
// @require      https://greasyfork.org/scripts/460322-geoguessr-styles-scan/code/Geoguessr%20Styles%20Scan.js?version=1151654
// @namespace    https://greasyfork.org/en/scripts/452579-geoguessr-team-duels-advanced-options
// ==/UserScript==

let cachedGameMode = '';
let cachedGameOptions = {};

const getGameId = () => {
    const scripts = document.getElementsByTagName('script');
    for (const script of scripts) {
        if (script.src.includes('_buildManifest.js')) {
            return script.src.split('/')[5];
        }
    }
}

const gameMode = () => {
    if (document.getElementsByClassName(cn('bars_content__')).length < 3) return '';
    const fullText = document.getElementsByClassName(cn('bars_content__'))[2].textContent;
    return fullText.substring(0, fullText.lastIndexOf(' ')).toLowerCase();
}

async function fetchWithCors(url, method, body) {
    return await fetch(url, {
        "headers": {
            "accept": "*/*",
            "accept-language": "en-US,en;q=0.8",
            "content-type": "application/json",
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "same-site",
            "sec-gpc": "1",
            "x-client": "web"
        },
        "referrer": "https://www.geoguessr.com/",
        "referrerPolicy": "strict-origin-when-cross-origin",
        "body": JSON.stringify(body),
        "method": method,
        "mode": "cors",
        "credentials": "same-origin"
    })
}

window.modifySetting = (e, settingName) => {
    let newValue = e.value;
    if (settingName === 'multiplierIncrement') {
        newValue *= 10;
        newValue = Math.round(newValue);
    } else {
        newValue *= 1; // string to number conversion
        newValue = Math.round(newValue);
    }

    // Fetch the game options
    fetchWithCors(`https://www.geoguessr.com/_next/data/${getGameId()}/en/party.json`, "GET")
    .then((response) => response.json())
    .then((data) => {
        let gameOptions = data.pageProps.party.gameSettings;
        gameOptions[settingName] = newValue;
        // Push the updated options
        fetchWithCors(`https://www.geoguessr.com/api/v4/parties/v2/game-settings`, "PUT", gameOptions);
    });

    cachedGameMode = gameMode();
    cachedGameOptions[settingName] = e.value;
}

let optionTextInputInnerHTML = (id, settingName, text, icon, helpText) =>
    `<div class="${cn('numeric-option_wrapper__')} advanced-option-setting"><div class="${cn('numeric-option_icon__')}"><img alt="" loading="lazy" width="48" height="48" decoding="async" data-nimg="1" class="${cn('rule-icons_icon__')}" style="color: transparent;" src="${icon}"></div><div class="${cn('numeric-option_label__')}">${text}</div><div><input type="text" id="${id}" onblur="modifySetting(this, '${settingName}')" style="text-align: center; background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 80px;"></div></div><p>${helpText}</p>`;

function makeCustomTextInput(elt, id, settingName, text, icon, helpText) {
    elt.parentElement.outerHTML = optionTextInputInnerHTML(id, settingName, text, icon, helpText);
    return elt;
}

function updateValue(inputId, option, gameOptions) {
    if (document.querySelector(inputId)) {
        if (gameMode() == cachedGameMode && option in cachedGameOptions) {
            document.querySelector(inputId).value = cachedGameOptions[option];
        } else {
            if (option == 'multiplierIncrement') {
                document.querySelector(inputId).value = gameOptions[option] / 10;
            } else {
                document.querySelector(inputId).value = gameOptions[option];
            }
        }
    }
}

let observer = new MutationObserver(async (mutations) => {
    if (document.querySelector('.advanced-option-setting')) return;

    await scanStyles();

    if (window.location.href.includes('party') && (document.getElementsByClassName(cn('slider-option_slider__')) || document.getElementsByClassName(cn('numeric-option_button__')))) {
        if (gameMode().includes('duels')) {
            let healthEl = document.evaluate('//div[text()="Initial health"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            let multiplierEl = document.evaluate('//div[text()="Multiplier increase"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            let timeAfterGuessEl = document.evaluate('//div[text()="Timer after guess"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            let maxRoundTimeEl = document.evaluate('//div[text()="Max round time"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            if (healthEl) {
                 makeCustomTextInput(healthEl, 'health-input', 'initialHealth', 'Initial health', '/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fheart.3a3fd066.png&amp;w=96&amp;q=75', '');
             }
            if (multiplierEl) {
                makeCustomTextInput(multiplierEl, 'increment-input', 'multiplierIncrement', 'Multiplier increase', '/_next/static/media/multipliers-icon.63803925.svg', '(must be between 0.1 and 10)');
            }
            if (timeAfterGuessEl) {
                makeCustomTextInput(timeAfterGuessEl, 'time-after-guess-input', 'timeAfterGuess', 'Timer after guess', '/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftime-limit.8a68c82e.png&w=96&q=75', '(must be between 10 and 300 seconds)');
            }
            if (maxRoundTimeEl) {
                makeCustomTextInput(maxRoundTimeEl, 'max-round-time-input', 'maxRoundTime', 'Max round time', '/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftime-limit.8a68c82e.png&w=96&q=75', '(0 for no time limit)');
            }
        } else if (gameMode() == 'city streaks') {
            let gameTimeEl = document.evaluate('//div[text()="Game time"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            if (gameTimeEl) {
                makeCustomTextInput(gameTimeEl, 'time-input', 'duration', 'Game time', '/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftime-limit.8a68c82e.png&w=96&q=75', '(must be between 60 and 900 seconds)');
            }
        } else {
            let roundTimeEl = document.evaluate('//div[text()="Round time"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            if (roundTimeEl) {
                let settingName = 'roundTime';
                let helpText = '(must be between 10 and 600 seconds)';
                if (gameMode() == 'bullseye') {
                    settingName = 'bullseyeRoundTime';
                    helpText = '(must be less than 600 seconds, 0 for no time limit)';
                }
                makeCustomTextInput(roundTimeEl, 'time-input', settingName, 'Round time', '/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftime-limit.8a68c82e.png&w=96&q=75', helpText);
            }
        }

        fetchWithCors(`https://www.geoguessr.com/_next/data/${getGameId()}/en/party.json`, "GET")
        .then((response) => response.json())
        .then((data) => {
            let gameOptions = data.pageProps.party.gameSettings;

            if (gameMode() == 'city streaks') {
                updateValue('#time-input', 'duration', gameOptions);
            } else if (gameMode().includes('duels')) {
                updateValue('#health-input', 'initialHealth', gameOptions);
                updateValue('#increment-input', 'multiplierIncrement', gameOptions);
                updateValue('#time-after-guess-input', 'timeAfterGuess', gameOptions);
                updateValue('#max-round-time-input', 'maxRoundTime', gameOptions);
            } else if (gameMode() == 'bullseye') {
                updateValue('#time-input', 'bullseyeRoundTime', gameOptions);
            } else {
                updateValue('#time-input', 'roundTime', gameOptions);
            }
        });
    }
});


observer.observe(document.body, {
  characterDataOldValue: false,
  subtree: true,
  childList: true,
  characterData: false
});

document.addEventListener('keydown', (event) => {
    if (event.key == 'Escape' && document.getElementsByClassName(cn('party-modal_heading__'))) {
        document.activeElement.blur();
    }
});