// ==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 });
}
})();