11// ==UserScript==
22// @name Import Qobuz releases to MusicBrainz
33// @description Add a button on Qobuz's album pages to open MusicBrainz release editor with pre-filled data for the selected release
4- // @version 2025.10.07 .1
4+ // @version 2025.12.21 .1
55// @namespace https://github.com/murdos/musicbrainz-userscripts
66// @downloadURL https://raw.github.com/murdos/musicbrainz-userscripts/master/qobuz_importer.user.js
77// @updateURL https://raw.github.com/murdos/musicbrainz-userscripts/master/qobuz_importer.user.js
8- // @include /^https?://www\.qobuz\.com/[^/]+/album/[^/]+/[^/]+ $/
8+ // @include /^https?://www\.qobuz\.com/[^/]+/( album|interpreter|label)( /[^/?]+)+(\?.*)? $/
99// @require https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
1010// @require lib/mbimport.js
1111// @require lib/logger.js
12- // @require lib/mblinks.js
12+ // @require lib/mblinks.js?version=v2025.12.21.1
1313// @require lib/mbimportstyle.js
1414// @icon https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/assets/images/Musicbrainz_import_logo.png
1515// @run -at document-start
@@ -419,11 +419,118 @@ function lookupLabelAndDisplayLinks({ release, mblinks }) {
419419 }
420420}
421421
422- $ ( document ) . ready ( function ( ) {
423- MBImportStyle ( ) ;
422+ /**
423+ * Check if the URL has a locale and return both the URL with and without the locale.
424+ */
425+ function getEntityUrls ( url , locale ) {
426+ if ( url . includes ( `/${ locale } /` ) ) {
427+ return [ url , url . replace ( `/${ locale } /` , '/' ) ] ;
428+ } else {
429+ return [ url ] ;
430+ }
431+ }
424432
425- // Initialize MBLinks for checking already imported releases
426- let mblinks = new MBLinks ( 'QOBUZ_MBLINKS_CACHE' ) ;
433+ function processDiscographyPage ( { mblinks, locale } ) {
434+ const items = document . querySelectorAll ( '.product__item' ) ;
435+ const artist_urls_data = [ ] ;
436+ const label_urls_data = [ ] ;
437+ const album_urls_data = [ ] ;
438+ const artist_urls_map = new Map ( ) ;
439+ const label_urls_map = new Map ( ) ;
440+ const album_urls_map = new Map ( ) ;
441+
442+ // Collect all artist links from the page
443+ for ( const item of items ) {
444+ const container = item . querySelector ( '.product__container' ) ;
445+ if ( ! container ) {
446+ continue ;
447+ }
448+ const artist_link = container . querySelector ( 'a[href*="interpreter"]' ) ;
449+ if ( artist_link && artist_link instanceof HTMLAnchorElement && artist_link . href ) {
450+ const artist_url = artist_link . href . split ( '?' ) [ 0 ] . split ( '#' ) [ 0 ] ;
451+ const artist_urls = getEntityUrls ( artist_url , locale ) ;
452+ artist_urls . forEach ( url => {
453+ if ( ! artist_urls_map . has ( url ) ) {
454+ artist_urls_map . set ( url , [ ] ) ;
455+ }
456+ artist_urls_map . get ( url ) . push ( artist_link ) ;
457+ } ) ;
458+ }
459+
460+ const label_link = container . querySelector ( 'a[href*="label"]' ) ;
461+ if ( label_link && label_link instanceof HTMLAnchorElement && label_link . href ) {
462+ const label_url = label_link . href . split ( '?' ) [ 0 ] . split ( '#' ) [ 0 ] ;
463+ const label_urls = getEntityUrls ( label_url , locale ) ;
464+ label_urls . forEach ( url => {
465+ if ( ! label_urls_map . has ( url ) ) {
466+ label_urls_map . set ( url , [ ] ) ;
467+ }
468+ label_urls_map . get ( url ) . push ( label_link ) ;
469+ } ) ;
470+ }
471+
472+ const album_link = container . querySelector ( 'a[href*="album"]' ) ;
473+ if ( album_link && album_link instanceof HTMLAnchorElement && album_link . href ) {
474+ const album_url = album_link . href . split ( '?' ) [ 0 ] . split ( '#' ) [ 0 ] ;
475+ const album_urls = getEntityUrls ( album_url , locale ) ;
476+ album_urls . forEach ( url => {
477+ if ( ! album_urls_map . has ( url ) ) {
478+ album_urls_map . set ( url , [ ] ) ;
479+ }
480+ album_urls_map . get ( url ) . push ( album_link ) ;
481+ } ) ;
482+ }
483+ }
484+
485+ // Build urls_data array for batch processing
486+ artist_urls_map . forEach ( ( artist_link_elements , artist_url ) => {
487+ artist_urls_data . push ( {
488+ url : artist_url ,
489+ mb_type : 'artist' ,
490+ insert_func : function ( link ) {
491+ // Insert the MusicBrainz link before each artist link
492+ artist_link_elements . forEach ( artist_link_element => {
493+ artist_link_element . insertAdjacentHTML ( 'beforebegin' , link ) ;
494+ } ) ;
495+ } ,
496+ key : `artist:${ artist_url } ` ,
497+ } ) ;
498+ } ) ;
499+
500+ label_urls_map . forEach ( ( label_link_elements , label_url ) => {
501+ label_urls_data . push ( {
502+ url : label_url ,
503+ mb_type : 'label' ,
504+ insert_func : function ( link ) {
505+ label_link_elements . forEach ( label_link_element => {
506+ label_link_element . insertAdjacentHTML ( 'beforebegin' , link ) ;
507+ } ) ;
508+ } ,
509+ key : `label:${ label_url } ` ,
510+ } ) ;
511+ } ) ;
512+
513+ album_urls_map . forEach ( ( album_link_elements , album_url ) => {
514+ album_urls_data . push ( {
515+ url : album_url ,
516+ mb_type : 'release' ,
517+ insert_func : function ( link ) {
518+ album_link_elements . forEach ( album_link_element => {
519+ album_link_element . insertAdjacentHTML ( 'beforebegin' , link ) ;
520+ } ) ;
521+ } ,
522+ key : `release:${ album_url } ` ,
523+ } ) ;
524+ } ) ;
525+
526+ // Batch retrieve and display links
527+ mblinks . searchAndDisplayMbLinks ( artist_urls_data ) ;
528+ mblinks . searchAndDisplayMbLinks ( label_urls_data ) ;
529+ mblinks . searchAndDisplayMbLinks ( album_urls_data ) ;
530+ }
531+
532+ function processReleasePage ( { mblinks } ) {
533+ MBImportStyle ( ) ;
427534
428535 extractAlbumData ( )
429536 . then ( raw_release_data => {
@@ -441,10 +548,40 @@ $(document).ready(function () {
441548 // Replace the image zoom link with the maximum size image link
442549 const ogImageMetaElement = document . querySelector ( 'meta[property="og:image"]' ) ;
443550 if ( ogImageMetaElement ) {
444- let maxImgURL = ogImageMetaElement . getAttribute ( 'content' ) . replace ( '_600' , '_max' ) ;
445- document . querySelectorAll ( 'img.album-cover__image' ) . forEach ( imgElement => {
446- imgElement . setAttribute ( 'src' , maxImgURL ) ;
447- } ) ;
551+ const content = ogImageMetaElement . getAttribute ( 'content' ) ;
552+ if ( content ) {
553+ let maxImgURL = content . replace ( '_600' , '_max' ) ;
554+ document . querySelectorAll ( 'img.album-cover__image' ) . forEach ( imgElement => {
555+ imgElement . setAttribute ( 'src' , maxImgURL ) ;
556+ } ) ;
557+ }
558+ }
559+ }
560+
561+ $ ( document ) . ready ( function ( ) {
562+ // Initialize MBLinks for checking already imported releases
563+ const mblinks = new MBLinks ( 'QOBUZ_MBLINKS_CACHE' ) ;
564+ const pageTypeRegex = new RegExp ( `^\\/([a-z]{2}-[a-z]{2})?(?:\\/)?(interpreter|album|label)\\/` ) ;
565+
566+ const locale = window . location . pathname . match ( pageTypeRegex ) ?. [ 1 ] ;
567+ const pageType = window . location . pathname . match ( pageTypeRegex ) ?. [ 2 ] ;
568+
569+ if ( ! pageType ) {
570+ return ;
571+ } else {
572+ switch ( pageType ) {
573+ case 'interpreter' :
574+ processDiscographyPage ( { mblinks, locale } ) ;
575+ break ;
576+ case 'album' :
577+ processReleasePage ( { mblinks } ) ;
578+ break ;
579+ case 'label' :
580+ processDiscographyPage ( { mblinks, locale } ) ;
581+ break ;
582+ default :
583+ return ;
584+ }
448585 }
449586} ) ;
450587
0 commit comments