您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically tries to export save to a file every 1+ hours (customizable)
// ==UserScript== // @name More Ore - Enhanced Save // @namespace https://syns.studio/more-ore/ // @version 1.1.2 // @description Automatically tries to export save to a file every 1+ hours (customizable) // @author 123HD123 // @match https://syns.studio/more-ore/ // @icon https://syns.studio/more-ore/misc-tinyRock.22ef93dd.ico // @require https://greasyfork.org/scripts/444840-more-ore-notification/code/More%20Ore%20-%20Notification+.user.js // @grant none // ==/UserScript== (function () { const MOD_NAME = "Enhanced Save"; const MOD_ID = "enhancedSave"; const MOD_STORAGE_DEFAULT = { intervals: [], mutationObservers: [], settings: [{ id: "askBeforeExport", text: "Ask before auto export", value: true, type: "checkbox" }, { id: "autoExportTo", text: "Automatically export to", value: "file", options: ["file", "clipboard"], type: "select" }, { id: "exportInterval", text: "Export save every x hours", value: 1, type: "range" } ] }; window.mods = window.mods || {}; const Logger = NotificationPlus?.notify || console.log; if (Object.keys(window.mods).includes(MOD_NAME)) return Logger( "Warning: Mod named " + MOD_NAME + " has already been loaded", 3 ); NotificationPlus?.load(MOD_NAME); window.mods[MOD_NAME] = window.mods[MOD_NAME] || Object.assign({}, MOD_STORAGE_DEFAULT); const MOD_STORAGE = window.mods[MOD_NAME]; if (MOD_STORAGE.intervals != []) MOD_STORAGE.intervals.forEach(interval => clearInterval(interval.id)); MOD_STORAGE.intervals = []; MOD_STORAGE.settings = JSON.parse(localStorage.getItem(`${MOD_ID}_settings`)) ?? MOD_STORAGE_DEFAULT.settings.slice(); let update = false; for(let setting of MOD_STORAGE_DEFAULT.settings) { if (MOD_STORAGE_DEFAULT.settings.length != MOD_STORAGE.settings.length) { update = true; break; } for(let key of Object.keys(setting)) { if (key == "value") continue; let index = MOD_STORAGE_DEFAULT.settings.indexOf(setting); if (setting[key] != MOD_STORAGE.settings[index][key] && typeof setting[key] != "object") update = true; else if (typeof setting[key] == "object") { for(let i in setting[key]) { if (setting[key][i] != MOD_STORAGE.settings[index][key][i]) { update = true; break; } } } if (update) break; } if (update) break; } if (update) MOD_STORAGE.settings = MOD_STORAGE_DEFAULT.settings.slice(); localStorage.setItem(`${MOD_ID}_settings`, JSON.stringify(MOD_STORAGE.settings)); if (MOD_STORAGE.settings.find(setting => setting.id == "exportInterval").value != -1) MOD_STORAGE.intervals.push(setInterval(requestExportSave, MOD_STORAGE.settings.find(setting => setting.id == "exportInterval")?.value * 60 * 60 * 1000)); MOD_STORAGE.requestExportSave = requestExportSave; if (MOD_STORAGE.mutationObservers != []) MOD_STORAGE.mutationObservers.forEach(mutationObserver => mutationObserver.disconnect()); MOD_STORAGE.mutationObservers = []; let m = new MutationObserver(onSettings); m.observe(document.querySelector(".page-container"), {childList: true}); MOD_STORAGE.mutationObservers.push(m); //window.requestExportSave = requestExportSave; function requestExportSave() { if (MOD_STORAGE.settings.find(setting => setting.id == "askBeforeExport")?.value) { let autoExportTo = MOD_STORAGE.settings.find(setting => setting.id == "autoExportTo")?.value; utils.confirmModal( MOD_NAME, `<p>Do you want to export your save to ${autoExportTo == "file" ? "an external file" : "clipboard"}?<br><span style='font-size: 13px;'>(TIP: the time between auto exports can be modified in the settings)</span></p>`, () => exportSave(true), "Export", "Cancel", 410, true, true); } else exportSave(true); } function exportSave(auto = false) { let autoExportTo = MOD_STORAGE.settings.find(setting => setting.id == "autoExportTo")?.value; document.querySelector(".save-game")?.click(); // Array.from(document.querySelector(".save-row").children).find(child => child.innerText == "save game")?.click(); // Save game let save = localStorage.getItem("s"); // Get save if (auto === true) { if (autoExportTo == "file") { // Download file let a = document.createElement("a"); a.download = `MO - Auto Save ${utils.getTimeFormat()}.txt`; a.href = "data:plain/text;charset=utf-8," + save; a.click(); } else if (autoExportTo == "clipboard") { navigator.clipboard.writeText(save); } } else { // Download file let a = document.createElement("a"); a.download = `MO - Save ${utils.getTimeFormat()}.txt`; a.href = "data:plain/text;charset=utf-8," + save; a.click(); } } function onSettings(mutationList, observer) { for (let mutation of mutationList) { if (mutation.addedNodes.keys().length == 0) return; let node = mutation.addedNodes.item(0); if (!node?.className?.toLowerCase() == "modal-wrapper") return; // Insert elements if (node?.querySelector(".modal-settings")) { // Update volume let volumeBar = Array.from(document.querySelectorAll(".setting-section")).find(setting => setting.children[0]?.innerText == "Volume").children[2]; window.volume = volumeBar.value; volumeBar.addEventListener("change", (event) => volume = event.target.value); insertSettings(); modifyExportSave(); } else if (node?.querySelector(".modal-importSave")) { insertUploadSave(node); } } } function insertSettings() { let settings = document.querySelector(".modal-settings"); if (!settings) return; if (Array.from(document.querySelectorAll(".setting-section")).find(child => child.children[0]?.innerText == MOD_NAME)) return; let settingSection = document.createElement("div"); settingSection.classList.add("setting-section"); let settingTitle = document.createElement("h3"); settingTitle.innerText = MOD_NAME; settingSection.append(settingTitle); for (let setting of MOD_STORAGE.settings) { let settingRow = document.createElement("div"); settingRow.classList.add("row"); settingRow.innerHTML = `<p>${setting.text}</p><input type='${setting.type}'/>`; switch(setting.type) { case "range": { let text = setting.text.replace(" x ", ` ${setting.value} `); if (setting.value == -1) text = "Auto export is disabled"; if (setting.value == 1) text = setting.text.split(" ").slice(0, setting.text.split(" ").length-2).join(" ") + " hour"; settingRow.innerHTML = `<p>${text}</p>`; break; } case "checkbox": { settingRow.children[1].checked = setting.value; break; } case "select": { settingRow.innerHTML = `<p>${setting.text}</p><select>${ setting.options.map(option => `<option value="${option}">${option}</option>`).join("") }</select>`; break; } } if (settingRow.children[1]) settingRow.children[1].onchange = e => { switch(setting.type) { case "checkbox": setting.value = e.target.checked; break; default: setting.value = e.target.value; }; localStorage.setItem(`${MOD_ID}_settings`, JSON.stringify(MOD_STORAGE.settings)); }; settingSection.append(settingRow); if (setting.type == "range") { let settingSlider = document.createElement("input"); settingSlider.type = "range"; settingSlider.min = "-1"; settingSlider.max = "12"; settingSlider.step = "1"; settingSlider.value = setting.value == .5 ? 0 : setting.value; settingSlider.style.width = "100%"; settingSlider.onchange = _ => { setting.value = parseInt(settingSlider.value) || .5; let text = setting.text.replace(" x ", ` ${setting.value} `); if (setting.value == -1) text = "Auto export is disabled"; if (setting.value == 1) text = setting.text.split(" ").slice(0, setting.text.split(" ").length-2).join(" ") + " hour"; settingRow.innerHTML = `<p>${text}</p>`; localStorage.setItem(`${MOD_ID}_settings`, JSON.stringify(MOD_STORAGE.settings)); clearInterval(MOD_STORAGE.intervals[0]); MOD_STORAGE.intervals = []; if (setting.value == -1) return; MOD_STORAGE.intervals.push(setInterval(requestExportSave, setting.value * 60 * 60 * 1000)); }; settingSection.append(settingSlider); } } settings.querySelector(".modal-content").insertBefore(settingSection, Array.from(settings.querySelector(".modal-content").children).find(section => section.querySelector("h3")?.innerText == "Saves")); } function modifyExportSave() { let savesSection = Array.from(document.querySelectorAll(".setting-section").values()) .find(section => section.children[0].innerText == "Saves"); let exportFile = savesSection.children[1].children[0]; let exportClipboard = savesSection.children[2].children[0]; exportFile.innerHTML = "export (file)"; exportFile.onclick = exportSave; exportClipboard.innerHTML = "export (clipboard)"; exportClipboard.onclick = () => { document.querySelector(".save-game")?.click(); //Array.from(document.querySelector(".save-row").children).find(child => child.innerText == "save game")?.click(); // Save game navigator.clipboard.writeText(localStorage.getItem("s")); }; /*a.onclick = () => utils.choiceModal( MOD_NAME, "<p>Would you like to export to <b>file</b> or <b>clipboard</b>?</p>", {text: "Clipboard", callback: () => { Array.from(document.querySelector(".save-row").children).find(child => child.innerText == "save game")?.click(); // Save game navigator.clipboard.writeText(localStorage.getItem("s")); }}, {text: "File", callback: exportSave}, 410, true);*/ } function insertUploadSave(node) { if (node.querySelector("#file")) return; let button = document.createElement("button"); button.className = "modal-action"; button.style.padding = "0"; let label = document.createElement("label"); label.htmlFor = "file"; label.style.cursor = "pointer"; label.style.padding = "2px 8px"; label.innerText = "Import File"; button.append(label); let file = document.createElement("input"); file.id = "file"; file.type = "file"; file.name = "file"; file.className = "modal-action"; file.style.display = "none"; file.addEventListener('change', function() { var fr = new FileReader(); fr.onload = function() { node.querySelector('.modal-content > textarea').innerText = fr.result; } fr.readAsText(this.files[0]); }); document.querySelector(".modal-importSave > .modal-actions").insertBefore(file, document.querySelector(".modal-importSave > .modal-actions").children[1]); document.querySelector(".modal-importSave > .modal-actions").insertBefore(button, file); } const utils = { confirmModal: function (title, innerHTML, onConfirm) { //this.closeTopmostModal(); var confirmMessage = arguments.length > 3 && void 0 !== arguments[3] ? arguments[3] : 'Confirm', cancelMessage = arguments.length > 4 && void 0 !== arguments[4] ? arguments[4] : 'Cancel', customWidth = arguments.length > 5 ? arguments[5] : void 0, center = arguments.length > 6 ? arguments[6] : void 0, nonBlocking = arguments.length > 7 ? arguments[7] : void 0; let wrapper = this.createEl('div', ['modal-wrapper']); if (nonBlocking) { wrapper.style.margin = "auto"; wrapper.style.width = "fit-content"; wrapper.style.height = "fit-content"; wrapper.style.background = "transparent"; } wrapper.addEventListener('click', this.closeTopmostModal); var modal = this.createEl('div', [ 'modal' ]); customWidth && (modal.style.width = customWidth + 'px'); center && (modal.style.textAlign = 'center'); var titleElement = this.createEl('h2', ['modal-title'], title); modal.append(titleElement); var contentElement = this.createEl('div', ['modal-content']); contentElement.innerHTML = innerHTML; modal.append(contentElement); var actionsContainer = this.createEl('div', ['modal-actions']), confirmAction = this.createEl('button', ['modal-action'], confirmMessage); confirmAction.onclick = function () { this.closeTopmostModal(); onConfirm(); }.bind(this); var cancelAction = this.createEl('button', ['modal-action'], cancelMessage); cancelAction.onclick = function () { this.closeTopmostModal(); }.bind(this); actionsContainer.append(cancelAction, confirmAction); modal.append(actionsContainer); wrapper.append(modal); document.querySelector(".page-container").append(wrapper); }, choiceModal: function (title, innerHTML, leftButton, rightButton) { //this.closeTopmostModal(); var leftMessage = leftButton.text, rightMessage = rightButton.text, customWidth = arguments.length > 4 ? arguments[4] : void 0, center = arguments.length > 5 ? arguments[5] : void 0; let wrapper = this.createEl('div', ['modal-wrapper']); wrapper.addEventListener('click', this.closeTopmostModal); var modal = this.createEl('div', [ 'modal' ]); customWidth && (modal.style.width = customWidth + 'px'); center && (modal.style.textAlign = 'center'); var titleElement = this.createEl('h2', ['modal-title'], title); modal.append(titleElement); var contentElement = this.createEl('div', ['modal-content']); contentElement.innerHTML = innerHTML; modal.append(contentElement); var actionsContainer = this.createEl('div', ['modal-actions']), leftAction = this.createEl('button', ['modal-action'], leftMessage); leftAction.onclick = function () { this.closeTopmostModal(); leftButton.callback(); }.bind(this); var rightAction = this.createEl('button', ['modal-action'], rightMessage); rightAction.onclick = function () { this.closeTopmostModal(); rightButton.callback(); }.bind(this); actionsContainer.append(rightAction, leftAction); modal.append(actionsContainer); wrapper.append(modal); document.querySelector(".page-container").append(wrapper); }, createEl: function (tag) { var classes = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : [], innerHTML = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : '', element = document.createElement(tag); return ( classes.forEach(function (clazz) { return element.classList.add(clazz) }), (element.innerHTML = innerHTML + ''), element ); }, getCodeName: function (name) { return name .replace(/(?:^\w|[A-Z]|\b\w)/g, function (match, p1) { return 0 === p1 ? match.toLowerCase() : match.toUpperCase() }) .replace(/\s+/g, '') .replace(/\./g, ''); }, getTimeFormat: function() { let d = new Date(); let dateString = d.toLocaleDateString().replaceAll("/", "-"); let hours = d.getHours().toString().padStart(2, '0'); let minutes = d.getMinutes().toString().padStart(2, '0'); let seconds = d.getSeconds().toString().padStart(2, '0'); return `${dateString}-${hours}.${minutes}.${seconds}`; }, closeTopmostModal: function (event) { var targetElement; if (event) { var targetEl = event.target; targetElement = targetEl.classList.contains('modal-wrapper') ? targetEl : null; } else { var allWrappers = document.querySelectorAll('.modal-wrapper'); allWrappers.length > 0 && (targetElement = allWrappers[allWrappers.length - 1]); } if (targetElement) { targetElement.style.pointerEvents = 'none'; new Howl({ src: ['./sounds/misc.wav'], volume: .1 * volume }).play(); var modal = targetElement.children[0]; modal.style.animation = 'fadeOutDown .15s ease-out forwards'; modal.addEventListener('animationend', () => targetElement.parentNode.removeChild(targetElement)); } } }; })();