diff --git a/deezer_importer.user.js b/deezer_importer.user.js index 6e52286..1438a60 100644 --- a/deezer_importer.user.js +++ b/deezer_importer.user.js @@ -1,159 +1,165 @@ // ==UserScript== -// @name Import Deezer releases into MusicBrainz +// @name Import Deezer releases into MusicBrainz (API & UI Fix) // @namespace https://github.com/murdos/musicbrainz-userscripts/ -// @description One-click importing of releases from deezer.com into MusicBrainz -// @version 2025.9.28 +// @description One-click importing of releases from deezer.com into MusicBrainz using the Deezer API +// @version 2026.02.03.2 // @downloadURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/deezer_importer.user.js // @updateURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/deezer_importer.user.js -// @match https://www.deezer.com/*/album/* +// @match https://www.deezer.com/* // @require https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js -// @require lib/mbimport.js -// @require lib/logger.js -// @require lib/mbimportstyle.js +// @require https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/lib/mbimport.js +// @require https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/lib/logger.js +// @require https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/lib/mbimportstyle.js // @icon https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/assets/images/Musicbrainz_import_logo.png // @grant GM_xmlhttpRequest -// @grant GM.xmlHttpRequest // ==/UserScript== -// prevent JQuery conflicts, see http://wiki.greasespot.net/@grant this.$ = this.jQuery = jQuery.noConflict(true); -$(document).ready(function () { - let gmXHR; +(function () { + 'use strict'; - if (typeof GM_xmlhttpRequest != 'undefined') { - gmXHR = GM_xmlhttpRequest; - } else if (GM.xmlHttpRequest != 'undefined') { - gmXHR = GM.xmlHttpRequest; - } else { - LOGGER.error('Userscript requires GM_xmlHttpRequest or GM.xmlHttpRequest'); - return; + // --- Helpers --- + function getAlbumId() { + const match = window.location.pathname.match(/\/album\/(\d+)/); + return match ? match[1] : null; } - // allow 1 second for Deezer SPA to initialize - window.setTimeout(function () { - MBImportStyle(); - let releaseUrl = window.location.href.replace(/\?.*$/, '').replace(/#.*$/, ''); - let releaseId = releaseUrl.replace(/^https?:\/\/www\.deezer\.com\/[^/]+\/album\//i, ''); - let deezerApiUrl = `https://api.deezer.com/album/${releaseId}`; - - gmXHR({ - method: 'GET', - url: deezerApiUrl, - onload: function (resp) { - try { - let release = parseDeezerRelease(releaseUrl, JSON.parse(resp.responseText)); - insertLink(release, releaseUrl); - } catch (e) { - LOGGER.error('Failed to parse release: ', e); + function parseDuration(duration) { + return duration * 1000; + } + + // --- Main Logic --- + async function fetchAndImport(albumId) { + const apiUrl = `https://api.deezer.com/album/${albumId}`; + + GM_xmlhttpRequest({ + method: "GET", + url: apiUrl, + onload: function (response) { + if (response.status === 200) { + const data = JSON.parse(response.responseText); + if (data.error) return; + renderButton(data); } - }, - onerror: function (resp) { - LOGGER.error('AJAX status:', resp.status); - LOGGER.error('AJAX response:', resp.responseText); - }, + } }); - }, 1000); -}); - -function parseDeezerRelease(releaseUrl, data) { - let releaseDate = data.release_date.split('-'); - - let release = { - artist_credit: [], - title: data.title, - year: releaseDate[0], - month: releaseDate[1], - day: releaseDate[2], - packaging: 'None', - country: 'XW', - status: 'official', - language: 'eng', - script: 'Latn', - type: '', - urls: [], - labels: [], - discs: [], - }; - - $.each(data.contributors, function (index, artist) { - if (artist.role != 'Main') return true; - - let ac = { - artist_name: artist.name, - joinphrase: index == data.contributors.length - 1 ? '' : ', ', + } + + function renderButton(data) { + const release = { + title: data.title, + artist_credit: MBImport.makeArtistCredits([data.artist.name]), + type: data.record_type === 'single' ? 'single' : 'album', + status: 'official', + language: 'eng', + script: 'Latn', + barcode: data.upc, + labels: data.label ? [{ name: data.label, catno: '' }] : [], + urls: [{ url: data.link, link_type: MBImport.URL_TYPES.stream_for_free }], + discs: [] }; - if (artist.name == 'Various Artists') { - ac = MBImport.specialArtist('various_artists', ac); + if (data.release_date) { + const dateParts = data.release_date.split('-'); + release.year = dateParts[0]; + release.month = dateParts[1]; + release.day = dateParts[2]; } - release.artist_credit.push(ac); - }); + const tracks = data.tracks.data; + const discs = {}; - let disc = { - format: 'Digital Media', - title: '', - tracks: [], - }; - - $.each(data.tracks.data, function (index, track) { - let t = { - number: index + 1, - title: track.title_short, - duration: track.duration * 1000, - artist_credit: [], - }; + tracks.forEach(track => { + const discNum = track.disk_number || 1; + if (!discs[discNum]) { + discs[discNum] = { tracks: [], format: 'Digital Media' }; + } - // ignore pointless "(Original Mix)" in title version - if (track.title_version && !track.title_version.match(/^\s*\(Original Mix\)\s*$/i)) { - t.title += ` ${track.title_version}`; - } + const trackObj = { + title: track.title, + duration: parseDuration(track.duration), + artist_credit: [] + }; - t.artist_credit.push({ artist_name: track.artist.name }); + if (track.artist.name !== data.artist.name && data.artist.name === "Various Artists") { + trackObj.artist_credit = MBImport.makeArtistCredits([track.artist.name]); + } - disc.tracks.push(t); - }); + if (trackObj.title.match(/\(Original Mix\)/i)) { + trackObj.title = trackObj.title.replace(/\s*\(Original Mix\)\s*/i, ""); + } + + discs[discNum].tracks.push(trackObj); + }); - release.discs.push(disc); + Object.keys(discs).sort().forEach(k => { + release.discs.push(discs[k]); + }); - release.urls.push({ - link_type: MBImport.URL_TYPES.stream_for_free, - url: releaseUrl, - }); - release.labels.push({ name: data.label }); - release.type = data.record_type; - release.barcode = data.upc; - - return release; -} - -function waitForEl(selector, callback) { - if (jQuery(selector).length) { - callback(); - } else { - setTimeout(function () { - waitForEl(selector, callback); - }, 100); + insertLink(release, data.link); } -} - -function insertLink(release, release_url) { - let editNote = MBImport.makeEditNote(release_url, 'Deezer'); - let parameters = MBImport.buildFormParameters(release, editNote); - - let mbUI = $( - `
- ${MBImport.buildFormHTML(parameters)} -
- ${MBImport.buildSearchButton(release)} -
`, - ).hide(); - waitForEl('[data-testid="toolbar"]', function () { - $('[data-testid="toolbar"]').css({ + + function insertLink(release, releaseUrl) { + $('#mb_import_container').remove(); + + const editNote = MBImport.makeEditNote(releaseUrl, 'Deezer'); + const parameters = MBImport.buildFormParameters(release, editNote); + + const mbUI = $(` +
+ ${MBImport.buildFormHTML(parameters)} + ${MBImport.buildSearchButton(release)} +
+ `); + + // Apply Native Deezer Styling + // We add the class "tempo-btn" and "tempo-btn-hollow-neutral" which are standard Deezer buttons + const $btns = mbUI.find('button'); + $btns.addClass('tempo-btn tempo-btn-hollow-neutral tempo-btn-s'); + + // Custom tweaks to ensure the logo fits nicely inside the rounded Deezer button + $btns.css({ + 'padding': '0 12px', + 'height': '32px', // Matches Deezer small button height + 'min-height': '32px', + 'display': 'flex', 'align-items': 'center', + 'justify-content': 'center', + 'border-radius': '500px' // Native pill shape }); - $('[data-testid="toolbar"]').append(mbUI); - mbUI.show(); + + // Add a text label or icon adjustment if needed + $btns.eq(0).html('Import to MB'); + $btns.eq(1).html('Search MB'); + + // Inject into the action bar + const target = $('.tempo-topbar-actions').first(); + + if (target.length) { + target.prepend(mbUI); + } else { + $('body').prepend(mbUI.css({ + 'position': 'fixed', 'top': '80px', 'right': '20px', 'z-index': '9999', 'background': 'white', 'padding': '5px', 'border-radius': '5px' + })); + } + } + + // --- Init --- + let lastUrl = location.href; + function checkUrl() { + if (location.href !== lastUrl) { + lastUrl = location.href; + init(); + } + } + function init() { + const albumId = getAlbumId(); + if (albumId) setTimeout(() => fetchAndImport(albumId), 1000); + } + $(document).ready(function() { + init(); + setInterval(checkUrl, 1000); }); -} + +})();