您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A script that adds a few things to mirror.sgkoi.dev's direct mod list page.
// ==UserScript== // @name tModLoader Mod Browser Mirror Direct Mod List Tools // @namespace http://tampermonkey.net/ // @version 2.7.182818284590.45235 // @description A script that adds a few things to mirror.sgkoi.dev's direct mod list page. // @author An Orbit // @match https://mirror.sgkoi.dev/direct* // @icon https://www.google.com/s2/favicons?sz=64&domain=sgkoi.dev // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; var urlParams = new URLSearchParams(window.location.search); var isDark = urlParams.get("dark") !== null; var css = isDark ? `body { background-color: black; color: white; } input, select, option { background-color: black; border: 1px solid gray; color: white; } button { background-color: #888; border: 1px solid #444; color: white; } td, tbody td, #index td, #index > tbody > tr > td { border: 1px solid #888; } tr, tbody tr, #index tr, #index > tbody > tr { border: unset; } index { border-collapse: collapse; } input[type="checkbox"]:not(:checked) { filter: invert(1); } a { color: aqua; } .file.tmod, .file.pak, .file.jar { //sorry if you wanted to use this to analyze Lobotomy Corporation mods or something background-color: #030; } .file.locpack { background-color: #330; } .file.png { background-color: #003; } .file.zip { background-color: #033; } .file[class~="tar.gz"] { background-color: #032; } .file.txt { background-color: #222; } .file.random { background-color: #313; } .file.invalid-mod { background-color: #380000; text-decoration: line-through; } .file.invalid-mod a { text-decoration: line-through; }` : `.file.tmod, .file.pak, .file.jar { background-color: #dfd; } .file.locpack { background-color: #ffc; } .file.png { background-color: #ddf; } .file.zip { background-color: #dff; } .file[class~="tar.gz"] { background-color: #dfe; } .file.txt { background-color: #dfdfdf; } .file.random { background-color: #fef; } .file.invalid-mod { background-color: #fab; text-decoration: line-through; } .file.invalid-mod a { text-decoration: line-through; }`; var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); head.appendChild(style); style.type = 'text/css'; if (style.styleSheet){ // This is required for IE8 and below. style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } var months = [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; window.parseDate = function(dateStr) { dateStr = dateStr.split("/"); dateStr[2] = dateStr[2].split(" "); dateStr = dateStr.flat(); dateStr[0] = months[parseInt(dateStr[0])]; dateStr[3] = dateStr[3].split(/(\d+|[AP]M)/).filter(function(t) { return (t.length > 0 && t !== ":") }); dateStr[3] = dateStr[3].map(x => x.match(/^[AP]M$/) ? x : parseInt(x)); if(dateStr[3][0] < 12 && dateStr[3][3] == "PM") { dateStr[3][0] += 12; } else if(dateStr[3][0] == 12 && dateStr[3][3] == "AM") { dateStr[3][0] -= 12; }; dateStr[3].splice(3,1); dateStr[3] = dateStr[3].map(x => x.toString().padStart(2,"0")).join(":"); dateStr = `${dateStr[2]} ${dateStr[0]} ${dateStr[1]} ${dateStr[3]}Z+00:00`; return new Date(dateStr); }; var parseDate = window.parseDate; function createDropdown(optionsArray,id,classes) { var dropdown = document.createElement("select"); for(var i in optionsArray) { var option = document.createElement("option"); var data = optionsArray[i]; if(data instanceof Array) { option.innerText = data[0]; option.setAttribute("value",data[1]); } else { option.setAttribute("value",data); option.innerText = data; }; dropdown.appendChild(option) }; if(classes) { if(classes instanceof Array) { for(var j in classes) { dropdown.classList.add(classes[j]) } } else { dropdown.setAttribute("class",classes); }; }; if(id) { dropdown.setAttribute("id",id); }; return dropdown }; window.getValueFromInputById = function(inputId) { var input = document.getElementById(inputId); if(!input) { throw new Error(`Couldn't find any element with ID "${inputId}"`) }; if([null,undefined].includes(input.value)) { throw new Error(`Couldn't find a value in element "${inputId}"`) }; return input.value }; var getValueFromInputById = window.getValueFromInputById; window.entries = []; window.modExtensions = [".tmod"]; //Opened to global scope for easier console-driven analysis of other mod lists window.invalidModArbitraryThreshold = 278; var rows = document.getElementsByTagName("tr"); rows = Array.from(rows).filter(function(node) { return node.nodeType == 1 && node.childNodes.length > 6}); for(var i = 0; i < rows.length; i++) { var row = rows[i]; var fileName = row.childNodes[1].innerText; var fileSize = parseInt(row.childNodes[3].innerText.replaceAll(",","")); var fileDate = parseDate(row.childNodes[5].innerText); var fileType = null; var isInvalidMod = null; var isMod = false; if(fileName.endsWith(".png")) { fileType = "image"; } else if(fileName.endsWith(".locpack")) { fileType = "locpack"; } else { fileType = "other"; }; for(var aa = 0; aa < window.modExtensions.length; aa++) { if(fileName.endsWith(window.modExtensions[aa])) { isMod = true; break //stop checking after the first valid extension } }; if(isMod) { fileType = "mod"; if(fileName.endsWith(".tmod") && fileSize < window.invalidModArbitraryThreshold) { //Only apply arbitrary invalid mod threshold to Terraria mods //for broken mod files of the format "No mod named 'Name' exists.", threshold is 255+23 because idk how to detect them row.classList.add("invalid-mod"); isInvalidMod = true } else { isInvalidMod = false }; row.classList.add("mod"); }; row.classList.add((fileName.match(/\.(tar\.gz|[^\.]*)$/) ?? [null,"no-extension"])[1]); window.entries.push({ name: fileName, size: fileSize, date: fileDate, type: fileType, mod: isMod, invalidMod: isInvalidMod, }); }; var finalSize = window.entries.map(x => x.size).reduce(function(a,b){return a+b},0); window.sortOptions = [ ["No sort", "none"], ["Date (Ascending)", "ascendingDate"], ["Date (Descending)", "descendingDate"], ["Size (Ascending)", "ascendingSize"], ["Size (Descending)", "descendingSize"], ["Name (Z-A)", "ascendingName"], ["Name (A-Z)", "descendingName"] ]; var sortOptions = window.sortOptions; window.filterOptions = [ ["No filter", "none"], ["Mods", "mod"], ["Images", "image"], ["Localization packs", "locpack"], ["Other", "other"] ]; var filterOptions = window.filterOptions; window.sortFunctions = { ascendingDate: function(modEntry1,modEntry2) { return modEntry1.date.getTime() - modEntry2.date.getTime() }, descendingDate: function(modEntry1,modEntry2) { return modEntry2.date.getTime() - modEntry1.date.getTime() }, ascendingSize: function(modEntry1,modEntry2) { return modEntry1.size - modEntry2.size }, descendingSize: function(modEntry1,modEntry2) { return modEntry2.size - modEntry1.size }, ascendingName: function(modEntry1,modEntry2) { return modEntry2.name.localeCompare(modEntry1.name, "en-US") }, descendingName: function(modEntry1,modEntry2) { return modEntry1.name.localeCompare(modEntry2.name, "en-US") } }; var sortFunctions = window.sortFunctions; window._numberInputOnPress = function(event) { if(isNaN(this.value + String.fromCharCode(event.keyCode))) { return false }; }; window._numberInputOnUpdate = function(field) { var text = field.value; if((!text) && (text !== "")) { throw new Error("_numberInputOnUpdate: Could not find value in field") }; var numbersOnly = text.replaceAll(/\D/g,""); field.value = numbersOnly }; window.generateReport = function(entriesIn) { var totalSize = entriesIn.map(x => x.size).reduce(function(a,b){return a+b},0); return `Total items: ${entriesIn.length.toLocaleString()}; Total size: ${totalSize.toLocaleString()} B, Average size: ${(totalSize / entriesIn.length).toLocaleString(undefined, {maximumFractionDigits: 4})} B` }; var generateReport = window.generateReport; var header = document.getElementsByTagName("header")[0]; var report = document.createElement("p"); report.setAttribute("id","reportText"); report.innerText = generateReport(window.entries); header.appendChild(report); var sorter = document.createElement("p"); sorter.setAttribute("id","sortAndFilter"); var filterPicker = createDropdown(filterOptions,"filterPicker"); filterPicker.setAttribute("title","Filter entries"); sorter.appendChild(filterPicker); sorter.appendChild(document.createElement("br")); var sortPicker = createDropdown(sortOptions,"sortPicker"); sortPicker.setAttribute("title","Sort entries"); sorter.appendChild(sortPicker); sorter.appendChild(document.createElement("br")); var textFilter = document.createElement("input"); textFilter.setAttribute("id","textFilter"); textFilter.setAttribute("placeholder","File name filter"); textFilter.setAttribute("title","Filter file names"); sorter.appendChild(textFilter); sorter.appendChild(document.createTextNode(String.fromCharCode(160) + "Regex?: ")); var tfRegexCheck = document.createElement("input"); tfRegexCheck.setAttribute("type","checkbox"); tfRegexCheck.setAttribute("id","textFilterIsRegexCheck"); textFilter.setAttribute("title","Regex?"); sorter.appendChild(tfRegexCheck); sorter.appendChild(document.createTextNode(String.fromCharCode(160) + "Case-insensitive?: ")); var tfCaseCheck = document.createElement("input"); tfCaseCheck.setAttribute("type","checkbox"); tfCaseCheck.setAttribute("id","textFilterIsCiCheck"); tfCaseCheck.setAttribute("title","Case-insensitive?"); tfCaseCheck.setAttribute("checked","true"); sorter.appendChild(tfCaseCheck); sorter.appendChild(document.createElement("br")); var sizeMin = document.createElement("input"); sizeMin.setAttribute("id","sizeMinimum"); sizeMin.setAttribute("placeholder","Minimum size (B)"); sizeMin.setAttribute("title","Filter file names"); sizeMin.setAttribute("type","number"); sizeMin.setAttribute("min","0"); sizeMin.addEventListener("keypress", window._numberInputOnPress); sizeMin.addEventListener("update", window._numberInputOnUpdate); sorter.appendChild(sizeMin); sorter.appendChild(document.createTextNode(String.fromCharCode(160))); var sizeMax = document.createElement("input"); sizeMax.setAttribute("id","sizeMaximum"); sizeMax.setAttribute("placeholder","Maximum size (B)"); sizeMax.setAttribute("title","Filter file names"); sizeMax.setAttribute("type","number"); sizeMax.setAttribute("min","0"); sizeMax.addEventListener("keypress", window._numberInputOnPress); sizeMax.addEventListener("update", window._numberInputOnUpdate); sorter.appendChild(sizeMax); sorter.appendChild(document.createElement("br")); sorter.appendChild(document.createTextNode(" Minimum date:" + String.fromCharCode(160))); var minDateInput = document.createElement("input"); minDateInput.setAttribute("type","date"); minDateInput.setAttribute("id","minDateInput"); minDateInput.setAttribute("title","Files after date"); sorter.appendChild(minDateInput); sorter.appendChild(document.createTextNode(" Maximum date:" + String.fromCharCode(160))); var maxDateInput = document.createElement("input"); maxDateInput.setAttribute("type","date"); maxDateInput.setAttribute("id","maxDateInput"); maxDateInput.setAttribute("title","Files before date"); sorter.appendChild(maxDateInput); sorter.appendChild(document.createElement("br")); sorter.appendChild(document.createTextNode("Include invalid mods?:" + String.fromCharCode(160))); var invalidCheck = document.createElement("input"); invalidCheck.setAttribute("type","checkbox"); invalidCheck.setAttribute("id","includeInvalidModsCheck"); invalidCheck.setAttribute("title","Include invalid mods?"); sorter.appendChild(invalidCheck); sorter.appendChild(document.createElement("br")); var sortBtn = document.createElement("button"); sortBtn.innerText = "Sort/Filter (can be slow)"; sortBtn.addEventListener("click",function() { var sortName = getValueFromInputById("sortPicker"); var filter = getValueFromInputById("filterPicker"); var search = getValueFromInputById("textFilter"); var regexCheck = document.getElementById("textFilterIsRegexCheck"); var useRegex = regexCheck.checked; var caseCheck = document.getElementById("textFilterIsCiCheck"); var caseIns = caseCheck.checked; var fileMinText = getValueFromInputById("sizeMinimum"); var fileSizeMin = fileMinText == "" ? null : Math.max(0,Number(fileMinText.replace(/e+$/,"e0"))); var fileMaxText = getValueFromInputById("sizeMaximum"); var fileSizeMax = fileMaxText == "" ? null : Math.max(0,Number(fileMaxText.replace(/e+$/,"e0"))); var dateMinText = getValueFromInputById("minDateInput"); var minDate = dateMinText == "" ? null : new Date(`${dateMinText} 00:00:00.000Z+00:00`); var dateMaxText = getValueFromInputById("maxDateInput"); var maxDate = dateMaxText == "" ? null : new Date(`${dateMaxText} 23:59:59.999Z+00:00`); var invCheck = document.getElementById("includeInvalidModsCheck"); var includeInvalid = invCheck.checked; var filteredData = []; //occurrence number 4892897578079974329834: FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUCK Array.prototype.filter not allowing me to pass arguments. I run into this problem fucking constantly. if((filter == "none") && (search == "") && includeInvalid) { filteredData = structuredClone(window.entries) } else { for(var i = 0; i < window.entries.length; i++) { if((includeInvalid == false) && (window.entries[i].invalidMod)) { continue }; if(search !== "") { var searchInclude = false; if(useRegex) { var regex = RegExp(search,caseIns ? "i" : ""); searchInclude = regex.test(window.entries[i].name) } else { if(caseIns) { searchInclude = window.entries[i].name.toLowerCase().includes(search.toLowerCase()) } else { searchInclude = window.entries[i].name.includes(search) } }; if(!searchInclude) { continue } }; if(fileSizeMin !== null || fileSizeMax !== null) { if((fileSizeMin !== null && fileSizeMax !== null) && (fileSizeMin > fileSizeMax)) { alert("Minimum cannot be greater than maximum!"); return }; if(fileSizeMin !== null && window.entries[i].size < fileSizeMin) { continue }; if(fileSizeMax !== null && window.entries[i].size > fileSizeMax) { continue } }; if(minDate !== null || maxDate !== null) { if((minDate !== null && maxDate !== null) && (minDate.getTime() > maxDate.getTime())) { alert("Minimum cannot be greater than maximum!"); return }; if(minDate !== null && window.entries[i].date.getTime() < minDate.getTime()) { continue }; if(maxDate !== null && window.entries[i].date.getTime() > maxDate.getTime()) { continue } }; if(filter == "none" || window.entries[i].type == filter) { filteredData.push(window.entries[i]) } } }; var sortFunction = sortFunctions[sortName]; if(sortFunction) { rebuildModList(filteredData.sort(sortFunction)) } else { rebuildModList(filteredData) } report.innerText = generateReport(filteredData); alert("Done") }); sorter.appendChild(sortBtn); header.appendChild(sorter); window.rebuildModList = function(entriesIn) { var body = document.getElementsByTagName("tbody")[0]; body.textContent = ""; for(var i = 0; i < entriesIn.length; i++) { var entry = entriesIn[i]; var newRow = document.createElement("tr"); newRow.classList.add((entry.name.match(/\.(tar\.gz|[^\.]*)$/) ?? [null,"no-extension"])[1]); newRow.classList.add("file"); if(entry.type == "mod" && entry.size < window.invalidModArbitraryThreshold) { newRow.classList.add("invalid-mod"); } var nameCell = document.createElement("td"); nameCell.setAttribute("class","name"); var newLink = document.createElement("a"); newLink.innerText = entry.name; newLink.setAttribute("href","https://mirror.sgkoi.dev/direct/" + entry.name); nameCell.appendChild(newLink); newRow.appendChild(nameCell); var sizeCell = document.createElement("td"); sizeCell.setAttribute("class","length"); sizeCell.innerText = entry.size.toLocaleString(); newRow.appendChild(sizeCell); var dateCell = document.createElement("td"); dateCell.setAttribute("class","modified"); dateCell.innerText = (entry.date.toLocaleString("en-US", { timeZone: 'UTC' }) + " +00:00").replace(", "," ").replace(/ ([AP]M)/,"$1").replace(".000Z","Z"); newRow.appendChild(dateCell); body.appendChild(newRow); } }; var rebuildModList = window.rebuildModList })();