您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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
// ==UserScript== // @name Pavlov mod.io maps integration with pavlovrcon.com // @namespace https://greasyfork.org/en/users/1103172-underpl // @version 3.0 // @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 // @require https://greasyfork.org/scripts/426194-toast-js/code/toastjs.js?version=971661 // @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); var defaultServerId = "0"; var storedServerId = GM_getValue("storedServerId", defaultServerId); var mapList = {}; var storedMapList = GM_getValue("storedMapList", mapList); const mapsPageRegex = new RegExp("^https:\/\/mod\.io\/g\/pavlov(?:$|\\?tags-in=|\\?_sort=|\\?platforms)"); //To use with an HTTP server/proxy that can forward the request to the server var httpProxy = false; var httpProxyIp = "192.168.0.150" var httpProxyPort = "3000" 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" } var retrievedMapList = false; function retrieveMapList() { GM_xmlhttpRequest({ method: "GET", url: httpProxy ? `http://${httpProxyIp}:${httpProxyPort}/rcon?command=maps` : `https://pavlovrcon.com/api/maplist/${storedServerId}`, headers: headers, onload: function(response) { if (response.status >= 200 && response.status < 400) { var data = httpProxy ? JSON.parse(response.responseText) : response.responseText; var mapListData = httpProxy ? data.MapList : JSON.parse(data); mapListData.forEach(function(item) { let mapId = httpProxy ? item.MapId : item.id; let gameMode = httpProxy ? item.GameMode : item.game_mode; if (mapId.startsWith("UGC")) { mapId = mapId.substring(3); if(!mapList[mapId]) { mapList[mapId] = []; } mapList[mapId].push(gameMode); } }); retrievedMapList = true; } else { console.log("Command failed with status: " + response.status); } }, onerror: function() { console.log("Request failed"); } }); } 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 === 'addmaprotation' ? 'add' : 'remove'} "${mapName}" ${action === 'addmaprotation' ? 'to' : 'from'} 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: 155%; 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; }; function mapAction(mapId, selectedGameMode, action, callback) { var body = JSON.stringify({ data: selectedGameMode, "Map Name": "UGC" + mapId, uid: null }); var requestUrl; if (httpProxy) { requestUrl = `http://${httpProxyIp}:${httpProxyPort}/rcon?command=${action} UGC${mapId} ${selectedGameMode}`; } else { requestUrl = `https://pavlovrcon.com/api/${action}/${storedServerId}`; } GM_xmlhttpRequest({ method: httpProxy ? "GET" : "POST", url: httpProxy ? `http://${httpProxyIp}:${httpProxyPort}/rcon?command=${action} UGC${mapId} ${selectedGameMode}` : `https://pavlovrcon.com/api/${action}/${storedServerId}`, headers: headers, data: body, onload: function(response) { if (response.status >= 200 && response.status < 400) { cocoMessage.success(1500, "Command sent successfully", () => { }); if(callback) { callback(true); } } else { cocoMessage.error(1500, "Command failed with status: " + response.status, () => { }); if(callback) { callback(false); } } } }); } 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) { var mapName; var targetDiv; if(specificMapPage) { mapName = document.querySelector('.tw-util-truncate-two-lines.tw-font-bold').textContent; targetDiv = document.querySelector('.tw-util-ratio-16-9.tw-relative.tw-w-full.tw-global--border-radius.tw-bg-theme-1.tw-overflow-hidden'); } else { mapName = subscribeButton.parentNode.parentNode.parentNode.querySelectorAll('a > div')[1].textContent; targetDiv = subscribeButton.parentNode.parentNode.parentNode; } var gameMode = GM_getValue("storedGameMode", selectElementContainer.querySelectorAll('select')[1].value); if (confirmAction(mapName, mapId, gameMode, "add")) { mapAction(mapId, gameMode, "addmaprotation"); addOverlayAndSpan(targetDiv, gameMode, mapId); } }); removeFromMapRotationButton.addEventListener('click', function(e) { var mapName; var targetDiv if(specificMapPage) { mapName = document.querySelector('.tw-util-truncate-two-lines.tw-font-bold').textContent; targetDiv = document.querySelector('.tw-util-ratio-16-9.tw-relative.tw-w-full.tw-global--border-radius.tw-bg-theme-1.tw-overflow-hidden'); } else { mapName = subscribeButton.parentNode.parentNode.parentNode.querySelectorAll('a > div')[1].textContent; targetDiv = subscribeButton.parentNode.parentNode.parentNode; } var gameMode = GM_getValue("storedGameMode", selectElementContainer.querySelectorAll('select')[1].value); if (confirmAction(mapName, mapId, gameMode, "remove")) { mapAction(mapId, gameMode, "removemaprotation"); var overlayToRemove = targetDiv.querySelector(`#o-${mapId}-${gameMode}`); if(overlayToRemove) { overlayToRemove.remove(); } } }); } function extractMapIdFromDiv(divsContainingMapId) { const mapIds = []; for (let i = 0; i < divsContainingMapId.length; i++) { const style = divsContainingMapId[i].getAttribute('style'); const urlMatch = style.match(/url\("([^"]+)"\)/); if (urlMatch) { const url = urlMatch[1]; const mapIdMatch = url.match(/\/(\d+)\//); if (mapIdMatch) { mapIds.push(mapIdMatch[1]); } } } return mapIds; } let addedMaps = new Set(); function processMapDivs(divsContainingMapId) { 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"]'); const mapIds = extractMapIdFromDiv(divsContainingMapId); for (let i = 0; i < mapIds.length; i++) { const mapId = mapIds[i]; if (addedMaps.has(mapId)) { continue; } addButtonsWithEventListeners(mapId, subscribeButtons[i]); addedMaps.add(mapId); } } 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); addGameModeInServerRotationTags(observerForSpecificMapPage, null, mapId); }; function addOverlayAndSpan(targetDiv, gameMode, mapId) { var tagsDiv = targetDiv.querySelector('.tags-div'); if (!tagsDiv) { tagsDiv = document.createElement('div'); tagsDiv.className = 'tags-div tw-flex tw-space-x-1 tw-items-center'; tagsDiv.style.cssText = 'flex-direction:column;align-items: flex-end; position: absolute; right: 0;'; targetDiv.appendChild(tagsDiv); } var overlayDiv = document.createElement('div'); overlayDiv.id = `o-${mapId}-${gameMode}`; overlayDiv.style = 'width: 100%; height: 100%; z-index: 1; text-align: right; margin-bottom:1px;'; overlayDiv.onclick = e => e.preventDefault(); var spanElement = document.createElement('span'); spanElement.id = `t-${mapId}-${gameMode}`; spanElement.className = `ttw-px-2 tw-z-2 tw-bg-primary tw-rounded-full tw-text-primary-text tw-leading-snug tw-max-w-full`; spanElement.style.cssText = 'background-color: blue; border:2px solid red; padding:0px 4px'; var pElement = document.createElement('p'); pElement.className = 'tw-util-truncate-one-line tw-w-full'; pElement.style = 'display:inline;'; pElement.textContent = `${gameMode}`; spanElement.appendChild(pElement); overlayDiv.appendChild(spanElement); tagsDiv.appendChild(overlayDiv); } function addGameModeInServerRotationTags(observer, divsContainingMapId, mapId = null) { observer.disconnect(); var mapIds = mapId ? [mapId] : extractMapIdFromDiv(divsContainingMapId); var targetDivs = mapId ? document.querySelectorAll('.tw-util-ratio-16-9.tw-relative.tw-w-full.tw-global--border-radius.tw-bg-theme-1.tw-overflow-hidden') : document.querySelectorAll('a .tw-relative.tw-util-ratio-16-9'); let mapGameModeCounters = {}; for(let i = 0; i < mapIds.length; i++) { var mapId = mapIds[i]; if(mapList[mapId] && targetDivs[i]) { var tagsDiv = targetDivs[i].querySelector('.tags-div'); if(!tagsDiv) { tagsDiv = document.createElement('div'); tagsDiv.className = 'tags-div tw-flex tw-space-x-1 tw-items-center'; tagsDiv.style.cssText = 'flex-direction:column;align-items: flex-end; position: absolute; right: 0;'; targetDivs[i].appendChild(tagsDiv); } mapList[mapId].forEach((gameMode) => { let currentMapId = mapId; let currentGameMode = gameMode; const mapGameModeKey = `${mapId}-${gameMode}`; if(!mapGameModeCounters[mapGameModeKey]){ mapGameModeCounters[mapGameModeKey] = 0; } var overlayDiv = document.createElement('div'); overlayDiv.id = `o-${mapId}-${gameMode}-${mapGameModeCounters[mapGameModeKey]}`; overlayDiv.style = 'width: 100%; height: 100%; z-index: 1; text-align: right;'; overlayDiv.onclick = e => e.preventDefault(); const spanId = `t-${mapId}-${gameMode}-${mapGameModeCounters[mapGameModeKey]++}`; if (!tagsDiv.querySelector(`#${spanId}`)) { var spanElement = document.createElement('span'); spanElement.id = spanId; spanElement.className = `ttw-px-2 tw-z-2 tw-bg-primary tw-rounded-full tw-text-primary-text tw-leading-snug tw-max-w-full`; spanElement.style.cssText = 'background-color: blue; border:2px solid red; padding:0px 4px'; var pElement = document.createElement('p'); pElement.className = 'tw-util-truncate-one-line tw-w-full'; pElement.style = 'display:inline;'; pElement.textContent = `${gameMode}`; spanElement.appendChild(pElement); overlayDiv.appendChild(spanElement); tagsDiv.appendChild(overlayDiv); spanElement.addEventListener('click', function(e) { e.preventDefault(); if (confirmAction(currentGameMode, currentMapId, gameMode, "removemaprotation")) { mapAction(currentMapId, gameMode, "removemaprotation", function(success) { if (success) { let index = mapList[currentMapId].indexOf(currentGameMode); if (index !== -1) { mapList[currentMapId].splice(index, 1); } document.querySelector(`#${overlayDiv.id}`).remove(); } }); } }); } }); } } observer.observe(document.body, { childList: true, subtree: 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); } function findTargetElementAndAddSelectElement(targetElementSelector, position) { var checkExist = setInterval(function() { var targetElement = document.getElementsByClassName(targetElementSelector)[1] if (targetElement) { addSelectElement(targetElement, position); clearInterval(checkExist); } }, 500); } var selectElementContainer = createSelectsAndLabels(); retrieveMapList(); 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] findTargetElementAndAddSelectElement(targetElementSelector, 'after'); var observerForAllMapsPage = new MutationObserver(function(mutationsList) { const divsContainingMapId = document.querySelectorAll('div.tw-bg-center.tw-bg-cover.tw-w-full.tw-h-full[role="img"]'); for (var mutation of mutationsList) { if (mutation.type === 'childList') { processMapDivs(divsContainingMapId); adjustElementStylesForMapsPage(); addGameModeInServerRotationTags(observerForAllMapsPage, divsContainingMapId) 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 }); } })();