您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Edit the XML files in the web version of Town of Salem
// ==UserScript== // @name Town of Salem XML Editor // @namespace https://kahoot-win.com // @version 1.2.4 // @icon https://blankmediagames.com/TownOfSalem/favicon.ico // @description Edit the XML files in the web version of Town of Salem // @author theusaf // @copyright 2021, Daniel Lau (https://github.com/theusaf/town-of-salem-edit-xml) // @match https://blankmediagames.com/TownOfSalem/ // @match https://www.blankmediagames.com/TownOfSalem/ // @grant none // @run-at document-start // @license MIT // ==/UserScript== /* global TOSXML_Data */ if (window.TOSXML_Loaded || window.parent.TOSXML_Loaded) { throw "[TOSXML] - Already loaded"; } if (!localStorage.TOSXML_Replacements){ localStorage.TOSXML_Replacements = "{}"; } if(window.location.hostname === "blankmediagames.com") { window.location.hostname = "www.blankmediagames.com"; throw "[TOSXML] - Redirecting to www.blankmediagames.com"; } document.write("[TOSXML] - Patching Town of Salem. Please wait. If this screen stays blank for long periods of time, this userscript may not be working properly. Disable it and send a report to theusaf."); const mainPage = new XMLHttpRequest(); mainPage.open("GET",location.href); mainPage.send(); mainPage.onload = function(){ let {responseText} = mainPage; const [scriptURL] = responseText.match(/Build\/.*?\.js/m), scriptRequest = new XMLHttpRequest(); responseText = responseText.replace(" src=\"" + scriptURL + "\"",""); scriptRequest.open("GET",scriptURL); scriptRequest.send(); scriptRequest.onload = function(){ let {responseText:scriptText} = scriptRequest; const code = (data)=>{ function sanitize(data){ return data.replace(/</mg,"<").replace(/>/mg,">"); } if(data.target.result && data.target.result.url && data.target.result.url.match(/TownOfSalem\/Unity\/WebAssets.+?\/XMLData\/StringTable.+?\.xml/)){ // modify! const encoder = new TextEncoder(), decoder = new TextDecoder("utf8"), XMLParser = new DOMParser, TOSXML_Replacements = JSON.parse(localStorage.TOSXML_Replacements), warn = []; let XMLText = decoder.decode(data.target.result.xhr.response); const XMLData = XMLParser.parseFromString(XMLText,"text/xml").documentElement; if(!XMLData.querySelector("[key=\"TOSXML_EDITED\"]")) { console.warn("[TOSXML] - Saving new original to local storage"); const thing = document.createElementNS("TOSXML", "Entry"); thing.setAttribute("key", "TOSXML_EDITED"); thing.innerHTML = "TRUE"; XMLData.append(thing); const originalData = XMLData.outerHTML; localStorage.TOSXML_OriginalData = originalData; } else if(localStorage.TOSXML_OriginalData) { console.warn("[TOSXML] - Loading original xml from local storage"); // cached the edited version, restore defaults from localStorage XMLData.innerHTML = localStorage.TOSXML_OriginalData .replace(/^<StringTable .*>/,"") .replace(/<\/StringTable>$/m,""); XMLText = XMLData.outerHTML; } else { console.error("[TOSXML] - Edited XML Detected, but no local version found. Perhaps localStorage was cleared?") } try{ window.parent.TOSXML_Data = { edited: XMLData, original: XMLParser.parseFromString(XMLText,"text/xml").documentElement, warn }; }catch(e){ // meh } if(TOSXML_Replacements){ // start modifying for(const i in TOSXML_Replacements){ const item = XMLData.querySelector(`[key="${TOSXML_Replacements[i].key}"]`); if(!item){ warn.push({ key: TOSXML_Replacements[i].key, reason: "Key Missing" }); continue; } try { item.innerHTML = TOSXML_Replacements[i].value; } catch(err) { item.innerHTML = sanitize(TOSXML_Replacements[i].value); } } data.target.result.xhr.response = encoder.encode(XMLData.outerHTML).buffer; } } }, [replaceText] = scriptText.match(/[a-z]\.target\.result\)}\)/m), [replaceLetter] = replaceText.match(/[a-z]/); scriptText = scriptText.replace(replaceText,`(()=>{ (${code.toString()})(${replaceLetter}); return ${replaceLetter}.target.result})() )})`); responseText = responseText.replace("<script></script>",`<script>${scriptText}</script>`); document.open(); document.write(`<style> body{ margin: 0; } iframe{ border: 0; width: 100%; height: 100%; } </style> <iframe src="about:blank"></iframe>`); document.close(); window.stop(); const doc = document.querySelector("iframe"); doc.contentDocument.write(responseText); document.title = "Town of Salem"; const settingsDiv = document.createElement("div"); settingsDiv.id = "TOSXML_Main"; settingsDiv.innerHTML = `<style> #TOSXML_Main{ position: fixed; bottom: 0; left: 0; background: rgba(0,0,0,0.7); color: white; padding: 0.5rem; border-radius: 0.5rem; } #TOSXML_Main:hover{ background: black; } #TOSXML_Hide, #TOSXML_Export, #TOSXML_Import{ position: fixed; right: 1rem; font-size: 2rem; } #TOSXML_Hide{ top: 1rem; } #TOSXML_Export{ top: 4rem; } #TOSXML_Import{ top: 7rem; } #TOSXML_EditWarnings{ flex: 0.5; } details>div{ display: flex; } details>div>div{ flex: 1; overflow: auto; border-radius: 0.5rem; border: solid #666 0.25rem; height: calc(100vh - 6rem); } details>div>div>div>div{ padding: 0.25rem; } details>div>div>div>div:nth-child(2n){ background: #444; } details>div>div>div>div:nth-child(2n+1){ background: #222; } code{ background: black; border-radius: 0.5rem; padding: 0.25rem; line-height: 1.5rem; } </style> <details> <summary>TOSXML 1.2.4 @theusaf</summary> <p>Here, you can edit keys. However, changes will only take effect on reload. <strong>Also, your changes do get cached, so you may need to clear your cache to restore original text.</strong></p> <button id="TOSXML_Hide" title="Closes the editor until you reload the page.">Close</button> <button id="TOSXML_Export" title="Generates an xml file">Export</button> <button id="TOSXML_Import" title="Loads an xml file">Import</button> <div id="TOSXML_Container"> <div id="TOSXML_AllKeys"> <span>All Keys</span> <input id="TOSXML_Search" placeholder="Search"> <div></div> </div> <div id="TOSXML_SavedEdits"> <span>Your Edits</span> <div></div> <button>New Edit</button> </div> <div id="TOSXML_EditWarnings"> <span>Errors/Warnings</span> <div></div> </div> </div> </details>`; document.body.append(settingsDiv); function sanitize(data){ return data.replace(/</mg,"<").replace(/>/mg,">"); } const awaiter = setInterval(()=>{ if(typeof TOSXML_Data !== "undefined"){ clearInterval(awaiter); const {original,warn} = TOSXML_Data, edited = JSON.parse(localStorage.TOSXML_Replacements), itemsAll = document.querySelector("#TOSXML_AllKeys>div"), itemsEdit = document.querySelector("#TOSXML_SavedEdits>div"), warnings = document.querySelector("#TOSXML_EditWarnings>div"), addButton = document.querySelector("#TOSXML_SavedEdits>button"); addButton.onclick = ()=>{ itemsEdit.append(newEdit()); }; for(let i = 0; i < original.children.length; i++){ const item = original.children[i], e = document.createElement("div"); e.innerHTML = `<code class="TOSXML_key">${item.getAttribute("key")}</code> - <code class="TOSXML_value">${sanitize(item.textContent)}</code>`; itemsAll.append(e); } for(const i in edited){ itemsEdit.append(newEdit(edited[i])); } for(let i = 0; i < warn.length; i++){ const item = warn[i], e = document.createElement("div"); e.innerHTML = `<code class="TOSXML_key">${item.key}</code> - <code class="TOSXML_value">${item.reason}</code>`; warnings.append(e); } } },500); function newEdit(info){ const e = document.createElement("div"), v = document.createElement("input"), d = document.createElement("button"), s = document.createElement("button"); let k; s.innerHTML = "✔️"; d.innerHTML = "X"; v.placeholder = "Value"; function save(){ const data = JSON.parse(localStorage.TOSXML_Replacements); if(k.nodeName === "INPUT"){ const n = document.createElement("code"); n.innerHTML = k.value; k.replaceWith(n); k = n; } data[k.value || k.textContent] = { value: v.value, key: k.value || k.textContent }; localStorage.TOSXML_Replacements = JSON.stringify(data); } function del(){ e.outerHTML = ""; const data = JSON.parse(localStorage.TOSXML_Replacements); delete data[k.value || k.textContent]; localStorage.TOSXML_Replacements = JSON.stringify(data); } if(info){ k = document.createElement("code"); k.textContent = info.key; v.value = info.value; }else{ k = document.createElement("input"); k.placeholder = "Key"; } d.onclick = del; s.onclick = save; e.append(k,v,s,d); return e; } const hideButton = document.querySelector("#TOSXML_Hide"), searchInput = document.querySelector("#TOSXML_Search"), exportButton = document.querySelector("#TOSXML_Export"), importButton = document.querySelector("#TOSXML_Import"); let searchTimeout; hideButton.onclick = function(){ settingsDiv.style.display = "none"; delete window.TOSXML_Data; }; searchInput.oninput = function(){ clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const all = document.querySelectorAll("#TOSXML_AllKeys > div > div"), values = searchInput.value.split(" "); for(let i = 0; i < all.length; i++){ const string = all[i].textContent.toLowerCase(); let shouldHide = false; all[i].style.display = ""; for(let j = 0; j < values.length; j++){ const test = values[j].toLowerCase(); if(string.indexOf(test) === -1){ shouldHide = true; break; } } if(shouldHide){ all[i].style.display = "none"; } } }, 500); }; exportButton.onclick = function() { const link = document.createElement("a"); link.setAttribute("download", "tos-xml-export.xml"); const XMLParser = new DOMParser, original = XMLParser.parseFromString(TOSXML_Data.original.outerHTML,"text/xml").documentElement, TOSXML_Replacements = JSON.parse(localStorage.TOSXML_Replacements); if(TOSXML_Replacements){ // start modifying for(const i in TOSXML_Replacements){ const item = original.querySelector(`[key="${TOSXML_Replacements[i].key}"]`); if(!item){ continue; } item.innerHTML = TOSXML_Replacements[i].value; } } const blob = new Blob([original.outerHTML], {type: "application/xml"}), url = URL.createObjectURL(blob); link.href = url; link.style.display = "none"; document.body.append(link); link.click(); link.remove(); URL.revokeObjectURL(url); }; importButton.onclick = function() { const input = document.createElement("input"); input.type = "file"; input.onchange = function() { const file = input.files[0]; file.text().then((text) => { const XMLParser = new DOMParser, XMLData = XMLParser.parseFromString(text,"text/xml").documentElement, TOSXML_Replacements = {}, original = XMLParser.parseFromString(TOSXML_Data.original.outerHTML,"text/xml").documentElement, keys = XMLData.querySelectorAll("Entry"); for(let i = 0; i < keys.length; i++) { const key = keys[i].getAttribute("key"), item = original.querySelector(`[key="${key}"]`); if(item && (item.innerHTML === keys[i].innerHTML || item.getAttribute("xmlns"))) { continue; } TOSXML_Replacements[key] = { key, value: keys[i].innerHTML }; } localStorage.TOSXML_Replacements = JSON.stringify(TOSXML_Replacements); alert("Imported XML File Successfully! Changes will be applied after reloading."); input.remove(); const itemsAll = document.querySelector("#TOSXML_AllKeys>div"), itemsEdit = document.querySelector("#TOSXML_SavedEdits>div"); itemsAll.innerHTML = ""; itemsEdit.innerHTML = ""; for(let i = 0; i < original.children.length; i++){ const item = original.children[i], e = document.createElement("div"); e.innerHTML = `<code class="TOSXML_key">${item.getAttribute("key")}</code> - <code class="TOSXML_value">${sanitize(item.textContent)}</code>`; itemsAll.append(e); } for(const i in TOSXML_Replacements){ itemsEdit.append(newEdit(TOSXML_Replacements[i])); } }); }; input.style.display = "none"; document.body.append(input); input.click(); }; }; };