Pavlov mod.io maps integration with pavlovrcon.com

Add extra buttons to mod.io pavlov map pages to allow switching to the map or adding it to the rotation of a server by using pavlovrcon.com

2023-06-20 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

// ==UserScript==
// @name         Pavlov mod.io maps integration with pavlovrcon.com
// @namespace    https://greasyfork.org/en/users/1103172-underpl
// @version      2.1
// @description  Add extra buttons to mod.io pavlov map pages to allow switching to the map or adding it to the rotation of a server by using pavlovrcon.com
// @author       UnderPL
// @license      CC BY-NC 4.0
// @match        https://mod.io/g/pavlov
// @match        https://mod.io/g/pavlov/m/*
// @match        https://mod.io/g/pavlov?_sort=*
// @match        https://mod.io/g/pavlov?tags-in=*
// @match        https://mod.io/g/pavlov?platforms*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function() {
    'use strict';
    var currentPage = window.location.href;

    var defaultGameMode = "INFECTION";
    var storedGameMode = GM_getValue("storedGameMode", defaultGameMode);
    GM_setValue("storedGameMode", storedGameMode);

    var defaultServerId = "0";
    var storedServerId = GM_getValue("storedServerId", defaultServerId);
    GM_setValue("storedServerId", storedServerId);

    const mapsPageRegex = new RegExp("^https:\/\/mod\.io\/g\/pavlov(?:$|\\?tags-in=|\\?_sort=|\\?platforms)");

    //To use with HTTP proxy that forward the request to the server 
    var localRcon = false;
    var localServerIp = "192.168.0.150"
    var localServerPort = "3000"

    function createButton(id, color, text, targetButton) {
        var newButton = targetButton.cloneNode(true);
        newButton.id = id;
        newButton.querySelector('span div span').textContent = text;
        newButton.style.cssText = 'border-color: ' + color + ';margin-bottom: 10px;--primary-hover:' + color;

        newButton.addEventListener('mouseover', function() {
            newButton.querySelector('span div span').style.color = color;
        });
        newButton.addEventListener('mouseout', function() {
            newButton.querySelector('span div span').style.color = 'inherit';
        });

        return newButton;
    };

    function confirmAction(mapName, mapId, selectedGameMode, action) {
        var confirmationMessage = `Are you sure you want to ${(action)} "${mapName}" to the map rotation?\n
        MapRotation=(MapId="${mapId}",GameMode="${selectedGameMode}")`;
        return window.confirm(confirmationMessage);
    }

    function createSelectsAndLabels() {
        var container = document.createElement('div');
        container.style.cssText = 'justify-content:center;' + (currentPage.startsWith('https://mod.io/g/pavlov/m/') ? 'display:flex;' : "");

        var serverIdDiv = document.createElement('div');
        var serverIdLabel = document.createElement('label');
        serverIdLabel.innerHTML = 'SERVER ID:';
        serverIdLabel.style.cssText = 'font-size: 150%; margin: 0px 10px;';
        serverIdDiv.appendChild(serverIdLabel);

        var serverIdSelect = document.createElement('select');
        serverIdSelect.id = "serverIds";
        serverIdSelect.classList.add("form-select");
        serverIdSelect.style.cssText = "color: black; font-size: 155%; padding-left: 5px; text-align: center; display: inline-block; margin: 0 auto;";
        serverIdDiv.appendChild(serverIdSelect);

        serverIdSelect.innerHTML = Array.from({length: 10}, (_, i) => i)
        .map(id => `<option value="${id}" ${storedServerId == id ? 'selected' : ''}>${id}</option>`).join('');

        serverIdSelect.addEventListener('change', function() {
            GM_setValue("storedServerId", serverIdSelect.value);
        });

        var gameModeDiv = document.createElement('div');
        var gameModeLabel = document.createElement('label');
        gameModeLabel.innerHTML = 'MODE:';
        gameModeLabel.style.cssText = 'font-size: 150%; margin: 0px 10px;';
        gameModeDiv.appendChild(gameModeLabel);

        var gameModeSelect = document.createElement('select');
        gameModeSelect.id = "gameModes";
        gameModeSelect.classList.add("form-select");
        gameModeSelect.style.cssText = "color: black; font-size: 150%; padding-left: 5px; text-align: center; display: inline-block; margin: 0 auto;";
        gameModeDiv.appendChild(gameModeSelect);

        gameModeSelect.innerHTML = ['SND', 'TDM', 'DM', 'GUN', 'ZWV', 'WW2GUN', 'TANKTDM', 'KOTH', 'TTT', 'OITC', 'INFECTION', 'HIDE', 'PUSH', 'PH', 'CUSTOM']
        .map(mode => `<option value="${mode}" ${storedGameMode === mode ? 'selected' : ''}>${mode}</option>`).join('');

        gameModeSelect.addEventListener('change', function() {
            storedGameMode = gameModeSelect.value;
            GM_setValue("storedGameMode", storedGameMode);
        });

        container.appendChild(serverIdDiv);
        container.appendChild(gameModeDiv);

        return container;
    };

    const headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0",
        "Accept": "*/*",
        "Accept-Language": "en-US,en;q=0.5",
        "Content-Type": "application/json"
    }

    function mapAction(mapId, selectedGameMode, action) {
        var body = JSON.stringify({
            data: selectedGameMode,
            "Map Name": "UGC" + mapId,
            uid: null
        });

        var requestUrl;
        if (localRcon) {
            requestUrl = `http://${localServerIp}:${localServerPort}/rcon?command=${action} UGC${mapId} ${selectedGameMode}`;
        } else {
            requestUrl = `https://pavlovrcon.com/api/${action}/${storedServerId}`;
        }

        console.log(
            '%cRequest Data:\n' +
            '----------------------\n' +
            '%cAction: ' + '%c' + action + '\n' +
            '----------------------\n' +
            '%cAPI Endpoint: ' + '%c' + `https://pavlovrcon.com/api/${action}/${GM_getValue("storedServerId", selectElementContainer.querySelectorAll('select')[0].value)}` + '\n' +
            '----------------------\n' +
            '%cResponse Body:\n' + '%c' + JSON.stringify(JSON.parse(body), null, 2),
            'color: blue; font-weight: bold;',
            'color: black; font-weight: bold;',
            'color: green;',
            'color: black; font-weight: bold;',
            'color: green;',
            'color: black; font-weight: bold;',
            'color: red;'
        );

        GM_xmlhttpRequest({
            method: localRcon ? "GET" : "POST",
            url: requestUrl,
            headers: headers,
            data: body,
            onload: function(response) {}
        });
    }

    function addButtonsWithEventListeners(mapId, subscribeButton, specificMapPage = false) {
        var playButton = createButton("playButton", "green", "Play", subscribeButton);
        playButton.style.marginBottom = "10px";
        playButton.style.marginTop = "8px";

        var mapRotationButtonsContainer = document.createElement('div');
        mapRotationButtonsContainer.style.cssText = 'display: flex; justify-content: space-between;';

        var addToMapRotationButton = createButton("addToMapRotationButton", "blue", "Add to map rotation", subscribeButton);
        var removeFromMapRotationButton = createButton("removeFromMapRotationButton", "blue", "Remove from map rotation", addToMapRotationButton);
        addToMapRotationButton.style.width = "49%";
        addToMapRotationButton.querySelector('span div span').style.fontSize = "75%";
        removeFromMapRotationButton.style.width = "49%";
        removeFromMapRotationButton.querySelector('span div span').style.fontSize = "75%";

        mapRotationButtonsContainer.appendChild(addToMapRotationButton);
        mapRotationButtonsContainer.appendChild(removeFromMapRotationButton);

        subscribeButton.parentNode.insertBefore(playButton, subscribeButton);
        subscribeButton.parentNode.insertBefore(mapRotationButtonsContainer, playButton.nextElementSibling);

        if(specificMapPage){
            addSelectElement(playButton, 'before');
        }

        playButton.addEventListener('click', function(e) {
            mapAction(mapId, GM_getValue("storedGameMode", selectElementContainer.querySelectorAll('select')[1].value), "switch_map");
        });

        var mapName;
        addToMapRotationButton.addEventListener('click', function(e) {
            specificMapPage ? mapName = document.querySelector('.tw-util-truncate-two-lines.tw-font-bold').textContent : mapName = subscribeButton.parentNode.parentNode.parentNode.querySelectorAll('a > div')[1].textContent;

            if (confirmAction(mapName, mapId, GM_getValue("storedGameMode", selectElementContainer.querySelectorAll('select')[1].value), "add")) {
                mapAction(mapId, GM_getValue("storedGameMode", selectElementContainer.querySelectorAll('select')[1].value), "addmaprotation");
            }
        });
        removeFromMapRotationButton.addEventListener('click', function(e) {
            specificMapPage ? mapName = document.querySelector('.tw-util-truncate-two-lines.tw-font-bold').textContent : mapName = subscribeButton.parentNode.parentNode.parentNode.querySelectorAll('a > div')[1].textContent;

            if (confirmAction(mapName, mapId, GM_getValue("storedGameMode", selectElementContainer.querySelectorAll('select')[1].value), "remove")) {
                mapAction(mapId, GM_getValue("storedGameMode", selectElementContainer.querySelectorAll('select')[1].value), "removemaprotation");
            }
        });
    }

    let addedMaps = new Set();

    function addButtonsToMapsPage() {
        const divs = document.querySelectorAll('div.tw-bg-center.tw-bg-cover.tw-w-full.tw-h-full[role="img"]');
        const subscribeButtons = document.querySelectorAll('button.tw-button-transition.tw-outline-none.tw-shrink-0.tw-items-center.tw-justify-center.tw-space-x-2.tw-font-bold.tw-bg-theme-1--hover.tw-text-md[id^="input"]');

        for(let i = 0; i < divs.length; i++) {
            const altText = divs[i].getAttribute('alt');
            const style = divs[i].getAttribute('style');
            const urlMatch = style.match(/url\("([^"]+)"\)/);

            if(urlMatch) {
                const url = urlMatch[1];
                const mapIdMatch = url.match(/\/(\d+)\//);

                if(mapIdMatch) {
                    const mapId = mapIdMatch[1];
                    if (addedMaps.has(mapId)) {
                        continue;
                    }
                    addButtonsWithEventListeners(mapId, subscribeButtons[i]);

                    addedMaps.add(mapId);
                } else {
                    console.log('Map ID not found');
                }
            } else {
                console.log('URL match not found');
            }
        }
    };

    function addControlsToSpecificMapPage() {
        var subscribeButton = document.querySelector('button.tw-button-transition.tw-outline-none.tw-shrink-0.tw-items-center.tw-justify-center.tw-space-x-2.tw-font-bold.tw-bg-theme-1--hover.tw-text-md[id^="input"]');
        if (!subscribeButton || document.getElementById('playButton') || document.getElementById('addToMapRotationButton')) {
            return;
        }

        var mapIdElement = document.querySelector('div.tw-justify-between.tw-items-center.tw-flex:last-child > span.tw-whitespace-nowrap:last-child > span');
        var mapId = mapIdElement.textContent;

        addButtonsWithEventListeners(mapId, subscribeButton, true);
    };

    function adjustElementStylesForMapsPage() {
        const elements = document.querySelectorAll('.tw-flex.tw-items-center.tw-flex-col.tw-absolute.tw-bottom-3.lg\\:tw-bottom-4.tw-inset-x-3.lg\\:tw-inset-x-4');

        elements.forEach((element) => {
            element.classList.remove('tw-absolute');
        });

        const elementsTwo = document.querySelectorAll('.tw-px-3.lg\\:tw-px-4.tw-pb-14.lg\\:tw-pb-\\[3\\.75rem\\]');
        elementsTwo.forEach((element) => {
            element.classList.remove('tw-pb-14');
            element.classList.remove('lg:tw-pb-[3.75rem]');
            element.classList.add('tw-pb-4');
        });
    }

    function addSelectElement(targetElement, position) {
        position === "before" ? targetElement.before(selectElementContainer) : targetElement.after(selectElementContainer);
    }

    var selectElementContainer = createSelectsAndLabels();

    if (mapsPageRegex.test(currentPage)) {
        var targetElementSelector = 'md:tw-rounded-lg md:dark:tw-bg-dark-1 md:tw-bg-light-1 tw-border-opacity-40 tw-border-grey md:tw-border-0 tw-border-b md:tw-mb-2 tw-relative';
        var targetElement = document.getElementsByClassName(targetElementSelector)[1]
        addSelectElement(targetElement, 'after');
        var observerForAllMapsPage = new MutationObserver(function(mutationsList) {
            for (var mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    addButtonsToMapsPage();
                    adjustElementStylesForMapsPage();
                    break;
                }
            }
        });
        observerForAllMapsPage.observe(document.body, { childList: true, subtree: true });
    } else {
        var observerForSpecificMapPage = new MutationObserver(function(mutationsList) {
            for (var mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    addControlsToSpecificMapPage();
                    break;
                }
            }
        });
        observerForSpecificMapPage.observe(document.body, { childList: true, subtree: true });
    }
})();