mirror of
https://github.com/murdos/musicbrainz-userscripts
synced 2024-12-14 12:22:26 +00:00
e801047823
This makes more sense because Harmony also supports Bandcamp lookups, but only by URL and not by UPC. With the extracted UPC, the user can optionally query other sources. Also bump the version and make the linter happy.
415 lines
16 KiB
JavaScript
415 lines
16 KiB
JavaScript
// ==UserScript==
|
|
// @name Import Bandcamp releases to MusicBrainz
|
|
// @description Add a button on Bandcamp's album pages to open MusicBrainz release editor with pre-filled data for the selected release
|
|
// @version 2024.9.6.1
|
|
// @namespace http://userscripts.org/users/22504
|
|
// @downloadURL https://raw.github.com/murdos/musicbrainz-userscripts/master/bandcamp_importer.user.js
|
|
// @updateURL https://raw.github.com/murdos/musicbrainz-userscripts/master/bandcamp_importer.user.js
|
|
// @include /^https?://[^/]+/(?:album|track)/[^/]+\/?$/
|
|
// @include /^https?://web\.archive\.org/web/\d+/https?://[^/]+/(?:album|track)/[^/]+\/?$/
|
|
// @require https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
|
|
// @require lib/mbimport.js
|
|
// @require lib/logger.js
|
|
// @require lib/mblinks.js
|
|
// @require lib/mbimportstyle.js
|
|
// @icon https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/assets/images/Musicbrainz_import_logo.png
|
|
// @grant unsafeWindow
|
|
// @run-at document-start
|
|
// ==/UserScript==
|
|
|
|
// prevent JQuery conflicts, see http://wiki.greasespot.net/@grant
|
|
this.$ = this.jQuery = jQuery.noConflict(true);
|
|
|
|
// eslint-disable-next-line no-global-assign
|
|
if (!unsafeWindow) unsafeWindow = window;
|
|
|
|
String.prototype.fix_bandcamp_url = function () {
|
|
return this.replace('http://', 'https://');
|
|
};
|
|
|
|
const BandcampImport = {
|
|
// Analyze Bandcamp data and return a release object
|
|
retrieveReleaseInfo: function () {
|
|
let bandcampAlbumData = unsafeWindow.TralbumData;
|
|
let bandcampEmbedData = unsafeWindow.EmbedData;
|
|
|
|
let release = {
|
|
discs: [],
|
|
artist_credit: [],
|
|
barcode: '',
|
|
title: '',
|
|
year: 0,
|
|
month: 0,
|
|
day: 0,
|
|
parent_album_url: '',
|
|
labels: [],
|
|
format: 'Digital Media',
|
|
country: 'XW',
|
|
type: '',
|
|
status: 'official',
|
|
packaging: 'None',
|
|
language: 'eng',
|
|
script: 'Latn',
|
|
urls: [],
|
|
url: bandcampAlbumData.url.fix_bandcamp_url(),
|
|
};
|
|
|
|
// Grab release title
|
|
release.title = bandcampAlbumData.current.title;
|
|
|
|
// Grab release event information
|
|
let date = this.convdate(bandcampAlbumData.current.release_date);
|
|
if (date) {
|
|
if (!(date.year > 2008 || (date.year == 2008 && date.month >= 9))) {
|
|
// use publish date if release date is before Bandcamp launch (2008-09)
|
|
let pdate = this.convdate(bandcampAlbumData.current.publish_date);
|
|
if (pdate) {
|
|
date = pdate;
|
|
}
|
|
}
|
|
release.year = date.year;
|
|
release.month = date.month;
|
|
release.day = date.day;
|
|
}
|
|
|
|
// FIXME: implement a mapping between bandcamp release types and MB ones
|
|
if (bandcampAlbumData.current.type == 'track') {
|
|
// map Bandcamp single tracks to singles
|
|
release.type = 'single';
|
|
// if track belongs to an album, get its url.
|
|
if (bandcampEmbedData.album_embed_data) {
|
|
release.parent_album_url = bandcampEmbedData.album_embed_data.linkback.fix_bandcamp_url();
|
|
release.type = 'track'; // <-- no import
|
|
}
|
|
}
|
|
|
|
// Tracks
|
|
let disc = {
|
|
tracks: [],
|
|
format: release.format,
|
|
};
|
|
release.discs.push(disc);
|
|
|
|
// attempt to detect multiple artists tracks
|
|
// bandcamp formats them as 'artist - tracktitle'
|
|
// only set to true if ALL tracks are formatted like this
|
|
// and if string doesn't start with a number (ie. 02 - title)
|
|
let various_artists = true;
|
|
for (let i = 0; i < bandcampAlbumData.trackinfo.length; i++) {
|
|
if (!bandcampAlbumData.trackinfo[i].title.match(/ - /) || bandcampAlbumData.trackinfo[i].title.match(/^\d+ - /)) {
|
|
various_artists = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Release artist credit
|
|
if (bandcampAlbumData.artist.match(/^various(?: artists)?$/i) && various_artists) {
|
|
release.artist_credit = [MBImport.specialArtist('various_artists')];
|
|
} else {
|
|
release.artist_credit = MBImport.makeArtistCredits([bandcampAlbumData.artist]);
|
|
}
|
|
|
|
let tracks_streamable = 0;
|
|
$.each(bandcampAlbumData.trackinfo, function (index, bctrack) {
|
|
let title = bctrack.title;
|
|
let artist = [];
|
|
if (various_artists) {
|
|
let m = bctrack.title.match(/^(.+) - (.+)$/);
|
|
if (m) {
|
|
title = m[2];
|
|
artist = [m[1]];
|
|
}
|
|
}
|
|
if (bctrack.file) tracks_streamable++;
|
|
let track = {
|
|
title: title,
|
|
duration: Math.round(bctrack.duration * 1000),
|
|
artist_credit: MBImport.makeArtistCredits(artist),
|
|
};
|
|
disc.tracks.push(track);
|
|
});
|
|
|
|
// Check for hidden tracks (more tracks in the download than shown for streaming ie.)
|
|
let showntracks = bandcampAlbumData.trackinfo.length;
|
|
let numtracks = -1;
|
|
let nostream = false;
|
|
// album description indicates number of tracks in the download
|
|
let match = /^\d+ track album$/.exec(document.querySelector('meta[property="og:description"]').getAttribute('content'));
|
|
if (match) {
|
|
numtracks = parseInt(match, 10);
|
|
}
|
|
if (numtracks > 0 && numtracks > showntracks) {
|
|
// display a warning if tracks in download differs from tracks shown
|
|
$('h2.trackTitle').append(
|
|
`<p style="font-size: 70%; font-style: italic; margin: 0.1em 0;">Warning: ${numtracks} vs ${showntracks} tracks</p>`
|
|
);
|
|
|
|
// append unknown tracks to the release
|
|
for (let i = 0; i < numtracks - showntracks; i++) {
|
|
let track = {
|
|
title: '[unknown]',
|
|
duration: null,
|
|
artist_credit: [],
|
|
};
|
|
disc.tracks.push(track);
|
|
}
|
|
// disable stream link as only part of the album can be streamed
|
|
nostream = true;
|
|
}
|
|
|
|
// URLs
|
|
let link_type = MBImport.URL_TYPES;
|
|
// Download for free vs. for purchase
|
|
if (bandcampAlbumData.current.download_pref !== null) {
|
|
if (
|
|
bandcampAlbumData.freeDownloadPage !== null ||
|
|
bandcampAlbumData.current.download_pref === 1 ||
|
|
(bandcampAlbumData.current.download_pref === 2 && bandcampAlbumData.current.minimum_price === 0)
|
|
) {
|
|
release.urls.push({
|
|
url: release.url,
|
|
link_type: link_type.download_for_free,
|
|
});
|
|
}
|
|
if (bandcampAlbumData.current.download_pref === 2) {
|
|
release.urls.push({
|
|
url: release.url,
|
|
link_type: link_type.purchase_for_download,
|
|
});
|
|
}
|
|
}
|
|
// Check if the release is streamable
|
|
if (bandcampAlbumData.hasAudio && !nostream && disc.tracks.length > 0 && disc.tracks.length == tracks_streamable) {
|
|
release.urls.push({
|
|
url: release.url,
|
|
link_type: link_type.stream_for_free,
|
|
});
|
|
}
|
|
// Check if release is Creative Commons licensed
|
|
const ccIcons = document.querySelector('div#license a.cc-icons');
|
|
if (ccIcons) {
|
|
release.urls.push({
|
|
url: ccIcons.getAttribute('href'),
|
|
link_type: link_type.license,
|
|
});
|
|
}
|
|
// Check if album has a back link to a label
|
|
let label = this.getlabelname();
|
|
if (label) {
|
|
release.labels.push({
|
|
name: label,
|
|
mbid: '',
|
|
catno: 'none',
|
|
});
|
|
}
|
|
|
|
// UPCs generally apply to physical releases so set the barcode when
|
|
// digital download is the only available medium
|
|
let mediums = bandcampAlbumData.packages;
|
|
let upc = bandcampAlbumData.current.upc;
|
|
if ((mediums === null || mediums.length === 0) && upc !== null) {
|
|
release.barcode = upc;
|
|
}
|
|
|
|
return release;
|
|
},
|
|
|
|
// Insert links in page
|
|
insertLink: function (release) {
|
|
if (release.type == 'track') {
|
|
// only import album or single, tracks belong to an album
|
|
return false;
|
|
}
|
|
// Form parameters
|
|
let edit_note = MBImport.makeEditNote(release.url, 'Bandcamp');
|
|
let parameters = MBImport.buildFormParameters(release, edit_note);
|
|
// Build form
|
|
let mbUI = $(`<div id="mb_buttons">${MBImport.buildFormHTML(parameters)}${MBImport.buildSearchButton(release)}</div>`).hide();
|
|
|
|
// Append MB import link
|
|
$('#name-section').append(mbUI);
|
|
document.querySelector('#mb_buttons').style.marginTop = '6px';
|
|
document.querySelectorAll('form.musicbrainz_import').forEach(form => (form.style.display = 'inline-block'));
|
|
mbUI.slideDown();
|
|
},
|
|
|
|
// helper to convert bandcamp date to MB date
|
|
convdate: function (date) {
|
|
if (typeof date != 'undefined' && date !== '') {
|
|
let d = new Date(date);
|
|
return {
|
|
year: d.getUTCFullYear(),
|
|
month: d.getUTCMonth() + 1,
|
|
day: d.getUTCDate(),
|
|
};
|
|
}
|
|
return false;
|
|
},
|
|
|
|
// get label name from back link if possible
|
|
getlabelname: function () {
|
|
let label = $('a.back-to-label-link span.back-link-text').contents().get(2);
|
|
if (typeof label == 'undefined') {
|
|
return '';
|
|
}
|
|
return label.textContent;
|
|
},
|
|
};
|
|
|
|
if (window.location.hostname === 'web.archive.org') {
|
|
window.addEventListener('beforescriptexecute', function (e) {
|
|
let prev = e.target.previousElementSibling;
|
|
|
|
if (!prev || !prev.src) {
|
|
return;
|
|
}
|
|
|
|
let patchproc = function () {
|
|
if (!window._WBWombat) {
|
|
return;
|
|
}
|
|
|
|
// Patch the Wombat options to exclude Musicbrainz URLs
|
|
let oldWombat = window._WBWombat;
|
|
window._WBWombat = function (wbwindow, wbinfo) {
|
|
wbinfo.wombat_opts.no_rewrite_prefixes.push('https://musicbrainz.org/');
|
|
wbinfo.wombat_opts.no_rewrite_prefixes.push('http://musicbrainz.org/');
|
|
wbinfo.wombat_opts.no_rewrite_prefixes.push('//musicbrainz.org/');
|
|
return oldWombat(wbwindow, wbinfo);
|
|
};
|
|
};
|
|
|
|
// Insert our payload after Wombat's been loaded, but before its initialised
|
|
if (prev.src.search(/\/_static\/js\/wombat.js/) != -1) {
|
|
window.removeEventListener('beforescriptexecute', arguments.callee);
|
|
|
|
let script = document.createElement('script');
|
|
script.type = 'text/javascript';
|
|
script.text = `(${patchproc})()`;
|
|
prev.parentNode.insertBefore(script, e.target);
|
|
}
|
|
});
|
|
}
|
|
|
|
$(document).ready(function () {
|
|
/* keep the following line as first, it is required to skip
|
|
* pages which aren't actually a bandcamp page, since we support
|
|
* bandcamp pages under third-party domains.
|
|
* see @include
|
|
*/
|
|
if (!unsafeWindow.TralbumData) return;
|
|
/***/
|
|
|
|
MBImportStyle();
|
|
|
|
let mblinks = new MBLinks('BCI_MBLINKS_CACHE');
|
|
|
|
let release = BandcampImport.retrieveReleaseInfo();
|
|
|
|
// add MB artist link
|
|
let root_url = release.url.match(/^(https?:\/\/[^/]+)/)[1].split('?')[0];
|
|
let label_url = '';
|
|
|
|
mblinks.searchAndDisplayMbLink(
|
|
root_url,
|
|
'label',
|
|
function (link) {
|
|
$('p#band-name-location span.title').append(link);
|
|
},
|
|
`label:${root_url}`
|
|
);
|
|
const labelback = document.querySelector('a.back-to-label-link');
|
|
if (labelback) {
|
|
const labelbacklink = labelback.getAttribute('href');
|
|
if (labelbacklink) {
|
|
label_url = labelbacklink
|
|
.match(/^(https?:\/\/[^/]+)/)[1]
|
|
.split('?')[0]
|
|
.fix_bandcamp_url();
|
|
mblinks.searchAndDisplayMbLink(
|
|
label_url,
|
|
'label',
|
|
function (link) {
|
|
$('a.back-to-label-link span.back-link-text').append(link);
|
|
},
|
|
`label:${label_url}`
|
|
);
|
|
}
|
|
}
|
|
|
|
if (release.artist_credit.length == 1) {
|
|
// try to get artist's mbid from cache
|
|
let artist_mbid = mblinks.resolveMBID(root_url);
|
|
if (artist_mbid) {
|
|
release.artist_credit[0].mbid = artist_mbid;
|
|
}
|
|
}
|
|
|
|
// try to get label mbid from cache
|
|
let label_mbid = '';
|
|
let label_name = '';
|
|
if (label_url) {
|
|
label_mbid = mblinks.resolveMBID(`label:${label_url}`);
|
|
label_name = BandcampImport.getlabelname();
|
|
} else {
|
|
label_mbid = mblinks.resolveMBID(`label:${root_url}`);
|
|
if (label_mbid) label_name = $('p#band-name-location span.title').text().trim();
|
|
}
|
|
if (label_mbid || label_name) {
|
|
if (release.labels.length == 0) {
|
|
release.labels.push({
|
|
name: '',
|
|
mbid: '',
|
|
catno: 'none',
|
|
});
|
|
}
|
|
release.labels[0].name = label_name;
|
|
release.labels[0].mbid = label_mbid;
|
|
}
|
|
|
|
BandcampImport.insertLink(release);
|
|
LOGGER.info('Parsed release: ', release);
|
|
|
|
if (release.type == 'track') {
|
|
mblinks.searchAndDisplayMbLink(root_url, 'artist', function (link) {
|
|
$('div#name-section h3 span:last').before(link);
|
|
});
|
|
// add MB links to parent album
|
|
mblinks.searchAndDisplayMbLink(release.parent_album_url, 'release', function (link) {
|
|
$('div#name-section h3 span:first').before(link);
|
|
});
|
|
} else {
|
|
mblinks.searchAndDisplayMbLink(root_url, 'artist', function (link) {
|
|
$('div#name-section h3 span:first').before(link);
|
|
});
|
|
// add MB release links to album or single
|
|
mblinks.searchAndDisplayMbLink(release.url, 'release', function (link) {
|
|
$('div#name-section h3 span:first').after(link);
|
|
});
|
|
}
|
|
|
|
// append a comma after each tag to ease cut'n'paste to MB
|
|
$('div.tralbum-tags a:not(:last-child).tag').after(', ');
|
|
|
|
// append a link to the full size image
|
|
const tralbumArt = document.querySelector('div#tralbumArt');
|
|
const fullsizeimageurl = tralbumArt.querySelector('a').getAttribute('href').replace('_10', '_0');
|
|
tralbumArt.insertAdjacentHTML(
|
|
'afterend',
|
|
`<div id='bci_link'><a class='custom-color' href='${fullsizeimageurl}' title='Open original image in a new tab (Bandcamp importer)' target='_blank'>Original image</a></div>`
|
|
);
|
|
|
|
const bci_link = document.querySelector('div#bci_link');
|
|
bci_link.style.paddingTop = '0.5em';
|
|
bci_link.style.textAlign = 'right';
|
|
bci_link.querySelector('a').style.fontWeight = 'bold';
|
|
const upc = unsafeWindow.TralbumData.current.upc;
|
|
if (typeof upc != 'undefined' && upc !== null) {
|
|
document.querySelector('div #trackInfoInner').insertAdjacentHTML(
|
|
'beforeend',
|
|
`<div id="mbimport_upc" style="margin-bottom: 2em; font-size: smaller;">UPC: ${upc}<br/>
|
|
Import: <a href="https://harmony.pulsewidth.org.uk/release?url=${encodeURIComponent(release.url)}">Harmony</a>
|
|
| <a href="https://atisket.pulsewidth.org.uk/?upc=${upc}">a-tisket</a></div>`
|
|
);
|
|
}
|
|
});
|