您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Standalone script for extracting release info and tracklist from Bandcamp (2024+ compatible). Outputs formatted text in a textbox at the top.
// ==UserScript== // @name release:txt [Bandcamp 2025 Standalone] // @namespace http://userscripts-mirror.org/scripts/show/156420 // @version 2024.06.29.07 // @description Standalone script for extracting release info and tracklist from Bandcamp (2024+ compatible). Outputs formatted text in a textbox at the top. // @author nj4442 + original author DMBoxer // @match http*://*.bandcamp.com/* // @license CC-BY-4.0 // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ==== CONFIGURATION ==== // Export header format: ARTIST - TITLE (YEAR) [LABEL] var exportHeaderFormat = '%artist% - %title% (%year%) [%label%]'; var sectionLineSeparator = '_'; var textWidth = 90; // ==== UTILITIES ==== function tidyline(s) { return s ? s.replace(/[\s\xA0\u200e]+/g, ' ').trim() : ''; } function headerline(title, toLength, sepChar) { toLength = toLength || textWidth; sepChar = sepChar || sectionLineSeparator; var filler = new Array(Math.max(0, toLength - (title ? title.length + 1 : 0))).join(sepChar); return (title ? title + ' ' : '') + filler; } function filesystemsafe(s) { return s.replace(/[\/\\|]/g, ', ') .replace(/:/g, ';') .replace(/\?/g, '¿') .replace(/"/g, "'") .replace(/[\*<>]/g, '_'); } // ==== MODELS ==== function Track() { this.number = ''; this.artist = ''; this.title = ''; this.time = ''; this.bpm = ''; this.credits = ''; this.release = ''; this.label = ''; } function Section(title, content) { this.title = title || ''; this.content = content || ''; } function Release() { this.artist = ''; this.title = ''; this.by = ''; this.label = ''; this.catalog = ''; this.released = ''; this.format = ''; this.tracks = ''; this.country = ''; this.genre = ''; this.style = ''; this.duration = ''; this.tags = ''; this.bandcamp = ''; this.tracklist = []; this.description = []; Object.defineProperty(this, 'year', { enumerable: true, get: function () { var rlsYear = (this.released || '').match(/[\d]{4}/); return (rlsYear === null) ? '' : rlsYear[0]; }}); } // ==== PROTOTYPE FORMATTERS ==== Track.prototype.TXT = function(fieldsSize, skipartist) { skipartist = skipartist || false; var spaceToTrack = ((this.time === '') ? 0 : fieldsSize.time + 3) + ((this.number === '') ? 0 : fieldsSize.number + 2); return ((this.time === '') ? '' : '[' + this.time + '] ') + ((this.number === '') ? '' : this.number + '. ') + ((skipartist || this.artist === '') ? '' : this.artist + ' - ') + ((this.title === '') ? 'unknown' : this.title); }; Release.prototype.TXT_tracklist = function() { var trklistTXT = '', trklist = this.tracklist, t, trk = new Track(), trksfieldsize = {time:0, number:0, artist:0, title:0}, k; for (t = 0; t < trklist.length; t++) { trk = trklist[t]; trksfieldsize.time = Math.max(trksfieldsize.time, (trk.time||'').length); trksfieldsize.number = Math.max(trksfieldsize.number, (trk.number||'').length); trksfieldsize.artist = Math.max(trksfieldsize.artist, (trk.artist||'').length); trksfieldsize.title = Math.max(trksfieldsize.title, (trk.title||'').length); } var rlsartist = (this.artist||'').toLowerCase(); var isSingleArtist = !this.tracklist.some(function(trk) { return (trk.artist||'').toLowerCase() !== rlsartist; }); for (t = 0; t < trklist.length; t++) { trklistTXT += trklist[t].TXT(trksfieldsize, isSingleArtist) + '\n'; } return trklistTXT.trim(); }; // Custom top header formatter Release.prototype.TXT_header = function() { var header = exportHeaderFormat; var keys = { artist: this.artist || this.by || '', title: this.title || '', year: this.year || '', label: this.label || '' }; Object.keys(keys).forEach(function(key) { header = header.replace(new RegExp('%' + key + '%', 'ig'), keys[key].replace(/\s+/g, ' ').trim()); }); // Collapse extra spaces return filesystemsafe(header).replace(/\s{2,}/g, ' ').trim(); }; Release.prototype.TXT_oneliner = function() { // No longer needed for top, but keep for reference/legacy var line = '%artist% - %title% (by %by%) [%label%] (%year%)', attrib = '', keys = Object.keys(this); for (var k = 0; k < keys.length; k++) { if (typeof this[keys[k]] === 'string' && this[keys[k]]) { attrib = this[keys[k]].replace(/\s+/g, ' ').trim(); line = line.replace(new RegExp('%' + keys[k] + '%', 'ig'), attrib); } else { line = line.replace(new RegExp('%' + keys[k] + '%', 'ig'), ''); } } line = line.replace(/\(by \)/g, '').replace(/ +\]/g, ']').replace(/\[ +/g, '[') .replace(/ +\)/g, ')').replace(/\( +/g, '(') .replace(/\[ *\]/g, '').replace(/\( *\)/g, '') .replace(/(^ *\- *|\- *\-| *\- *$)/g, '').replace(/ +/g, ' '); return filesystemsafe(line); }; Release.prototype.TXT_profile = function() { let profile = ''; const keys = Object.keys(this); let keysmaxlenght = 0; for (let k = 0; k < keys.length; k++) { if (typeof this[keys[k]] === 'string' && keys[k].length > keysmaxlenght) { keysmaxlenght = keys[k].length; } } for (let k = 0; k < keys.length; k++) { // Only include if value is non-empty and not 'year' if (typeof this[keys[k]] === 'string' && keys[k] !== 'year' && this[keys[k]]) { // Remove leading/trailing spaces, and collapse multiple spaces let value = this[keys[k]].replace(/\s+/g, ' ').trim(); profile += keys[k].replace(/_/g, ' ').padEnd(keysmaxlenght + 1, ' ') + ': ' + value + '\n'; } } return profile.trim(); }; Release.prototype.TXT = function() { var rlsTXT = '', d = 0; rlsTXT = this.TXT_header() + '\n'; rlsTXT += headerline('') + '\n\n'; rlsTXT += this.TXT_profile() + '\n'; if (this.tracklist.length > 0) { rlsTXT += '\n' + headerline('Tracklist') + '\n\n'; rlsTXT += this.TXT_tracklist() + '\n'; } for (d = 0; d < this.description.length; d++) { let descContent = this.description[d].content; // Clean up extra blank lines descContent = descContent.replace(/\n{2,}/g, '\n\n').trim(); rlsTXT += '\n' + headerline(this.description[d].title) + '\n\n'; rlsTXT += descContent + '\n'; } rlsTXT += '\n' + headerline('__ generated by release:txt') + '\n'; return rlsTXT.trim(); }; // ==== BANDCAMP EXTRACTION ==== function get_bandcamp_release(htmldoc) { var rls = new Release(); // Title and Artist rls.title = htmldoc.querySelector('#name-section .trackTitle')?.textContent.trim() || ''; rls.by = htmldoc.querySelector('#name-section h3 a')?.textContent.trim() || ''; // Label/Publisher rls.label = htmldoc.querySelector('#bio-container #band-name-location .title')?.textContent.trim() || ''; // Release Date (extract only the date, not credits/description) var creditsElem = htmldoc.querySelector('#trackInfoInner .tralbum-credits'); if (creditsElem) { let creditsText = creditsElem.textContent.trim(); // Look for "released Month DD, YYYY" let releasedMatch = creditsText.match(/released\s+([A-Za-z]+\s+\d{1,2},\s+\d{4})/i); if (releasedMatch) { rls.released = releasedMatch[1]; } else { // fallback: just look for Month DD, YYYY anywhere let fallbackMatch = creditsText.match(/([A-Za-z]+\s+\d{1,2},\s+\d{4})/); rls.released = fallbackMatch ? fallbackMatch[1] : ''; } } else { rls.released = ''; } // Format var formatElem = htmldoc.querySelector('#trackInfoInner .buyItemPackageTitle'); rls.format = formatElem ? formatElem.textContent.trim() : ''; // Tags var tagsElem = htmldoc.querySelector('.tralbum-tags'); if (tagsElem) { rls.tags = Array.from(tagsElem.querySelectorAll('a.tag')).map(a => a.textContent.trim()).join(', '); } // Description/About var aboutElem = htmldoc.querySelector('#trackInfoInner .tralbum-about'); var rlsDescriptionSection = new Section(); rlsDescriptionSection.title = 'Description'; rlsDescriptionSection.content = ''; if (aboutElem) rlsDescriptionSection.content += aboutElem.textContent.trim(); // Add other credits except the release date if (creditsElem && creditsElem.textContent.trim()) { let creditsText = creditsElem.textContent.trim(); // Remove the "released Month DD, YYYY" from the start, if present let extraCredits = creditsText.replace(/^released\s+[A-Za-z]+\s+\d{1,2},\s+\d{4}[.,;:! ]*/i, '').trim(); if (extraCredits) { rlsDescriptionSection.content += (rlsDescriptionSection.content ? '\n\n' : '') + extraCredits; } } // Label bio and links var bandBioElem = htmldoc.querySelector('#bio-container .signed-out-artists-bio-text'); if (bandBioElem && bandBioElem.textContent.trim()) { rlsDescriptionSection.content += '\n\n' + bandBioElem.textContent.trim(); } var bandLinks = htmldoc.querySelectorAll('#bio-container #band-links li a'); if (bandLinks.length) { rlsDescriptionSection.content += '\n\nLinks:\n' + Array.from(bandLinks).map(a => a.href).join('\n'); } // Clean up extra blank lines in description content if (rlsDescriptionSection.content) rlsDescriptionSection.content = rlsDescriptionSection.content.replace(/\n{2,}/g, '\n\n').trim(); if (rlsDescriptionSection.content) rls.description.push(rlsDescriptionSection); // Tracklist var trackRows = htmldoc.querySelectorAll('#track_table tr.track_row_view'); trackRows.forEach(function(row) { var trk = new Track(); var numElem = row.querySelector('.track_number'); trk.number = numElem ? numElem.textContent.trim().replace(/\.$/, '') : ''; var titleElem = row.querySelector('.track-title'); trk.title = titleElem ? titleElem.textContent.trim() : ''; var timeElem = row.querySelector('.time.secondaryText'); trk.time = timeElem ? timeElem.textContent.trim() : ''; rls.tracklist.push(trk); }); // Bandcamp URL rls.bandcamp = htmldoc.URL; return rls; } // ==== UI ==== function buildUI() { var htmldoc = window.top.document; // Remove old UI if present var old = htmldoc.getElementById('releaseTXT_header'); if (old) old.remove(); var UIcontainer = htmldoc.createElement('div'); UIcontainer.id = 'releaseTXT_header'; UIcontainer.style.cssText = 'position: fixed; z-index: 9999; top: 0; left: 0; margin-top: 0; height: 28px; width: 100%; background: #222; color: #fff; box-shadow:0 2px 4px #0007;'; htmldoc.body.insertBefore(UIcontainer, htmldoc.body.firstChild); var div = htmldoc.createElement('div'); div.style.cssText = 'margin:0 auto; height:24px; width:990px;'; UIcontainer.appendChild(div); var gettxt = htmldoc.createElement('input'); gettxt.type = 'button'; gettxt.id = 'releaseTXT_button'; gettxt.value = 'release:txt'; gettxt.style.cssText = 'margin:2px 1px 2px 10px;padding:0 5px;height:20px;font-family:verdana;font-size:10px;background:#333;color:#fff;border:1px solid #888;border-radius:6px;'; div.appendChild(gettxt); var txtbox = htmldoc.createElement('textarea'); txtbox.id = 'releaseTXT_txtbox'; txtbox.value = 'click to get the text version of this release...'; txtbox.spellcheck = false; txtbox.style.cssText = 'margin:2px 1px;padding:2px 1px 1px 5px;min-height:15px;height:15px;width:750px;font-family:monospace;font-size:12px;line-height:15px;vertical-align:top;resize:both;overflow:auto;border:solid 1px #d7d7d7;border-radius:6px;box-shadow:inset 1px 1px 3px 0px #333;color:#000;background:#fff;'; div.appendChild(txtbox); gettxt.addEventListener('click', function () { main(); }, false); } // ==== MAIN FUNCTION ==== function main() { var htmldoc = window.top.document; var txtbox = htmldoc.getElementById('releaseTXT_txtbox'); txtbox.value = 'page loading...'; txtbox.style.background='#FFD700'; try { var rls = get_bandcamp_release(htmldoc); var outtxt = rls.TXT(); txtbox.value = outtxt; txtbox.style.background='#fff'; } catch(e) { txtbox.value = 'Could not collect the data for this release. Error: ' + e; txtbox.style.background='#fdd'; } } // ==== INIT ==== function isBandcampReleasePage() { return !!document.querySelector('#name-section .trackTitle'); } if (isBandcampReleasePage()) { buildUI(); // Optionally auto-trigger extraction: // setTimeout(main, 1000); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址