├── LICENSE ├── README.md ├── banhammer.user.js ├── blockmod.user.js ├── cfqol.user.js ├── images ├── allfiles.png ├── defaultusertab.png ├── fortcraft.png ├── modpackdownload.png ├── modstab.png ├── moduleundisabled.png ├── nocountdown.png ├── nocountdown2.png ├── pagination.png └── searchbar.png ├── oss.user.js └── redir.user.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 comp500 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Curseforge Userscripts 2 | Some useful userscripts for curseforge.com. Feel free to PR or create issues for features you want added. They work well with [Curse Dark by its_meow](https://itsmeow.dev/cursedark/). If you want your own userscript to be listed here, submit an issue! 3 | 4 | ## Install 5 | To install these userscripts, you will need a supported userscript manager for your browser. I highly recommend Tampermonkey, which is available for all popular browsers: 6 | 7 | - [Chrome](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) 8 | - [Firefox](https://addons.mozilla.org/firefox/addon/tampermonkey/) 9 | - [Safari](http://tampermonkey.net/?browser=safari) 10 | - [Microsoft Edge](https://www.microsoft.com/store/p/tampermonkey/9nblggh5162s) 11 | - [Opera](https://addons.opera.com/extensions/details/tampermonkey-beta/) 12 | 13 | Once you have installed a userscript manager, click the following links to install userscripts from this repository: 14 | 15 | - [Curseforge Quality Of Life Fixes](https://github.com/comp500/Curseforge-Userscripts/raw/master/cfqol.user.js) 16 | - [Redirect Old Links](https://github.com/comp500/Curseforge-Userscripts/raw/master/redir.user.js) 17 | 18 | They can also be installed from [Greasy Fork](https://greasyfork.org/en/users/331451-comp500). 19 | 20 | ## Curseforge Quality Of Life Fixes 21 | This userscript fixes a few annoying problems with Curseforge. If you have any suggestions, please submit them as issues or [ping me on Discord](https://discord.mcmoddev.com/)! 22 | 23 | ### Features 24 | - Adds a search bar to the top navigation bar that searches mods. 25 | 26 | ![Search bar](images/searchbar.png) 27 | - Readds modpack download buttons, because they removed them. Useful for people using alternative launchers. 28 | 29 | ![Modpack download button](images/modpackdownload.png) 30 | - Adds a tab to mod and modpack pages that allows all the files to be viewed, rather than just the recent files - normally hidden behind a "View All" button 31 | 32 | ![All Files tab](images/allfiles.png) 33 | - Adds pagination for dependency lists to the bottom of the page, so you don't have to scroll up! 34 | 35 | ![Pagination](images/pagination.png) 36 | - Changes the default action of the Minecraft logo on the homepage and the games list to the Mods tab of Minecraft 37 | 38 | ![Mods tab](images/modstab.png) 39 | - Automatically follows the 5 second download redirect for you, so you don't have to wait. Works best with version-specific download buttons. 40 | 41 | ![Say No to the Countdown](images/nocountdown.png) 42 | - Automatically follows the "You are about to leave Curseforge" message for you, so you don't have to wait. 43 | 44 | ![Say No to the Countdown 2 electric boogaloo](images/nocountdown2.png) 45 | - Changes the default user tab to "Projects", because most of the time that's what you actually want to see. 46 | 47 | ![Projects tab](images/defaultusertab.png) 48 | - Sorts the "Related Projects" list on the file details page. 49 | - Changes the default Dependency Type in the Relations tab to "Required Dependency" 50 | 51 | ## Redirect Old Links 52 | When Curseforge redesigned their site, they moved mods from minecraft.curseforge.com to www.curseforge.com/minecraft. It doesn't seem to redirect the old links to the new ones any more, so this userscript will do it for you. For some reason the new site also doesn't support viewing mods by Addon ID anymore, so this script will resolve the IDs if they exist in the old links. 53 | 54 | ![Module disabled??!!](images/moduleundisabled.png) 55 | -------------------------------------------------------------------------------- /banhammer.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name No More MCreator Mods 3 | // @version 0.10 4 | // @description you weren't gonna use them anyway 5 | // @author comp500 6 | // @namespace https://infra.link/ 7 | // @match https://www.curseforge.com/minecraft/* 8 | // @match https://www.curseforge.com/Minecraft/* 9 | // @match https://legacy.curseforge.com/minecraft/* 10 | // @match https://legacy.curseforge.com/Minecraft/* 11 | // @updateURL https://github.com/comp500/Curseforge-Userscripts/raw/master/banhammer.user.js 12 | // @downloadURL https://github.com/comp500/Curseforge-Userscripts/raw/master/banhammer.user.js 13 | // @homepageURL https://github.com/comp500/Curseforge-Userscripts/ 14 | // @supportURL https://github.com/comp500/Curseforge-Userscripts/issues/ 15 | // @source https://github.com/comp500/Curseforge-Userscripts/ 16 | // @run-at document-end 17 | // @grant GM_xmlhttpRequest 18 | // @connect addons-ecs.forgesvc.net 19 | // @connect edge.forgecdn.net 20 | // @connect media.forgecdn.net 21 | // @require https://unpkg.com/jszip@3.2.2/dist/jszip.min.js 22 | // ==/UserScript== 23 | 24 | (function() { 25 | "use strict"; 26 | 27 | if (!JSZip) { 28 | console.log("NoMoreMCreatorMods: JSZip did not load properly!"); 29 | return; 30 | } 31 | 32 | let storage = {}; 33 | if (localStorage.NoMoreMCreatorMods != null) { 34 | try { 35 | storage = JSON.parse(localStorage.NoMoreMCreatorMods); 36 | } catch (e) { 37 | console.error(e); 38 | storage = {}; 39 | } 40 | } 41 | 42 | let modRegex = /\/minecraft\/mc-mods\/([a-z][\da-z\-_]{0,127})$/; 43 | let mCreatorDescRegex = /
[\w\W]*MCreator[\w\W]*?<\/div>/i; 44 | let openSourceRegex = /Source<\/svg>/; 45 | // haha nice try 46 | let hahaSourceRegex = /\s*\s*Source/i; 47 | let projectIDRegex = /Project ID<\/span>\s*(\d+)<\/span>/; 48 | 49 | let greaseMonkeyXHR = details => { 50 | details.method = details.method ? details.method : "GET"; 51 | details.anonymous = true; 52 | details.responseType = details.responseType ? details.responseType : "arraybuffer"; 53 | return new Promise((resolve, reject) => { 54 | details.onload = resolve; 55 | details.onerror = reject; 56 | GM_xmlhttpRequest(details); 57 | }); 58 | }; 59 | 60 | Promise.all( 61 | Array.from(document.querySelectorAll(".project-listing-row")).map(async row => { 62 | let link = Array.from(row.getElementsByTagName("a")).find(a => modRegex.test(a.href)); 63 | if (link != undefined) { 64 | let stored = (link.href 65 | .replace("https://legacy.curseforge.com/minecraft/mc-mods/", "") 66 | .replace("https://legacy.curseforge.com/Minecraft/mc-mods/", "") 67 | .replace("https://www.curseforge.com/minecraft/mc-mods/", "") 68 | .replace("https://www.curseforge.com/Minecraft/mc-mods/", "")); 69 | if (storage[stored] == "mcreator") { 70 | row.parentNode.removeChild(row); 71 | return null; 72 | } else if (storage[stored] == "normal" || stored == "thanos-skin") { 73 | return null; 74 | } 75 | let res = await fetch(link.href); 76 | if (res.status !== 200) { 77 | console.error("NoMoreMCreatorMods: failed to get mod page " + link.href); 78 | console.log(res); 79 | return null; 80 | } 81 | 82 | let text = await res.text(); 83 | // Exclude mods with MCreator in the description 84 | if (mCreatorDescRegex.test(text)) { 85 | storage[stored] = "mcreator"; 86 | row.parentNode.removeChild(row); 87 | return null; 88 | } 89 | // Allow OSS mods 90 | if (openSourceRegex.test(text) && !hahaSourceRegex.test(text)) { 91 | storage[stored] = "normal"; 92 | return null; 93 | } 94 | 95 | let projectIDMatches = projectIDRegex.exec(text); 96 | if (projectIDMatches != null && projectIDMatches.length > 0) { 97 | return [parseInt(projectIDMatches[1], 10), row, stored]; 98 | } 99 | } 100 | }) 101 | ) 102 | .then(async ids => { 103 | let validIDs = ids.filter(id => id != null); 104 | if (validIDs.length > 0) { 105 | // Request the mod data from CurseForge's API 106 | let res = await greaseMonkeyXHR({ 107 | url: "https://addons-ecs.forgesvc.net/api/v2/addon/", 108 | method: "POST", 109 | data: JSON.stringify(validIDs.map(i => i[0])), 110 | responseType: "json", 111 | headers: { 112 | Accept: "application/json", 113 | "Content-Type": "application/json" 114 | } 115 | }); 116 | if (res.status != 200) { 117 | console.error("NoMoreMCreatorMods: failed to get addon info: " + res.status + " " + res.statusText); 118 | return; 119 | } 120 | if (res.response == null) { 121 | console.error("NoMoreMCreatorMods: failed to get addon info: null response"); 122 | return; 123 | } 124 | await Promise.all( 125 | res.response.map(async mod => { 126 | if (mod.latestFiles == null) { 127 | console.error("NoMoreMCreatorMods: failed to get addon info: null latestFiles"); 128 | return; 129 | } 130 | if (mod.latestFiles.length > 0) { 131 | // Download the mod 132 | let url = mod.latestFiles[0].downloadUrl; 133 | if (url == null) { 134 | console.error("NoMoreMCreatorMods: failed to get addon info: null download url"); 135 | return; 136 | } 137 | let downloadRes = await greaseMonkeyXHR({ url: url }); 138 | if (downloadRes.status != 200) { 139 | console.error("NoMoreMCreatorMods: failed to get mod download: " + res.status); 140 | return; 141 | } 142 | let zip = await JSZip.loadAsync(downloadRes.response); 143 | let row = validIDs.find(i => i[0] == mod.id)[1]; 144 | let stored = validIDs.find(i => i[0] == mod.id)[2]; 145 | // Check for a folder called net/mcreator 146 | if (zip.files["net/mcreator/"] != null || zip.files["mod/mcreator/"] != null) { 147 | storage[stored] = "mcreator"; 148 | row.parentNode.removeChild(row); 149 | return; 150 | } else { 151 | storage[stored] = "normal"; 152 | return; 153 | } 154 | } 155 | }) 156 | ); 157 | } 158 | }) 159 | .then(a => { 160 | localStorage.NoMoreMCreatorMods = JSON.stringify(storage); 161 | }) 162 | .catch(e => { 163 | console.error(e); 164 | }); 165 | })(); 166 | -------------------------------------------------------------------------------- /blockmod.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Block mod button 3 | // @version 0.2 4 | // @description Hides selected mods from Curseforge 5 | // @author comp500 6 | // @namespace https://infra.link/ 7 | // @match https://www.curseforge.com/minecraft/* 8 | // @match https://www.curseforge.com/Minecraft/* 9 | // @match https://legacy.curseforge.com/minecraft/* 10 | // @match https://legacy.curseforge.com/Minecraft/* 11 | // @updateURL https://github.com/comp500/Curseforge-Userscripts/raw/master/blockmod.user.js 12 | // @downloadURL https://github.com/comp500/Curseforge-Userscripts/raw/master/blockmod.user.js 13 | // @homepageURL https://github.com/comp500/Curseforge-Userscripts/ 14 | // @supportURL https://github.com/comp500/Curseforge-Userscripts/issues/ 15 | // @source https://github.com/comp500/Curseforge-Userscripts/ 16 | // @grant GM_registerMenuCommand 17 | // ==/UserScript== 18 | 19 | (function() { 20 | "use strict"; 21 | 22 | let storage = {}; 23 | if (localStorage.BlockMod != null) { 24 | try { 25 | storage = JSON.parse(localStorage.BlockMod); 26 | } catch (e) { 27 | console.error(e); 28 | storage = {}; 29 | } 30 | } 31 | 32 | let modRegex = /\/minecraft\/mc-mods\/([a-z][\da-z\-_]{0,127})$/; 33 | 34 | GM_registerMenuCommand("List blocked mods", () => { 35 | alert(Object.keys(storage).join(", ")); 36 | }); 37 | 38 | GM_registerMenuCommand("Clear blocked mods", () => { 39 | storage = {}; 40 | localStorage.BlockMod = JSON.stringify(storage); 41 | location.reload(); 42 | }); 43 | 44 | Array.from(document.querySelectorAll(".project-listing-row")).map(row => { 45 | for (let link of Array.from(row.getElementsByTagName("a")).filter(a => modRegex.test(a.href))) { 46 | let stored = (link.href 47 | .replace("https://legacy.curseforge.com/minecraft/mc-mods/", "") 48 | .replace("https://legacy.curseforge.com/Minecraft/mc-mods/", "") 49 | .replace("https://www.curseforge.com/minecraft/mc-mods/", "") 50 | .replace("https://www.curseforge.com/Minecraft/mc-mods/", "")); 51 | if (storage[stored] == "block") { 52 | if (row.parentNode != null) { 53 | row.parentNode.removeChild(row); 54 | break; 55 | } 56 | } else { 57 | if (link.querySelector(".text-lg") == null && link.querySelector(".font-bold") == null) { 58 | continue; 59 | } 60 | let blockLink = document.createElement("a"); 61 | blockLink.href = "#"; 62 | blockLink.innerText = "Block"; 63 | blockLink.style.paddingLeft = "5px"; 64 | blockLink.addEventListener("click", e => { 65 | storage[stored] = "block"; 66 | localStorage.BlockMod = JSON.stringify(storage); 67 | e.preventDefault(); 68 | row.parentNode.removeChild(row); 69 | return false; 70 | }, false); 71 | link.addEventListener("mouseenter", () => { 72 | link.parentNode.insertBefore(blockLink, link.nextSibling); 73 | }, false); 74 | let timeout = -1; 75 | link.addEventListener("mouseleave", () => { 76 | if (timeout > -1) { 77 | window.clearTimeout(timeout); 78 | } 79 | timeout = window.setTimeout(() => { 80 | link.parentNode.removeChild(blockLink); 81 | }, 1000); 82 | }, false); 83 | } 84 | } 85 | }); 86 | })(); 87 | -------------------------------------------------------------------------------- /cfqol.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Curseforge QOL Fixes 3 | // @version 0.27 4 | // @description Various Quality of Life improvements to the Curseforge website 5 | // @author comp500 6 | // @namespace https://infra.link/ 7 | // @match https://www.curseforge.com/* 8 | // @match https://legacy.curseforge.com/* 9 | // @updateURL https://github.com/comp500/Curseforge-Userscripts/raw/master/cfqol.user.js 10 | // @downloadURL https://github.com/comp500/Curseforge-Userscripts/raw/master/cfqol.user.js 11 | // @homepageURL https://github.com/comp500/Curseforge-Userscripts/ 12 | // @supportURL https://github.com/comp500/Curseforge-Userscripts/issues/ 13 | // @source https://github.com/comp500/Curseforge-Userscripts/ 14 | // @run-at document-end 15 | // @grant none 16 | // ==/UserScript== 17 | 18 | (function () { 19 | "use strict"; 20 | 21 | // Add a search box 22 | let searchBoxContainer = document.createElement("div"); 23 | searchBoxContainer.className = "flex mr-4 items-center"; 24 | // Get the current assets path 25 | let styleSheet = Array.from(document.styleSheets).find((s) => /\/Content\/([\d\-]+)\//.test(s.href)); 26 | let assetsPath = styleSheet == null ? "2-0-7179-35052" : /\/Content\/([\d\-]+)\//.exec(styleSheet.href)[1]; 27 | searchBoxContainer.innerHTML = `
28 |
29 |
30 | 31 | 32 | 33 | 34 |
35 |
`; 36 | let insertLocation = document.querySelector(".curseforge-header .ml-auto > div"); 37 | if (insertLocation != null && insertLocation.firstChild != null) { 38 | // @Inject(method = "the navbar", at = @At("HEAD")) 39 | insertLocation.insertBefore(searchBoxContainer, insertLocation.firstChild); 40 | 41 | // Make the search box magically grow 42 | let searchBox = searchBoxContainer.querySelector("#cfqolTopbarSearch"); 43 | if (searchBox != null) { 44 | // Fix stupid flexboxes - set to flex-grow 1 flex-shrink 0 45 | searchBoxContainer.style.flex = "1 0"; 46 | searchBoxContainer.parentNode.parentNode.style.flex = "1 0"; 47 | 48 | let navLinksContainer = document.querySelector(".top-nav__nav-link").parentNode; 49 | navLinksContainer.style.transition = "opacity 0.4s, max-width 0.3s"; 50 | searchBox.addEventListener("focus", (e) => { 51 | navLinksContainer.style.opacity = 0; 52 | navLinksContainer.style.maxWidth = "0"; 53 | }); 54 | searchBox.addEventListener("blur", (e) => { 55 | navLinksContainer.style.opacity = 1; 56 | navLinksContainer.style.maxWidth = "2000px"; 57 | }); 58 | // Make the search icon focus the search box 59 | let searchIcon = searchBoxContainer.querySelector(".search"); 60 | if (searchIcon != null) { 61 | searchIcon.addEventListener("click", (e) => { 62 | searchBox.focus(); 63 | }); 64 | } 65 | } 66 | } 67 | 68 | // Add an "All Files" tab for all curseforge projects 69 | let projectPathMatches = /^\/([\w-]+)\/([\w-]+)\/([a-z][\da-z-_]{0,127})/.exec(document.location.pathname); 70 | let filesTab = document.getElementById("nav-files"); 71 | if (projectPathMatches != null && projectPathMatches.length == 4 && filesTab != null) { 72 | let gameName = projectPathMatches[1]; 73 | let projectCategory = projectPathMatches[2]; 74 | let projectSlug = projectPathMatches[3]; 75 | 76 | let projectAllFiles = document.createElement("li"); 77 | let isAllFilesPage = /\/files\/all$/.test(document.location.pathname); 78 | if (isAllFilesPage) { 79 | projectAllFiles.className = 80 | "border-b-2 border-primary-500 b-list-item p-nav-item px-2 pb-1/10 -mb-1/10 text-gray-500"; 81 | filesTab.className = "b-list-item p-nav-item px-2 pb-1/10 -mb-1/10 text-gray-500"; 82 | filesTab.getElementsByTagName("a")[0].className = "text-gray-500 hover:no-underline"; 83 | } else { 84 | projectAllFiles.className = "b-list-item p-nav-item px-2 pb-1/10 -mb-1/10 text-gray-500"; 85 | } 86 | projectAllFiles.innerHTML = ` 89 | 90 | All Files 91 | 92 | `; 93 | filesTab.parentNode.insertBefore(projectAllFiles, filesTab.nextSibling); 94 | } 95 | 96 | // Add pagination to the bottom of the page in dependency lists 97 | let dependenciesPage = document.querySelector(".project-dependencies-page > div"); 98 | if (dependenciesPage != null) { 99 | let paginationTop = document.querySelector(".project-dependencies-page > div .pagination-top"); 100 | if (paginationTop != null) { 101 | dependenciesPage.appendChild(paginationTop.parentNode.cloneNode(true)).classList.remove("mb-4"); 102 | } 103 | } 104 | 105 | // Skip download countdowns 106 | let downloadScript = Array.from(document.scripts).find( 107 | (s) => s.innerText != null && s.innerText.includes("PublicProjectDownload.countdown") 108 | ); 109 | if (downloadScript != null && downloadScript.innerText != null) { 110 | let matches = downloadScript.innerText.match(/countdown\("(.+)"\)/); 111 | if (matches != null && matches[1] != null) { 112 | // Break the existing script 113 | let countdownEl = document.querySelector("span[data-countdown-seconds]"); 114 | if (countdownEl != null) { 115 | // UNSAFE if grant != none! For some reason jQuery stores data in itself rather than attrs?! 116 | jQuery.removeData(countdownEl, "countdown-seconds"); 117 | } 118 | 119 | let downloadText = document.querySelector("p[data-countdown-timer]"); 120 | if (downloadText != null) { 121 | downloadText.innerText = "Downloading now..."; 122 | } 123 | 124 | window.location.href = matches[1]; 125 | } 126 | } 127 | 128 | /** 129 | * Link redirections 130 | */ 131 | 132 | const linkList = Array.from(document.getElementsByTagName("a")); 133 | const regexDownloadLink = /^https:\/\/(www|legacy).curseforge.com\/.*\/download\/\d+$/; 134 | 135 | const redirections = [ 136 | // Better method for skipping, if links contain file ID already 137 | [regexDownloadLink, a => { 138 | a.href = a.href + "/file"; 139 | }], 140 | // Change the default Minecraft tab (from other links) to /minecraft/mc-mods 141 | [/^http:\/\/bit.ly\/2Lzpfsl|https:\/\/(www|legacy).curseforge.com\/minecraft\/?$/, a => { 142 | a.path = "/minecraft/mc-mods"; 143 | }], 144 | // Change the default member tab to projects 145 | [/^https:\/\/(www|legacy).curseforge.com\/members\/[^\/]+\/?$/, a => { 146 | a.href = a.href + (a.href.endsWith("/") ? "" : "/") + "projects"; 147 | }], 148 | // Redirect linkout URLs 149 | [/^https:\/\/(www|legacy).curseforge.com\/linkout/, a => { 150 | let url = new URL(a.href); 151 | a.href = decodeURIComponent(url.searchParams.get("remoteUrl")); 152 | }], 153 | // Change the default dependency type to "Include" for Minecraft modpacks 154 | [/^https:\/\/(www|legacy).curseforge.com\/minecraft\/modpacks\/([a-z][\da-z-_]{0,127})\/relations\/dependencies\/?$/, a => { 155 | a.href = a.href + "?filter-related-dependencies=6"; 156 | }], 157 | // Change the default dependency type to "Required Dependency" 158 | [/^https:\/\/(www|legacy).curseforge.com\/([\w-]+)\/([\w-]+)\/([a-z][\da-z-_]{0,127})\/relations\/dependencies\/?$/, a => { 159 | a.href = a.href + "?filter-related-dependencies=3"; 160 | }], 161 | // Change the default dependent type to "Required Dependency" 162 | [/^https:\/\/(www|legacy).curseforge.com\/([\w-]+)\/([\w-]+)\/([a-z][\da-z-_]{0,127})\/relations\/dependents\/?$/, a => { 163 | a.href = a.href + "?filter-related-dependents=3"; 164 | }] 165 | ]; 166 | 167 | for (let link of linkList) { 168 | for (let redir of redirections) { 169 | if (redir[0].test(link.href)) { 170 | redir[1](link); 171 | break; 172 | } 173 | } 174 | } 175 | 176 | /** 177 | * Readd download buttons for modpacks 178 | */ 179 | 180 | // All Files list 181 | Array.from(document.querySelectorAll("table.listing a.button")) 182 | .filter(l => l.pathname.startsWith("/minecraft/modpacks") && l.href.endsWith("?client=y")) 183 | .map(link => { 184 | let newHref = link.href.slice(0, -9); 185 | if (regexDownloadLink.test(newHref)) { 186 | newHref = newHref + "/file"; 187 | } 188 | 189 | let newLink = link.cloneNode(true); 190 | newLink.href = newHref; 191 | newLink.classList.add("button--icon-only"); 192 | newLink.classList.add("mr-2"); 193 | 194 | newLink.innerHTML = ` 195 | 196 | 197 | 198 | `; 199 | 200 | if (link.parentNode != null) { 201 | link.parentNode.insertBefore(newLink, link); 202 | } 203 | }); 204 | 205 | // Main File button, Page header button 206 | Array.from(document.querySelectorAll("article a.button, header a.button")) 207 | .filter((l) => l.pathname.startsWith("/minecraft/modpacks") && l.href.endsWith("?client=y")) 208 | .map((link) => { 209 | let newHref = link.href.slice(0, -9); 210 | if (regexDownloadLink.test(newHref)) { 211 | newHref = newHref + "/file"; 212 | } 213 | 214 | if (link.parentNode.parentNode.childElementCount >= 2) { 215 | // For some reason, direct file pages now have it, but not the main files page? 216 | return; 217 | } 218 | 219 | let newButton = link.parentNode.cloneNode(true); 220 | newButton.classList.remove("ml-2"); 221 | let newLink = newButton.querySelector("a.button"); 222 | newLink.classList.add("button--hollow"); 223 | newLink.href = newHref; 224 | 225 | newLink.innerHTML = ` 226 | 227 | 228 | Download 229 | `; 230 | link.parentNode.parentNode.insertBefore(newButton, link.parentNode); 231 | }); 232 | 233 | // Minecraft version-specific files list 234 | Array.from(document.querySelectorAll(".cf-recentfiles-credits-wrapper")) 235 | .filter((w) => w.firstChild == null || (w.childNodes.length == 1 && w.firstChild.nodeType != Node.ELEMENT_NODE)) 236 | .forEach((wrapper) => { 237 | let link = wrapper.parentNode.querySelector("a"); 238 | 239 | if (link != null) { 240 | let newHref = link.href.replace("files", "download"); 241 | if (regexDownloadLink.test(newHref)) { 242 | newHref = newHref + "/file"; 243 | } 244 | wrapper.innerHTML = ` 245 | 246 | 247 | 248 | `; 249 | } 250 | }); 251 | 252 | // Sort file dependency lists 253 | let sortNodeList = (list, sortBy) => { 254 | let pairs = Array.from(list).map(el => [el, el.parentNode]); 255 | pairs.sort((a, b) => sortBy(a[0], b[0])); 256 | pairs.forEach(pair => pair[1].removeChild(pair[0])); 257 | pairs.forEach(pair => pair[1].appendChild(pair[0])); 258 | }; 259 | 260 | let relatedProjectsHeading = Array.from(document.querySelectorAll("section > h3")).find(heading => heading.innerText == "Related Projects"); 261 | if (relatedProjectsHeading != undefined) { 262 | sortNodeList(relatedProjectsHeading.parentNode.querySelectorAll("section > div.flex > div"), (a, b) => 263 | a.querySelector("p.font-bold > a").innerText.localeCompare(b.querySelector("p.font-bold > a").innerText)); 264 | } 265 | })(); 266 | -------------------------------------------------------------------------------- /images/allfiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comp500/Curseforge-Userscripts/389fd8017b9fe15c59e9d414e065995ca7431f1c/images/allfiles.png -------------------------------------------------------------------------------- /images/defaultusertab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comp500/Curseforge-Userscripts/389fd8017b9fe15c59e9d414e065995ca7431f1c/images/defaultusertab.png -------------------------------------------------------------------------------- /images/fortcraft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comp500/Curseforge-Userscripts/389fd8017b9fe15c59e9d414e065995ca7431f1c/images/fortcraft.png -------------------------------------------------------------------------------- /images/modpackdownload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comp500/Curseforge-Userscripts/389fd8017b9fe15c59e9d414e065995ca7431f1c/images/modpackdownload.png -------------------------------------------------------------------------------- /images/modstab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comp500/Curseforge-Userscripts/389fd8017b9fe15c59e9d414e065995ca7431f1c/images/modstab.png -------------------------------------------------------------------------------- /images/moduleundisabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comp500/Curseforge-Userscripts/389fd8017b9fe15c59e9d414e065995ca7431f1c/images/moduleundisabled.png -------------------------------------------------------------------------------- /images/nocountdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comp500/Curseforge-Userscripts/389fd8017b9fe15c59e9d414e065995ca7431f1c/images/nocountdown.png -------------------------------------------------------------------------------- /images/nocountdown2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comp500/Curseforge-Userscripts/389fd8017b9fe15c59e9d414e065995ca7431f1c/images/nocountdown2.png -------------------------------------------------------------------------------- /images/pagination.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comp500/Curseforge-Userscripts/389fd8017b9fe15c59e9d414e065995ca7431f1c/images/pagination.png -------------------------------------------------------------------------------- /images/searchbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comp500/Curseforge-Userscripts/389fd8017b9fe15c59e9d414e065995ca7431f1c/images/searchbar.png -------------------------------------------------------------------------------- /oss.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name No More ARR Mods 3 | // @version 0.3 4 | // @description you weren't gonna use them anyway 5 | // @author comp500 6 | // @namespace https://infra.link/ 7 | // @match https://www.curseforge.com/minecraft/* 8 | // @match https://www.curseforge.com/Minecraft/* 9 | // @match https://legacy.curseforge.com/minecraft/* 10 | // @match https://legacy.curseforge.com/Minecraft/* 11 | // @updateURL https://github.com/comp500/Curseforge-Userscripts/raw/master/oss.user.js 12 | // @downloadURL https://github.com/comp500/Curseforge-Userscripts/raw/master/oss.user.js 13 | // @homepageURL https://github.com/comp500/Curseforge-Userscripts/ 14 | // @supportURL https://github.com/comp500/Curseforge-Userscripts/issues/ 15 | // @source https://github.com/comp500/Curseforge-Userscripts/ 16 | // @run-at document-end 17 | // @grant GM_xmlhttpRequest 18 | // @connect addons-ecs.forgesvc.net 19 | // @connect edge.forgecdn.net 20 | // @connect media.forgecdn.net 21 | // ==/UserScript== 22 | 23 | (function() { 24 | "use strict"; 25 | 26 | let storage = {}; 27 | if (localStorage.NoMoreARRMods != null) { 28 | try { 29 | storage = JSON.parse(localStorage.NoMoreARRMods); 30 | } catch (e) { 31 | console.error(e); 32 | storage = {}; 33 | } 34 | } 35 | 36 | let modRegex = /\/minecraft\/mc-mods\/([a-z][\da-z\-_]{0,127})$/; 37 | let mCreatorDescRegex = /
[\w\W]*MCreator[\w\W]*?<\/div>/i; 38 | let openSourceRegex = /Source<\/svg>/; 39 | // haha nice try 40 | let hahaSourceRegex = /\s*\s*Source/i; 41 | 42 | let greaseMonkeyXHR = details => { 43 | details.method = details.method ? details.method : "GET"; 44 | details.anonymous = true; 45 | details.responseType = details.responseType ? details.responseType : "arraybuffer"; 46 | return new Promise((resolve, reject) => { 47 | details.onload = resolve; 48 | details.onerror = reject; 49 | GM_xmlhttpRequest(details); 50 | }); 51 | }; 52 | 53 | Promise.all( 54 | Array.from(document.querySelectorAll(".project-listing-row")).map(async row => { 55 | let link = Array.from(row.getElementsByTagName("a")).find(a => modRegex.test(a.href)); 56 | if (link != undefined) { 57 | let stored = (link.href 58 | .replace("https://legacy.curseforge.com/minecraft/mc-mods/", "") 59 | .replace("https://legacy.curseforge.com/Minecraft/mc-mods/", "") 60 | .replace("https://www.curseforge.com/minecraft/mc-mods/", "") 61 | .replace("https://www.curseforge.com/Minecraft/mc-mods/", "")); 62 | if (storage[stored] == "arr") { 63 | row.parentNode.removeChild(row); 64 | return; 65 | } else if (storage[stored] == "normal" || stored == "thanos-skin") { 66 | return; 67 | } 68 | let res = await fetch(link.href); 69 | if (res.status !== 200) { 70 | console.error("NoMoreARRMods: failed to get mod page " + link.href); 71 | console.log(res); 72 | return; 73 | } 74 | 75 | let text = await res.text(); 76 | // Exclude mods with MCreator in the description 77 | if (mCreatorDescRegex.test(text)) { 78 | storage[stored] = "arr"; 79 | row.parentNode.removeChild(row); 80 | return; 81 | } 82 | // Allow only OSS mods 83 | if (openSourceRegex.test(text) && !hahaSourceRegex.test(text)) { 84 | storage[stored] = "normal"; 85 | } else { 86 | storage[stored] = "arr"; 87 | row.parentNode.removeChild(row); 88 | } 89 | } 90 | }) 91 | ) 92 | .then(a => { 93 | localStorage.NoMoreARRMods = JSON.stringify(storage); 94 | }) 95 | .catch(e => { 96 | console.error(e); 97 | }); 98 | })(); 99 | -------------------------------------------------------------------------------- /redir.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Redirect Old Links 3 | // @version 0.3 4 | // @description No more "Module Disabled"! 5 | // @author comp500 6 | // @namespace https://infra.link/ 7 | // @match https://minecraft.curseforge.com/mc-mods/* 8 | // @updateURL https://github.com/comp500/Curseforge-Userscripts/raw/master/redir.user.js 9 | // @downloadURL https://github.com/comp500/Curseforge-Userscripts/raw/master/redir.user.js 10 | // @homepageURL https://github.com/comp500/Curseforge-Userscripts/ 11 | // @supportURL https://github.com/comp500/Curseforge-Userscripts/issues/ 12 | // @source https://github.com/comp500/Curseforge-Userscripts/ 13 | // @run-at document-end 14 | // @grant GM_xmlhttpRequest 15 | // @connect addons-ecs.forgesvc.net 16 | // ==/UserScript== 17 | 18 | (async function() { 19 | 'use strict'; 20 | 21 | let greaseMonkeyXHR = details => { 22 | details.method = details.method ? details.method : "GET"; 23 | details.anonymous = true; 24 | details.responseType = details.responseType ? details.responseType : "arraybuffer"; 25 | return new Promise((resolve, reject) => { 26 | details.onload = resolve; 27 | details.onerror = reject; 28 | GM_xmlhttpRequest(details); 29 | }); 30 | }; 31 | 32 | // Match project IDs 33 | let matches = location.href.match(/https:\/\/minecraft.curseforge.com\/mc-mods\/(\d+)/); 34 | if (matches != null && matches[1] != null) { 35 | location.href = "https://www.curseforge.com/projects/" + matches[1]; 36 | } else { 37 | // Match slug IDs 38 | matches = location.href.match(/https:\/\/minecraft.curseforge.com\/mc-mods\/([a-z][\da-z\-_]{1,127})\/?$/); 39 | if (matches != null && matches[1] != null) { 40 | location.href = "https://www.curseforge.com/minecraft/mc-mods/" + matches[1]; 41 | } 42 | } 43 | })(); 44 | --------------------------------------------------------------------------------