Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ Add a button on Bandcamp's album pages to open MusicBrainz release editor with p
[![Source](assets/buttons/button-source.svg)](https://github.com/murdos/musicbrainz-userscripts/blob/master/bandcamp_importer.user.js)
[![Install](assets/buttons/button-install.svg)](https://raw.github.com/murdos/musicbrainz-userscripts/master/bandcamp_importer.user.js)

## <a name="ampwall_importer"></a> Import Ampwall releases to MusicBrainz

Add a button on Ampwall's album pages to open MusicBrainz release editor with pre-filled data for the selected release

[![Source](assets/buttons/button-source.svg)](https://github.com/murdos/musicbrainz-userscripts/blob/master/ampwall_importer.user.js)
[![Install](assets/buttons/button-install.svg)](https://raw.github.com/murdos/musicbrainz-userscripts/master/ampwall_importer.user.js)

## <a name="beatport_importer"></a> <img src="assets/icons/typescript.svg" alt="TypeScript" width="16" height="16"> Import Beatport releases to MusicBrainz

One-click importing of releases from beatport.com/release pages into MusicBrainz
Expand Down Expand Up @@ -139,6 +146,13 @@ Add a button on Qobuz's album pages to open MusicBrainz release editor with pre-
[![Source](assets/buttons/button-source.svg)](https://github.com/murdos/musicbrainz-userscripts/blob/master/qobuz_importer.user.js)
[![Install](assets/buttons/button-install.svg)](https://raw.github.com/murdos/musicbrainz-userscripts/master/qobuz_importer.user.js)

## <a name="subvert_importer"></a> Import Subvert.fm releases to MusicBrainz

Add a button on Subvert.fm's album pages to open MusicBrainz release editor with pre-filled data for the selected release

[![Source](assets/buttons/button-source.svg)](https://github.com/murdos/musicbrainz-userscripts/blob/master/subvert_importer.user.js)
[![Install](assets/buttons/button-install.svg)](https://raw.github.com/murdos/musicbrainz-userscripts/master/subvert_importer.user.js)

## <a name="takealot_importer"></a> Import Takealot releases to MusicBrainz

Add a button to import https://www.takealot.com/ releases to MusicBrainz via API
Expand Down
145 changes: 145 additions & 0 deletions ampwall_importer.user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// ==UserScript==
// @name Import Ampwall releases to Musicbrainz
// @description Add a button on Ampwall release pages to open MusicBrainz release editor with pre-filled data for the selected release
// @version 2026.6.20
// @license X11
// @downloadURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/ampwall_importer.user.js
// @updateURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/ampwall_importer.user.js
// @namespace https://github.com/murdos/musicbrainz-userscripts
// @match https://ampwall.com/a/*/album/*
// @require lib/mbimport.js
// @require lib/logger.js
// @require lib/mbimportstyle.js
// ==/UserScript==

function onLoad() {
MBImportStyle();
const release = retrieveReleaseInfo();
insertLink(release, window.location);
}

function getArtistCredit(json) {
return MBImport.makeArtistCredits([json['byArtist']['name']]);
}

function getFormat() {
// We only support digital downloads right now.
return 'Digital Media';
}

function getPackaging() {
// We only support digital downloads right now.
return 'None';
}

function getReleaseType() {
// It appears that Ampwall treats all releases as albums so just
// return nothing for now and let the user decide.
return '';
}

function getTitle(json) {
return json['albumRelease'][0]['name'];
}

function getLabels() {
// TODO this will probably explode on releases with multiple labels.
const labels = document.querySelector('div[data-testid=labels-links] a');
if (labels === null) {
return [];
}
return [
{
name: labels.textContent,
catno: '',
},
];
}

function getDiscs(json) {
let tracks = [];
json['track']['itemListElement'].forEach(function (track) {
tracks.push({
artist_credit: MBImport.makeArtistCredits([json['byArtist']['name']]),
title: track['name'],
duration: parseDuration(track['duration']),
});
});
return [
{
tracks: tracks,
format: getFormat(json),
},
];
}

function getURLs(json) {
return [
{
url: json['url'],
link_type: MBImport.URL_TYPES.purchase_for_download,
},
];
}

function parseDuration(s) {
// Durations are returned in the format "PT0H0M7S". Strip out the
// leading/trailing characters and convert the rest to colons.
return s.replace(/(^PT)|(S$)/g, '').replace(/[HM]/g, ':');
}

function retrieveReleaseInfo() {
const json = JSON.parse(document.querySelector("script[type='application/ld+json']").textContent);
const releaseDate = new Date(json['datePublished']);
const release = {
artist_credit: getArtistCredit(json),
title: getTitle(json),
year: releaseDate.getUTCFullYear(),
month: releaseDate.getUTCMonth() + 1,
day: releaseDate.getUTCDate(),
country: 'XW',
status: 'official',
language: 'eng',
script: 'Latn',
type: getReleaseType(json),
urls: getURLs(json),
labels: getLabels(),
discs: getDiscs(json),
packaging: getPackaging(),
format: getFormat(),
};
LOGGER.info('Parsed release: ', release);
return release;
}

// Insert button into page
function insertLink(release) {
const edit_note = MBImport.makeEditNote(window.location, 'Ampwall');
const div = document.createElement('div');

const formButton = document.createElement('span');
formButton.className = 'tab-title musicbrainz-import';
formButton.innerHTML = MBImport.buildFormHTML(MBImport.buildFormParameters(release, edit_note));
formButton.style.display = 'inline-block';

const searchButton = document.createElement('span');
searchButton.className = 'tab-title musicbrainz-import';
searchButton.innerHTML = MBImport.buildSearchButton(release);
searchButton.style.display = 'inline-block';

div.appendChild(formButton);
div.appendChild(searchButton);

// Set a slight delay to stop breaking React/Next.
window.setTimeout(function () {
const container = document.querySelector('div.d_flex.ai_center.flex-wrap_wrap.gap_space1');
container.appendChild(formButton);
container.appendChild(searchButton);
}, 2000);
}

if (document.readyState !== 'loading') {
onLoad();
} else {
document.addEventListener('DOMContentLoaded', onLoad);
}
149 changes: 149 additions & 0 deletions subvert_importer.user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// ==UserScript==
// @name Import Subvert.fm releases to Musicbrainz
// @description Add a button on Subvert.fm release pages to open MusicBrainz release editor with pre-filled data for the selected release
// @version 2026.6.20
// @license X11
// @downloadURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/subvert_importer.user.js
// @updateURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/subvert_importer.user.js
// @namespace https://github.com/murdos/musicbrainz-userscripts
// @match https://subvert.fm/*/*
// @match https://www.subvert.fm/*/*
// @require lib/mbimport.js
// @require lib/logger.js
// @require lib/mbimportstyle.js
// ==/UserScript==

function onLoad() {
MBImportStyle();
const release = retrieveReleaseInfo();
insertLink(release, window.location);
}

function getArtistCredit(data) {
let artists = [];
data['release']['artists'].forEach(function (artist) {
artists.push(artist['name']);
});
return MBImport.makeArtistCredits(artists);
}

function getFormat() {
// We only support digital downloads right now.
return 'Digital Media';
}

function getPackaging() {
// We only support digital downloads right now.
return 'None';
}

function getReleaseType(data) {
return data['release']['releaseType'];
}

function getTitle(data) {
return data['release']['name'];
}

function getLabels(data) {
let labels = [];
data['release']['labelsOnReleases'].forEach(function (label) {
labels.push({
label: label['label']['name'],
catno: label['catalogNumber'],
});
});
return labels;
}

function getDiscs(data) {
let tracks = [];
data['release']['tracks'].forEach(function (track) {
let artists = [];
track['track']['artists'].forEach(function (artist) {
artists.push(artist['name']);
});
tracks.push({
artist_credit: MBImport.makeArtistCredits(artists),
title: track['track']['name'],
duration: track['track']['duration'] * 1000,
});
});
return [
{
tracks: tracks,
format: getFormat(data),
},
];
}

function getURLs(data) {
let urls = [];
if (data['release']['isDownloadAllowed']) {
urls.push({
url: window.location,
link_type: MBImport.URL_TYPES.purchase_for_download,
});
}
if (data['release']['isStreamingAllowed']) {
urls.push({
url: window.location,
link_type: MBImport.URL_TYPES.stream_for_free,
});
}
if (data['release']['priceCents'] > 0) {
urls.push({
url: window.location,
link_type: MBImport.URL_TYPES.download_for_free,
});
}
return urls;
}

function retrieveReleaseInfo() {
let json = JSON.parse(document.querySelector('script#__NEXT_DATA__').textContent);

// Skip non-release pages
if (json['page'] !== '/[artistSlug]/[releaseSlug]') {
return;
}

const data = json['props']['pageProps'];
const releaseDate = new Date(data['release']['releaseDate']);
const release = {
artist_credit: getArtistCredit(data),
title: getTitle(data),
year: releaseDate.getUTCFullYear(),
month: releaseDate.getUTCMonth() + 1,
day: releaseDate.getUTCDate(),
country: 'XW',
status: 'official',
language: 'eng',
script: 'Latn',
type: getReleaseType(data),
urls: getURLs(data),
labels: getLabels(data),
discs: getDiscs(data),
packaging: getPackaging(data),
format: getFormat(data),
};
LOGGER.info('Parsed release: ', release);
return release;
}

// Insert button into page
function insertLink(release, url) {
const edit_note = MBImport.makeEditNote(url, 'Subvert');
const container = document.querySelector('div.releaseRailMetaWrapper.leftRail.hideMobile');
const sourceNode = container.children[container.children.length - 3];
const importContainer = sourceNode.cloneNode(true);
importContainer.children[0].innerHTML = MBImport.buildFormHTML(MBImport.buildFormParameters(release, edit_note));
importContainer.children[1].innerHTML = MBImport.buildSearchButton(release);
sourceNode.after(importContainer);
}

if (document.readyState !== 'loading') {
onLoad();
} else {
document.addEventListener('DOMContentLoaded', onLoad);
}
Loading