// ==UserScript== // @name GGn Unified OST Uploady // @version 1.1.1 // @author SleepingGiant // @description Uploady for multi-source OSTs on GGn (e.g. VGMdb, bandcamp, etc.) // @namespace https://greasyfork.org/users/1395131 // @include https://gazellegames.net/upload.php* // @match https://gazellegames.net/torrents.php?action=editgroup* // @require https://code.jquery.com/jquery-3.4.1.min.js // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js // @grant GM_xmlhttpRequest // @grant GM.xmlHttpRequest // @grant GM_addStyle // @grant GM.addStyle // @downloadURL none // ==/UserScript== // Prior Authors: NeutronNoir, ZeDoCaixao, Wealth - do not reach out to them for support, but feel free to thank them for their work :) var UPLOADY_FIELD = ``; // tampermonkey intro (function() { if (window.location.href.includes("action=editgroup")) { $("input[name='aliases']").after($(UPLOADY_FIELD)); url_parser_text_entry(editgroup_page_handler); } else { $("#categories").click(function () { var el = $(this); setTimeout(function() { $("#catalog_number").remove(); if (el.find(":selected").text() == "OST") { url_parser_text_entry(upload_page_handler); } }, 500); }); } })(); function url_parser_text_entry(handler) { $("#categories").after($(UPLOADY_FIELD)); $("#catalog_number").on("blur", function() { let url = $(this).val(); let input = this; handleURL(url).then(data => { handler(data); }).catch(() => { $(input).val("Album not found"); }); }); } function handleURL(url) { if (url.includes('vgmdb.net')) { return parseVGMdb(url); } else if (url.includes('bandcamp.com')) { return parseBandcamp(url); }else if (url.includes('music.apple.com')) { return parseAppleMusic(url); } else { return Promise.reject('Unsupported URL'); } } // These two do the actual "uploading" of data to the GGn site by setting the values, `data` is pre-filled from the parser flows for each website. // If adding a new site, follow the same style and read the comment over `parseVGMdb` function upload_page_handler(data) { $("#aliases").val(data.aliases); $("#album_desc").val(data.album_desc); $("#title").val(data.title); $("#year").val(data.year); $("#image").val(data.image); } function editgroup_page_handler(data) { $("input[name='aliases']").val(data.aliases); $("textarea[name='body']").val(data.album_desc); $("input[name='name']").val(data.title); $("input[name='year']").val(data.year); $("input[name='image']").val(data.image); } /** * Website parsing section. This comment applies to effectively all parseWebsite functions. * * Further below there is also a "xyz website helper method section" (should be commented at each separation) * This is where all the logic for each individual site will be stored. * To make scaling to more sites easier, you can think of each website as its own script - where the "master" script just handles the arbitrary data response * from a website script and fills in the GGn fields with that response object. * * @param {*} url - the URL pasted into the textbox (that we will retrieve data from). That's it. * @returns A standardized data object. The expected schema is: title: The string to be put in 'Title by Artist' in GGn aliases: The string to be put in 'Aliases' in GGn year: The number (it's javascript so in string form normally) that is the year - e.g. 2025 image: Link to the cover image found on the page album_desc: Pre-formatted description. This should contain EVERYTHING. Header, tracklist, notes, etc. - what each function returns just ends up in the textbox. tags is purposefully omitted from here as sites that do have them (e.g. bandcamp) often have ones that will not apply to GGn and autofilling bad is worse than not autofilling. * */ function parseVGMdb(url) { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: "GET", url: url, onload: (response) => { if (response.status === 200) { let env = $(response.responseText); resolve({ source: 'VGMdb', aliases: get_aliases_vgmdb(env), album_desc: get_desc_vgmdb(env) + get_tracks_vgmdb(env) + get_notes_vgmdb(env), title: get_title_vgmdb(env), year: get_year_vgmdb(env), image: get_cover_vgmdb(env) }); } else { reject('Error fetching VGMdb'); } } }); }); } function parseBandcamp(url) { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: "GET", url: url, onload: function(response) { if (response.status !== 200) { reject('Error fetching Bandcamp'); return; } const doc = new DOMParser().parseFromString(response.responseText, 'text/html'); const jsonLd = doc.querySelector('script[type="application/ld+json"]'); if (!jsonLd) { reject('No JSON-LD found in the page.'); return; } const albumData = JSON.parse(jsonLd.textContent); let title = get_title_bandcamp(albumData); let aliases = get_aliases_bandcamp(albumData); let albumDesc = get_album_desc_bandcamp(albumData); let year = get_release_date_bandcamp(albumData); let image = get_cover_art_bandcamp(albumData); resolve({ source: 'Bandcamp', aliases: aliases, album_desc: albumDesc, title: title, year: year, image: image }); } }); }); } function parseAppleMusic(url) { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: "GET", url: url, onload: (response) => { if (response.status !== 200) { reject('Error fetching Apple Music'); return; } const doc = new DOMParser().parseFromString(response.responseText, 'text/html'); let title = get_title_applemusic(doc); let aliases = get_aliases_applemusic(doc); let albumDesc = get_album_desc_applemusic(doc); let year = get_release_year_applemusic(doc); let image = get_cover_art_applemusic(doc); resolve({ source: 'Apple Music', aliases: aliases, album_desc: albumDesc, title: title, year: year, image: image }); } }); }); } // VGMdb helper method section function get_cover_vgmdb(env) { return env.find("#coverart").css("background-image").replace(/url\("([^"]*)"\)/, "$1").replace("medium-", ""); } function get_year_vgmdb(env) { return env.find("#album_infobit_large>tbody>tr>td>span>b:contains('Release Date')").parent().parent().parent().find("td").last().text().trim().split(" ").pop(); } function get_title_vgmdb(env) { return env.find(".albumtitle").first().text(); } function get_aliases_vgmdb(env) { var aliases = []; env.find("#innermain .albumtitle:not(:first)[lang='en']").each(function() { aliases.push($(this).text().trim()); }); return aliases.join(", "); } function get_desc_vgmdb(env) { var desc = "[align=center][u][b]" + env.find(".albumtitle").first().text() + "[/b]\n[i][size=1]by[/i] [b]" + "" + "[/b][/u][/align]\n\n"; env.find("#album_infobit_large>tbody>tr").each(function() { var title = $(this).find("td>span>b").text(); var value = $(this).find("td").last().text().trim(); if (title && value) { if (title == "Release Date") { var rls_date = new Date(Date.parse(value)); desc += "[*][b]" + title + ":[/b]\t" + rls_date.getFullYear() + "-" + String(rls_date.getMonth()+1).padStart(2, '0') + "-" + String(rls_date.getDate()).padStart(2, '0') + "\n"; } else { desc += "[*][b]" + title + ":[/b]\t" + value + "\n"; } } }); return desc; } function get_tracks_vgmdb(env) { var tracks = "\n[align=center][u][b]Tracklist[/b][/u][/align]\n"; env.find("#tracklist>span>table>tbody").each(function(index) { var disc_count = env.find("#tracklist").text().match(/Disc [0-9]+/g)?.length || 1; if (disc_count > 1) { tracks += "[b]Disc " + (index + 1) + "[/b]\n"; } $(this).find("tr").each(function() { var tds = $(this).find("td"); var track_number = tds.eq(0).text().trim(); var track_title = tds.eq(1).text().trim(); var track_duration = tds.last().text().trim(); if (track_title) { tracks += "[#] " + track_title + " [i](" + track_duration + ")[/i]\n"; } }); }); var total_time = env.find('#tracklist>span>span .time').text().trim(); if (total_time) { tracks += "[b]Total length[/b]:\t" + total_time; } return tracks; } function get_notes_vgmdb(env) { var notes = env.find("#notes").html(); if (notes) { notes = notes.replace(//g, "\n").trim(); return "\n\n[quote][align=center][b][u]Notes[/u][/b][/align]\n" + notes + "[/quote]"; } return ""; } // Bandcamp helper method section function get_title_bandcamp(albumData) { return albumData.name + " by " + albumData.byArtist.name|| ""; } function get_aliases_bandcamp(albumData) { return ""; } function get_album_desc_bandcamp(albumData) { // Initialize total duration in seconds and an array to hold track details let totalDurationSeconds = 0; let trackDetails = []; // Process each track in the album albumData.track.itemListElement.forEach((track, index) => { const trackDurationSeconds = parseDuration_bandcamp(track.item.duration); totalDurationSeconds += trackDurationSeconds; const trackNumber = index + 1; // Track number starts at 1 trackDetails.push({ number: trackNumber, name: track.item.name, duration: formatTrackDuration_bandcamp(trackDurationSeconds) // Format track duration }); }); // Add header content let bodyContent = "[align=center][u][b]" + albumData.name + "[/b]\n" + "[i][size=1]by[/i] [b]" + albumData.byArtist.name + "[/b][/u][/align]\n\n"; // Add pre-tracklist content bodyContent += '[align=center][u][b]Tracklist[/b][/u][/align]\n' trackDetails.forEach(track => { bodyContent += "[#] " + track.name + " [i](" + track.duration + ")[/i]\n"; }); bodyContent += "[b]Total Length:[/b] " + formatTotalDuration_bandcamp(totalDurationSeconds);; return bodyContent; } // Adjust the total duration format for the album (without leading zero for total time over 10 minutes) function formatTotalDuration_bandcamp(seconds) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${String(remainingSeconds).padStart(2, '0')}`; } function get_release_date_bandcamp(albumData) { if (!albumData.dateModified) return ""; const dateString = albumData.dateModified; // Example: "27 Mar 2025 09:04:37 GMT" const yearMatch = dateString.match(/\b\d{4}\b/); // Extracts a 4-digit year return yearMatch ? yearMatch[0] : ""; // Return the year or fallback } function get_cover_art_bandcamp(albumData) { return albumData.image || "No Cover Art Available"; } function get_tracks_bandcamp(albumData) { let trackDetails = []; albumData.track.itemListElement.forEach((track) => { const trackDurationSeconds = parseDuration_bandcamp(track.item.duration); trackDetails.push({ name: track.item.name, duration: formatTrackDuration_bandcamp(trackDurationSeconds) }); }); return trackDetails; } function formatTrackDuration_bandcamp(seconds) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${String(remainingSeconds).padStart(2, '0')}`; } function parseDuration_bandcamp(durationStr) { const regex = /^P(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/; const match = regex.exec(durationStr); const hours = parseInt(match[1] || 0); const minutes = parseInt(match[2] || 0); const seconds = parseInt(match[3] || 0); return (hours * 3600) + (minutes * 60) + seconds; } // Apple Music helper method section function get_title_applemusic(doc) { let titleElement = doc.querySelector("h1.headings__title span"); return titleElement ? titleElement.textContent.trim() : ""; } function get_aliases_applemusic(doc) { return ""; // Apple Music doesn't typically provide alternate album names } function get_album_desc_applemusic(doc) { // Extract JSON-LD data from the script const schemaScript = doc.querySelector('script[type="application/ld+json"]#schema\\:music-album'); if (!schemaScript) { console.error("Schema script not found"); return ""; } console.log(schemaScript) let tracks = []; let totalDuration = 0; try { const jsonData = JSON.parse(schemaScript.textContent); // Extract track details const trackList = jsonData.tracks || []; trackList.forEach((track) => { let trackName = track.name || "Unknown Track"; let trackDuration = track.duration || "PT0S"; totalDuration += parseDuration_applemusic(trackDuration); tracks.push(`[#] ${trackName} [i](${formatDuration_applemusic(trackDuration)})[/i]`); }); } catch (error) { console.error("Error parsing JSON data:", error); } // Generate album description with the custom format let albumDesc = "[align=center][u][b]" + get_title_applemusic(doc) + "[/b]\n" + "[i][size=1]by[/i] [b][/b][/u][/align]\n\n"; albumDesc += `[align=center][u][b]Tracklist[/b][/u][/align]\n`; albumDesc += tracks.join('\n') + `\n[b]Total length[/b]: ${formatTotalDuration_applemusic(totalDuration)}`; return albumDesc; } function get_release_year_applemusic(doc) { let metadataElement = doc.querySelector(".headings__metadata-bottom"); let yearMatch = metadataElement ? metadataElement.textContent.match(/\b(\d{4})\b/) : null; return yearMatch ? yearMatch[1] : ""; } function get_cover_art_applemusic(doc) { console.log("Getting cover art"); // Find the