您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
A GGn user script to help with No-Intro uploads/trumps
当前为
// ==UserScript== // @name GGn No-Intro Helper // @description A GGn user script to help with No-Intro uploads/trumps // @namespace http://tampermonkey.net/ // @version 2.4.0 // @author BestGrapeLeaves // @license MIT // @match *://gazellegames.net/upload.php?groupid=* // @match *://gazellegames.net/torrents.php?id=* // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_listValues // @grant GM_deleteValue // @grant GM_setValue // @grant GM_getValue // @connect datomatic.no-intro.org // @icon https://i.imgur.com/UFOk0Iu.png // ==/UserScript== /******/ (() => { // webpackBootstrap /******/ "use strict"; var __webpack_exports__ = {}; ;// CONCATENATED MODULE: ./src/inserts/checkForTrumpsButton.ts function checkForTrumpsButton() { const existing = $("#check-for-no-intro-trumps-button"); const button = existing.length > 0 ? existing : $(`<input id="check-for-no-intro-trumps-button" type="button" value="Check for No-Intro Trumps" style="background: hotpink; color: black; font-weight: bold; margin-left: 10px;"/>`); const progress = (text)=>{ button.val(text); }; const disable = ()=>{ button.prop("disabled", true); button.css("background-color", "pink"); button.css("color", "darkslategray"); button.css("box-shadow", "none"); }; const insert = ()=>{ button.detach(); $(".torrent_table > tbody > tr:first-child > td:first-child").first().append(button); }; return { disable, progress, insert, button }; } ;// CONCATENATED MODULE: ./src/utils/dom/extractNoIntroLinkFromDescription.ts function extractNoIntroLinkFromDescription(torrentId) { const links = $(`#torrent_${torrentId} #description a`); return links.map(function() { return $(this).attr("href"); }).get().map((link)=>{ const url = new URL(link); url.protocol = "https:"; // Rarely descriptions have the http protocol return url.toString(); }).find((link)=>link.startsWith("https://datomatic.no-intro.org/")); } ;// CONCATENATED MODULE: ./src/utils/dom/getNoIntroTorrentsOnPage.ts function notFalse(x) { return x !== false; } function getNoIntroTorrentsOnPage() { return $('a[title="Permalink"]').map(function() { const torrentId = new URLSearchParams($(this).attr("href").replace("torrents.php", "")).get("torrentid"); const noIntroLink = extractNoIntroLinkFromDescription(torrentId); if (!noIntroLink) { return false; } const reported = $(this).parent().parent().find(".reported_label").text() === "Reported"; return { torrentId, a: $(this), noIntroLink, reported, permalink: window.location.origin + "/" + $(this).attr("href") }; }).get().filter(notFalse); } ;// CONCATENATED MODULE: ./src/inserts/insertAddCopyHelpers.ts function insertAddCopyHelpers() { getNoIntroTorrentsOnPage().forEach((param)=>{ let { torrentId , a , noIntroLink } = param; // Extract edition information const editionInfo = a.parents(".group_torrent").parent().prev().find(".group_torrent > td > strong").text(); const [editionYear, ...rest] = editionInfo.split(" - "); const editionName = rest.join(" - "); const formatedEditionInfo = `${editionName} (${editionYear})`; // GroupId const groupId = new URLSearchParams(window.location.search).get("id"); // Create params const params = new URLSearchParams(); params.set("groupid", groupId); params.set("edition", formatedEditionInfo); params.set("no-intro", noIntroLink); // Insert button const addCopyButton = $(`<a href="upload.php?${params.toString()}" title="Add Copy" id="ac_${torrentId}">AC</a>`); $([ " | ", addCopyButton ]).insertAfter(a); }); } ;// CONCATENATED MODULE: ./src/utils/GMCache.ts class GMCache { getKeyName(key) { return `cache${this.name}.${key}`; } get(key) { const res = GM_getValue(this.getKeyName(key)); if (res === undefined) { return undefined; } const { value , expires } = res; if (expires && expires < Date.now()) { this.delete(key); return undefined; } return value; } set(key, value, ttl) { const expires = Date.now() + ttl; GM_setValue(this.getKeyName(key), { value, expires }); } delete(key) { GM_deleteValue(this.getKeyName(key)); } cleanUp() { const keys = GM_listValues(); keys.forEach((key)=>{ if (key.startsWith(this.getKeyName(""))) { const { expires } = GM_getValue(key); if (expires < Date.now()) { GM_deleteValue(key); } } }); } clear() { const keys = GM_listValues(); keys.forEach((key)=>{ if (key.startsWith(this.getKeyName(""))) { GM_deleteValue(key); } }); } constructor(name){ this.name = name; } } ;// CONCATENATED MODULE: ./src/constants.ts // LISTS const GGN_REGIONS = [ "USA", "Europe", "Japan", "Asia", "Australia", "France", "Germany", "Spain", "Italy", "UK", "Netherlands", "Sweden", "Russia", "China", "Korea", "Hong Kong", "Taiwan", "Brazil", "Canada", "Japan, USA", "Japan, Europe", "USA, Europe", "Europe, Australia", "Japan, Asia", "UK, Australia", "World", "Region-Free", "Other", ]; // TABLES const REGION_TO_LANGUAGE = { World: "English", USA: "English", Europe: "English", Japan: "Japanese", Australia: "English", France: "French", Germany: "German", Italy: "Italian", UK: "English", Netherlands: "Other", Sweden: "Other", Russia: "Russian", China: "Chinese", Korea: "Korean", Taiwan: "Chinese", "Hong Kong": "Chinese", Brazil: "Portuguese", Canada: "English", "USA, Europe": "English", "Europe, Australia": "English", "UK, Australia": "English", Other: "Other" }; const TWO_LETTER_REGION_CODE_TO_NAME = { en: "English", de: "German", fr: "French", cz: "Czech", zh: "Chinese", it: "Italian", ja: "Japanese", ko: "Korean", pl: "Polish", pt: "Portuguese", ru: "Russian", es: "Spanish" }; ;// CONCATENATED MODULE: ./src/utils/identifyNoIntroTags.ts function identifyNoIntroTags(title) { const tags = title.match(/\(.+?\)/g).map((p)=>p.slice(1, -1)); let region = ""; let languages = []; let edition = []; let release = []; tags.forEach((tag)=>{ // Region if (GGN_REGIONS.includes(tag)) { region = tag; return; } // Language const maybeTwoLetterCodes = tag.split(",").map((l)=>l.trim().toLowerCase()); const isLanguages = maybeTwoLetterCodes.every((l)=>l.length === 2); if (isLanguages) { languages = maybeTwoLetterCodes; return; } // Edition if ([ "Proto", "Sample" ].includes(tag) || tag.startsWith("Beta") || tag.startsWith("Demo") || tag.endsWith("Virtual Console") || tag.includes("Edition")) { edition.push(tag); return; } // None of the above release.push(tag); }); if (region === "") { release.shift(); region = "Other"; } let language; if (languages.length === 0) { language = REGION_TO_LANGUAGE[region] || "Other"; } else if (languages.length === 1) { language = TWO_LETTER_REGION_CODE_TO_NAME[languages[0]] || "Other"; } else { language = "Multi-Language"; } return { language: language, region: region, edition, release: release.map((t)=>`(${t})`).join(" ") }; } ;// CONCATENATED MODULE: ./src/utils/fetchNoIntro.ts const cache = new GMCache("no-intro"); // @ts-expect-error unsafeWindow.GGN_NO_INTRO_HELPER_CACHE = cache; function fetchNoIntro(url) { return new Promise((resolve, reject)=>{ if (url.endsWith("n=")) { return reject(new Error("Blacklist no-intro url. Fetch was aborted to prevent IP ban.")); } const cached = cache.get(url); if (cached) { resolve({ ...cached, cached: true }); return; } GM_xmlhttpRequest({ method: "GET", url, timeout: 5000, onload: (param)=>{ let { responseText } = param; try { const parser = new DOMParser(); const scraped = parser.parseFromString(responseText, "text/html"); // HTML is great const dumpsTitle = [ ...scraped.querySelectorAll("td.TableTitle"), ].find((td)=>td.innerText.trim() === "Dump(s)"); if (!dumpsTitle) { // @ts-expect-error unsafeWindow.GMPARSER = scraped; console.error("GGn No-Intro Helper: dumps title not found, set parser as global: GMPARSER", responseText); throw new Error("No dump's title found"); } const filename = dumpsTitle.parentElement.parentElement.parentElement.nextElementSibling.querySelector("table > tbody > tr:nth-child(2) > td:last-child").innerText.trim(); const title = scraped.querySelector("tr.romname_section > td").innerText.trim(); // Region/Lang const { language , region , edition , release } = identifyNoIntroTags(title); // One hour seems appropriate const extension = filename.split(".").pop() || ""; const info = { // We stopped shipping entire filenames // when zibzab reported that it varies from dump to dump // like some can have a "bad" filename // title is 100% accurate, and filename shouldn't vary filename: title + "." + extension, extension, title, language, region, editionTags: edition, releaseTags: release, cached: false }; cache.set(url, info, 1000 * 60 * 60); resolve(info); } catch (err) { console.error("zibzab helper failed to parse no-intro:", err); reject(new Error("Failed to parse no-intro :/\nPlease report to BestGrapeLeaves,\nincluding the error that was logged to the browser console")); } }, ontimeout: ()=>{ reject(new Error("Request to No-Intro timed out after 5 seconds")); } }); }); } ;// CONCATENATED MODULE: ./src/utils/dom/fetchTorrentFilelist.ts // We are fetching files for checking, // might as well reduce load on servers and save to dom (like the button does) function fetchTorrentFilelist(torrentId) { const parseFromDom = ()=>$(`#files_${torrentId} > table > tbody > tr:not(.colhead_dark) > td:first-child`).map(function() { return $(this).text(); }).get(); return new Promise((resolve)=>{ // @ts-expect-error if ($("#files_" + torrentId).raw().innerHTML === "") { // $('#files_' + torrentId).gshow().raw().innerHTML = '<h4>Loading...</h4>'; ajax.get("torrents.php?action=torrentfilelist&torrentid=" + torrentId, function(response) { // @ts-expect-error $("#files_" + torrentId).ghide(); // @ts-expect-error $("#files_" + torrentId).raw().innerHTML = response; resolve(parseFromDom()); }); } else { resolve(parseFromDom()); } }); } ;// CONCATENATED MODULE: ./src/utils/dom/checkIfTrumpable.ts async function checkIfTrumpable(torrent) { try { const { title , cached } = await fetchNoIntro(torrent.noIntroLink); const desiredFilename = title + ".zip"; const files = await fetchTorrentFilelist(torrent.torrentId); if (files.length !== 1) { return { trumpable: true, desiredFilename, cached, inditermint: "Couldn't determine if the torrent is trumpable -\nMultiple/No zip files found in torrent" }; } const actualFilename = files[0]; return { trumpable: desiredFilename !== actualFilename, desiredFilename, actualFilename, cached }; } catch (err) { console.error("GGn No-Intro Helper: Error checking trumpability", err); return { trumpable: true, cached: false, inditermint: "Couldn't determine if the torrent is trumpable -\nFailed fetching No-Intro:\n" + err.message }; } } ;// CONCATENATED MODULE: ./src/inserts/smallPre.ts function smallPre(text, bgColor) { return `<pre style=" padding: 0px; margin: 0; background-color: ${bgColor}; color: black; font-weight: bold; font-size: 12px; padding-left: 3px; padding-right: 3px; width: fit-content; ">${text}</pre>`; } ;// CONCATENATED MODULE: ./src/inserts/insertTrumpNotice.ts function inditermintNoticeInfo(param) { let { inditermint } = param; return { title: "Couldn't determine if the torrent is trumpable:", details: inditermint, color: "pink" }; } function reportedNoticeInfo(param) { let { inditermint } = param; return { title: "Torrent was trumped and reported!", details: "", color: "var(--darkRed)" }; } function trumpableNoticeInfo(param) { let { actualFilename , desiredFilename } = param; return { title: "This torrent is trumpable!", details: `The filename in the torrent is: ${smallPre(actualFilename, "lightcoral")} but the desired filename, based on <i>No-Intro</i> is: ${smallPre(desiredFilename, "lightgreen")}`, color: "hotpink" }; } function reportableNoticeInfo(param) { let { fixedVersion , torrentId , actualFilename , desiredFilename , noIntroLink } = param; const form = $("<div/></div>"); const trumpingTorrentInput = $(`<input type="text" value="${fixedVersion.permalink}" placeholder="https://gazellegames.net/torrents.php?id=xxxxx&torrentid=yyyyyy" style="width: 75%; background: #ffb952; color: black;"/>`); const commentTextarea = $(`<textarea placeholder="Previous upload didn't like hummus" style="height: 100px; width: 90%; background: #ffb952; color: black; margin: 0;"/>`); commentTextarea.text(`ROM name changed on No-Intro. Reported filename : ${actualFilename} Trumping filename : ${desiredFilename} No-Intro for reference : ${noIntroLink}`); const submitInputButton = $(`<input id="no-intro-helper-submit-trump-report-${torrentId}" type="button" value="REPORT" style="width: 70px; margin: 0; background: #ffb952; color: black; font-weight: bolder;"/>`); const errorMessage = $("<div style='color: red; font-weight: bold; font-size: 13px; white-space: pre-line;'></div>").hide(); form.append(`<p style="font-size: 12px;">Trumping Torrent Permalink:</p>`, trumpingTorrentInput, `<p style="font-size: 12px;">Report Comment:</p>`, commentTextarea, `<p style="font-size: 12px;">Submit Report:</p>`, submitInputButton, errorMessage); submitInputButton.click(async ()=>{ // If it's disabled this should in theory not trigger, // but just in case jquery does some shenaningans // when you manually trigger a click event if (submitInputButton.prop("disabled") === true) { return; } errorMessage.hide(); submitInputButton.prop("disabled", true); const data = new FormData(); data.append("submit", "true"); data.append("torrentid", torrentId); data.append("categoryid", "1"); data.append("type", "trump"); data.append("sitelink", fixedVersion.permalink); data.append("extra", commentTextarea.val()); data.append("id_token", new Date().getTime().toString()); try { await fetch("/reportsv2.php?action=takereport", { method: "POST", body: data }); location.reload(); } catch (err) { console.error("Error submitting trump report", err); console.error("Form data sent:", Object.fromEntries([ ...data.entries() ])); errorMessage.text(`An error occurred while submitting the trump report. If you believe this is a problem with the script, please report to BestGrapeLeaves (including console logs if you can). Error Message: ${err.message}`); errorMessage.show(); submitInputButton.prop("disabled", false); } }); return { title: "Torrent needs to be reported for trump!", details: form, color: "#ff7600" }; } function insertTrumpNotice(torrent) { const { inditermint , fixedVersion , torrentId , reported } = torrent; // Settings let info; let type; if (inditermint) { type = "inditermint"; info = inditermintNoticeInfo(torrent); } else if (fixedVersion) { if (reported) { type = "reported"; info = reportedNoticeInfo(torrent); } else { type = "reportable"; info = reportableNoticeInfo(torrent); } } else { type = "trumpable"; info = trumpableNoticeInfo(torrent); } const { color , details , title } = info; // Elements const detailsDiv = $(`<div style="font-weight: normal; color: white;"></div>`).hide(); detailsDiv.append(details); const titleSpan = $(` <span style="color: ${color}; font-size: 14px; font-weight: bold;">${title}</span>`); const actionsDiv = $(`<div id="trump-notice-links-${torrentId}" style="font-weight: normal; font-size: 11px; display: inline; margin: 5px; user-select: none;"></div>`); // Toggle Details if (type !== "reported") { const toggleDetailsActionSpan = $(`<span style="cursor: pointer; margin-right: 5px;">[Expand]</span>`); toggleDetailsActionSpan.click(()=>{ const collapsed = toggleDetailsActionSpan.text() === "[Expand]"; if (collapsed) { toggleDetailsActionSpan.text("[Collapse]"); detailsDiv.show(); } else { toggleDetailsActionSpan.text("[Expand]"); detailsDiv.hide(); } }); actionsDiv.append(toggleDetailsActionSpan); } // Send Report if (type === "reportable") { const sendReportActionSpan = $(`<span style="cursor: pointer; margin-right: 5px;">[Send Report]</span>`); sendReportActionSpan.click(()=>{ $(`#no-intro-helper-submit-trump-report-${torrentId}`).click(); }); actionsDiv.append(sendReportActionSpan); } // Cheer if (type === "reported") { const cheerActionSpan = $(`<span style="cursor: pointer; margin-right: 5px; position: absolute;">[Cheer]</span>`); cheerActionSpan.click(()=>{ cheerActionSpan.text("HOORAY!"); cheerActionSpan.animate({ opacity: 0 }, // @ts-expect-error { duration: 2000, step: function(now) { cheerActionSpan.css({ transform: "rotate(" + now * 360 * 5 + "deg)" }); } }, "swing"); }); actionsDiv.append(cheerActionSpan); } // Tree const wrapper = $(`<div></div>`); titleSpan.append(actionsDiv); wrapper.append(titleSpan); wrapper.append(detailsDiv); // Place let currentlyAdaptedToSmallScreen; function placeTrumpNotice() { console.log("adapting", window.innerWidth); if (window.innerWidth <= 800) { if (currentlyAdaptedToSmallScreen) { return; } currentlyAdaptedToSmallScreen = true; $(`#torrent${torrentId}`).css("border-bottom", "none"); wrapper.css("margin-left", "25px"); wrapper.detach(); wrapper.insertAfter(`#torrent${torrentId}`); } else { if (currentlyAdaptedToSmallScreen === false) { return; } currentlyAdaptedToSmallScreen = false; $(`#torrent${torrentId}`).css("border-bottom", ""); wrapper.css("margin-left", "0px"); wrapper.detach(); wrapper.appendTo(`#torrent${torrentId} > td:first-child`); } } placeTrumpNotice(); $(window).resize(placeTrumpNotice); // Call global hook (for other scripts) // @ts-expect-error if (typeof unsafeWindow.GM_GGN_NOINTRO_HELPER_ADDED_LINKS === "function") { // @ts-expect-error unsafeWindow.GM_GGN_NOINTRO_HELPER_ADDED_LINKS({ ...torrent, links: actionsDiv }); } } ;// CONCATENATED MODULE: ./src/inserts/insertTrumpSuggestions.ts async function checkForImproperlyNamedTorrents(torrents) { const { disable , progress } = checkForTrumpsButton(); disable(); let prevCached = false; const results = []; for(let i = 0; i < torrents.length; i++){ const torrent = torrents[i]; progress(`Checking For Trumps ${i + 1}/${torrents.length}...`); // timeout to avoid rate limiting if (!prevCached) { await new Promise((resolve)=>setTimeout(resolve, 500)); } // Check trump const TrumpCheckResult = await checkIfTrumpable(torrent); const { cached } = TrumpCheckResult; prevCached = cached; results.push({ ...TrumpCheckResult, ...torrent }); } return results; } // Filter the torrents that have a trump uploaded function attachFixedVersionsToTorrents(torrents) { const trumpCanidates = []; const validTorrents = []; for (const torrent of torrents){ if (torrent.trumpable) { trumpCanidates.push(torrent); continue; } validTorrents.push(torrent); } // Efficiency is not my greatest of concerns, // if you want implement a graph theory solution in O(1) or something const processed = trumpCanidates.map((c)=>({ ...c, fixedVersion: validTorrents.find((v)=>!v.inditermint && v.noIntroLink === c.noIntroLink) })); return [ ...validTorrents, ...processed ]; } async function insertTrumpSuggestions(results) { const { progress } = checkForTrumpsButton(); let trumps = 0; results.forEach((torrent)=>{ if (!torrent.trumpable) { return; } if (!torrent.inditermint && !torrent.fixedVersion) { trumps++; } insertTrumpNotice(torrent); }); if (trumps === 0) { progress("No Trumps Found"); } else if (trumps === 1) { progress("1 Trump Found"); } else { progress(`${trumps} Trumps Found`); } } async function findAndDisplayTrumps() { const torrents = getNoIntroTorrentsOnPage(); const results = await checkForImproperlyNamedTorrents(torrents); const processed = attachFixedVersionsToTorrents(results); console.log("GGn No-Intro Helper: Trumps", processed); insertTrumpSuggestions(processed); } ;// CONCATENATED MODULE: ./src/pages/torrents.ts function trumpSuggestions() { const torrents = getNoIntroTorrentsOnPage(); if (torrents.length === 0) { return; } const { button , insert } = checkForTrumpsButton(); insert(); if (torrents.length <= 4) { findAndDisplayTrumps(); } button.click((e)=>{ e.stopImmediatePropagation(); findAndDisplayTrumps(); }); } function torrentsPageMain() { insertAddCopyHelpers(); trumpSuggestions(); } ;// CONCATENATED MODULE: ./src/inserts/uploadLinkParserUI.ts function uploadNoIntroLinkParserUI() { // elements const container = $(`<tr id="no-intro-url" name="no-intro-url"> <td class="label">No-Intro Link</td> </tr>`); const input = $('<input type="text" id="no-intro-url-input" name="no-intro-url-input" size="60%" class="input_tog" value="">'); const error = $('<p id="no-intro-url-error" name="no-intro-url-error" style="color: red; white-space:pre-line;"></p>').hide(); const loading = $('<p id="no-intro-url-loading" name="no-intro-url-loading" style="color: green;">Loading...</p>').hide(); const goButton = $('<input type="button" value="No-Intro"></input>'); // structure const td = $("<td></td>"); td.append(input); td.append(error); td.append(loading); container.append(td); // utils const setError = (msg)=>{ error.text(msg); error.show(); }; const setLoading = (isLoading)=>{ if (isLoading) { loading.show(); } else { loading.hide(); } }; const insert = ()=>{ container.insertAfter("#torrent_file"); }; // stuff input.on("paste", (e)=>{ e.preventDefault(); const text = e.originalEvent.clipboardData.getData("text/plain"); input.val(text); }); return { loading, error, container, input, goButton, setError, setLoading, insert }; } ;// CONCATENATED MODULE: ./src/utils/dom/setUploadEdition.ts function setUploadEdition(edition) { try { $("#groupremasters").val(edition).change(); GroupRemaster(); } catch { // group remaster always throws (regardless of the userscript) } } ;// CONCATENATED MODULE: ./src/utils/generateTorrentDescription.ts const generateTorrentDescription = function() { let url = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "xxx", filename = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "xxx"; return `[align=center]${filename} matches [url=${url}]No-Intro checksum[/url] Compressed with [url=https://sourceforge.net/projects/trrntzip/]torrentzip.[/url][/align] `; }; ;// CONCATENATED MODULE: ./src/pages/upload.ts function uploadPageMain() { // const filename = $("#file").val() as string; const { goButton , error , input , setError , setLoading , insert } = uploadNoIntroLinkParserUI(); insert(); goButton.insertAfter("#file"); // Pre-fill link if possible const params = new URLSearchParams(window.location.search); const noIntroLink = params.get("no-intro"); if (noIntroLink) { $("#no-intro-url-input").val(noIntroLink).change(); } goButton.click(async ()=>{ // Prechecks error.hide(); let link = input.val(); const url = new URL(link); url.protocol = "https:"; link = url.toString(); if (!link.startsWith("https://datomatic.no-intro.org/")) { setError("Invalid URL"); return; } // Start setLoading(true); goButton.prop("disabled", true); try { const { filename , language , region , releaseTags , editionTags } = await fetchNoIntro(link); // Release type = ROM $("select#miscellaneous").val("ROM").change(); // Description $("textarea#release_desc").val(generateTorrentDescription(link, filename)); // Region $("select#region").val(region); // Language $("select#language").val(language); // It is a special edition (all no intro are) if (!$("input#remaster").prop("checked")) { $("input#remaster").prop("checked", true); Remaster(); } // Not a scene release $("#ripsrc_home").prop("checked", true); updateReleaseTitle(// @ts-expect-error Update release title $("#title").raw().value + (releaseTags ? " " + releaseTags : "")); // Set correct edition from url params/edition // @TODO: Compare from param to expected // Sometimes editions are created wrong // If so maybe let the user choose or just show a warning let editionFromParam = params.get("edition"); let editionFromTags = ""; if (!editionFromParam) { const postfix = editionTags.length === 0 ? "" : ` - ${editionTags.join(", ")}`; editionFromTags = `No-Intro${postfix}`; } // Update edition select let updatedEdition = false; $("#groupremasters > option").each(function() { const title = $(this).text().toLowerCase(); if (editionFromParam && title === editionFromParam.toLowerCase()) { setUploadEdition($(this).val()); updatedEdition = true; return false; // This breaks out of the jquery loop } if (editionFromTags && title.replace(/ \(\d{4}\)/, "") === editionFromTags.toLowerCase()) { setUploadEdition($(this).val()); updatedEdition = true; return false; } }); // If edition does not exist, fill name at least if (!updatedEdition) { $("#remaster_title").val(editionFromTags); } } catch (err) { setError(err.message || err || "An unexpected error has occurred"); } finally{ setLoading(false); goButton.prop("disabled", false); } }); } ;// CONCATENATED MODULE: ./src/index.ts async function main() { console.log("GGn No-Intro Helper: Starting..."); if (window.location.pathname === "/torrents.php") { torrentsPageMain(); } else if (window.location.pathname === "/upload.php") { uploadPageMain(); } // Blacklist no-intro URLs that lead to ban $("a").click(function() { if (/https?:\/\/datomatic\.no-intro\.org\/index\.php\?page=show_record&s=(.*?)&n=$/i.test($(this).attr("href"))) { $(this).text("BLACKLISTED"); return false; } }); } main().catch((e)=>{ console.log(e); }); /******/ })() ;