您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows Bandcamp releases' real "publish date" below the listed release date
// ==UserScript== // @name Bandcamp: Show more dates // @namespace https://musicbrainz.org/user/chaban // @version 2.0 // @description Shows Bandcamp releases' real "publish date" below the listed release date // @tag ai-created // @author w_biggs (~joks), chaban // @license MIT // @match https://*.bandcamp.com/track/* // @match https://*.bandcamp.com/album/* // @include /^https?://web\.archive\.org/web/\d+/https?://[^/]+/(?:album|track)/[^/]+\/?$/ // @grant none // ==/UserScript== (function() { 'use strict'; /** * Formats a date string into an ISO 8601 date string (YYYY-MM-DD). * Includes validation to ensure the date is valid. * @param {string} dateString - The date string to format. * @returns {string|null} The formatted ISO 8601 date string, or null if input is invalid or date is unparseable. */ function formatDate(dateString) { if (!dateString) { return null; } const date = new Date(dateString); if (isNaN(date.getTime())) { console.warn(`Invalid date string provided to formatDate: "${dateString}"`); return null; } return date.toISOString().slice(0, 10); } /** * Parses date information from a script element based on its type. * @param {HTMLScriptElement} scriptElement - The script element to parse. * @returns {Object} An object containing extracted raw date strings (or null if not found). */ function parseScriptData(scriptElement) { const rawDates = {}; if (scriptElement.type === 'application/ld+json') { try { const jsonld = JSON.parse(scriptElement.innerText); rawDates.ldPublished = jsonld?.datePublished; rawDates.ldModified = jsonld?.dateModified; } catch (e) { console.error('Error parsing JSON-LD from script element:', scriptElement, e); } } else if (scriptElement.hasAttribute('data-tralbum')) { try { const tralbumContent = scriptElement.getAttribute('data-tralbum'); const jsonalbum = JSON.parse(tralbumContent); rawDates.tralbumPublish = jsonalbum.current?.publish_date; rawDates.tralbumModified = jsonalbum.current?.mod_date; rawDates.tralbumNew = jsonalbum.current?.new_date; rawDates.tralbumRelease = jsonalbum.current?.release_date; const embedContent = scriptElement.getAttribute('data-embed'); const jsonembed = embedContent ? JSON.parse(embedContent) : null; if (typeof jsonembed?.embed_info?.public_embeddable === 'string' && !isNaN(new Date(jsonembed.embed_info.public_embeddable))) { rawDates.embeddable = jsonembed.embed_info.public_embeddable; } } catch (e) { console.error('Error parsing data-tralbum or data-embed attributes from script element:', scriptElement, e); } } return rawDates; } /** * Main function to collect and display all unique date information. */ function displayAllDates() { const creditsElement = document.querySelector('div.tralbum-credits'); if (!creditsElement) { console.debug('Target div.tralbum-credits not found.'); return; } const allRawDates = {}; const allRelevantScripts = document.querySelectorAll('script[type="application/ld+json"], script[data-tralbum]'); allRelevantScripts.forEach(scriptElement => { const currentScriptDates = parseScriptData(scriptElement); Object.assign(allRawDates, currentScriptDates); }); // Use a Map to store unique formatted dates along with their associated labels, sources, and explanations. // The key will be the formatted date string, and the value will be an Array of objects {text: string, title: string}. const consolidatedDates = new Map(); // Map<formattedDateString, Array<{text: string, title: string}>> const datePriorities = [ { key: 'tralbumRelease', label: 'released', source: 'release_date', explanation: 'The official release date set by the artist.' }, { key: 'tralbumPublish', label: 'published', source: 'publish_date', explanation: 'The actual date the release became public on Bandcamp.' }, { key: 'ldPublished', label: 'published', source: 'datePublished', explanation: 'The actual date the release became public on Bandcamp (Schema.org).' }, { key: 'tralbumNew', label: 'created', source: 'new_date', explanation: 'The date the album/track entry was first saved as a draft.' }, { key: 'tralbumModified', label: 'modified', source: 'mod_date', explanation: 'The last date any changes were saved to the release page.' }, { key: 'ldModified', label: 'modified', source: 'dateModified', explanation: 'The last date any changes were saved to the release page (Schema.org).' }, { key: 'embeddable', label: 'embeddable', source: 'public_embeddable', explanation: 'The date the release became publicly embeddable.' } ]; datePriorities.forEach(({ key, label, source, explanation }) => { const rawDate = allRawDates[key]; if (rawDate) { const formattedDate = formatDate(rawDate); if (formattedDate) { if (!consolidatedDates.has(formattedDate)) { consolidatedDates.set(formattedDate, []); } consolidatedDates.get(formattedDate).push({ text: `${label} (${source})`, title: explanation }); } } }); const finalDateLines = []; const scriptAddedParagraphs = creditsElement.querySelectorAll('p[data-userscript-added]'); scriptAddedParagraphs.forEach(p => p.remove()); const sortedFormattedDates = Array.from(consolidatedDates.keys()).sort((a, b) => { const dateA = new Date(a); const dateB = new Date(b); return dateA.getTime() - dateB.getTime(); }); const outputParagraph = document.createElement('p'); outputParagraph.setAttribute('data-userscript-added', 'true'); sortedFormattedDates.forEach((formattedDate, dateIndex) => { let descriptions = consolidatedDates.get(formattedDate); if (descriptions.length > 0) { outputParagraph.appendChild(document.createTextNode(`${formattedDate}: `)); descriptions.forEach((desc, descIndex) => { const descSpan = document.createElement('span'); descSpan.textContent = desc.text; descSpan.title = desc.title; outputParagraph.appendChild(descSpan); if (descIndex < descriptions.length - 1) { outputParagraph.appendChild(document.createTextNode('; ')); } }); if (dateIndex < sortedFormattedDates.length - 1) { outputParagraph.appendChild(document.createElement('br')); } } }); if (outputParagraph.hasChildNodes()) { creditsElement.appendChild(outputParagraph); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', displayAllDates); } else { displayAllDates(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址