22
33// @name Import Discogs releases to MusicBrainz
44// @description Add a button to import Discogs releases to MusicBrainz and add links to matching MusicBrainz entities for various Discogs entities (artist,release,master,label)
5- // @version 2026.7.7 .1
5+ // @version 2026.7.20 .1
66// @namespace http://userscripts.org/users/22504
77// @downloadURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/discogs_importer.user.js
88// @updateURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/discogs_importer.user.js
1717// @icon https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/assets/images/Musicbrainz_import_logo.png
1818// ==/UserScript==
1919
20+ // @ts -nocheck
21+
2022// prevent JQuery conflicts, see http://wiki.greasespot.net/@grant
2123this . $ = this . jQuery = jQuery . noConflict ( true ) ;
2224
@@ -38,6 +40,13 @@ $(document).ready(function () {
3840 MBImportStyle ( ) ;
3941 MBSearchItStyle ( ) ;
4042
43+ // User Collection and Wantlist pages
44+ const entityListPage = getDiscogsEntityListPage ( ) ;
45+ if ( entityListPage ) {
46+ initDiscogsEntityListPage ( entityListPage ) ;
47+ return ;
48+ }
49+
4150 const current_page_key = getDiscogsLinkKey (
4251 window . location . href . replace ( / \? .* $ / , '' ) . replace ( / # .* $ / , '' ) . replace ( '/master/view/' , '/master/' ) ,
4352 ) ;
@@ -93,6 +102,165 @@ $(document).ready(function () {
93102 }
94103} ) ;
95104
105+ ////////////////////////////////////////////////
106+ // Display MusicBrainz links on collection and wantlist pages
107+ ////////////////////////////////////////////////
108+
109+ function getDiscogsEntityListPage ( ) {
110+ if ( / ^ \/ u s e r \/ [ ^ / ] + \/ c o l l e c t i o n \/ ? $ / . test ( window . location . pathname ) ) {
111+ return {
112+ getEntitySelector : discogsType => `[role="row"] [role="cell"][data-field="title"] a[href*="/${ discogsType } /"]` ,
113+ } ;
114+ }
115+
116+ if ( / ^ \/ m y w a n t l i s t \/ ? $ / . test ( window . location . pathname ) ) {
117+ return {
118+ getEntitySelector : discogsType => {
119+ const entityContainer = 'table.release_list_table tbody td.artist_title span.release_title' ;
120+ if ( discogsType === 'release' ) return `${ entityContainer } .release_title_link > a[href*="/release/"]` ;
121+ return `${ entityContainer } > a[href*="/${ discogsType } /"]` ;
122+ } ,
123+ } ;
124+ }
125+
126+ return null ;
127+ }
128+
129+ const discogsEntityListTypes = [
130+ { discogsType : 'release' , mbType : 'release' , mark : 'R' } ,
131+ { discogsType : 'artist' , mbType : 'artist' , mark : 'A' } ,
132+ { discogsType : 'label' , mbType : 'label' , mark : 'L' } ,
133+ ] ;
134+
135+ function getDiscogsEntityInfo ( link , discogsType ) {
136+ const entityKey = getDiscogsLinkKey ( link . href ) ;
137+ const entityInfo = link_infos [ entityKey ] ;
138+ return entityInfo ?. type === discogsType ? { key : entityKey , ...entityInfo } : null ;
139+ }
140+
141+ function collectDiscogsEntityLinks ( root , selector ) {
142+ const links = [ ] ;
143+
144+ if ( root . nodeType === Node . ELEMENT_NODE && root . matches ( selector ) ) links . push ( root ) ;
145+ if ( root . querySelectorAll ) links . push ( ...root . querySelectorAll ( selector ) ) ;
146+
147+ return links ;
148+ }
149+
150+ function removeDiscogsListMbIndicators ( entityLink ) {
151+ while ( entityLink . previousElementSibling ?. hasAttribute ( 'data-mb-discogs-list-indicator' ) ) {
152+ entityLink . previousElementSibling . remove ( ) ;
153+ }
154+ }
155+
156+ function keepDiscogsListIndicatorWithEntity ( entityLink ) {
157+ if ( entityLink . parentElement ?. hasAttribute ( 'data-mb-discogs-list-nowrap' ) ) return ;
158+
159+ const wrapper = document . createElement ( 'span' ) ;
160+ wrapper . setAttribute ( 'data-mb-discogs-list-nowrap' , '' ) ;
161+ wrapper . style . whiteSpace = 'nowrap' ;
162+ entityLink . before ( wrapper ) ;
163+ wrapper . append ( entityLink ) ;
164+ }
165+
166+ function removeDiscogsListEntitySearchLink ( entityLink ) {
167+ if ( entityLink . previousElementSibling ?. getAttribute ( 'data-mb-discogs-list-indicator' ) === 'search' ) {
168+ entityLink . previousElementSibling . remove ( ) ;
169+ }
170+ }
171+
172+ function insertDiscogsListEntitySearchLink ( entityLink , mbType , mark ) {
173+ const searchIndicator = document . createElement ( 'span' ) ;
174+ searchIndicator . className = 'mb_valign mb_searchit' ;
175+ searchIndicator . setAttribute ( 'data-mb-discogs-list-indicator' , 'search' ) ;
176+
177+ const searchLink = document . createElement ( 'a' ) ;
178+ searchLink . className = 'mb_search_link' ;
179+ searchLink . target = '_blank' ;
180+ searchLink . title = `Search this ${ mbType } on MusicBrainz (open in a new tab)` ;
181+ searchLink . href = MBImport . searchUrlFor ( mbType , entityLink . textContent . trim ( ) ) ;
182+ searchLink . innerHTML = `<small>${ mark } </small>?` ;
183+ searchIndicator . append ( searchLink ) ;
184+ entityLink . before ( searchIndicator ) ;
185+ }
186+
187+ function insertDiscogsListMbLink ( entityLink , link ) {
188+ entityLink . insertAdjacentHTML ( 'beforebegin' , link . trim ( ) ) ;
189+ const mbLink = entityLink . previousElementSibling ;
190+ if ( ! mbLink ) return ;
191+
192+ mbLink . setAttribute ( 'data-mb-discogs-list-indicator' , 'link' ) ;
193+ }
194+
195+ function initDiscogsEntityListPage ( pageConfig ) {
196+ const pendingRoots = new Set ( ) ;
197+ let scanScheduled = false ;
198+
199+ const addEntityLinks = roots => {
200+ discogsEntityListTypes . forEach ( ( { discogsType, mbType, mark } ) => {
201+ const urlsData = [ ] ;
202+ const processedEntityUrlAttribute = `data-mb-discogs-list-${ discogsType } -url` ;
203+ const entitySelector = pageConfig . getEntitySelector ( discogsType ) ;
204+
205+ roots
206+ . flatMap ( root => collectDiscogsEntityLinks ( root , entitySelector ) )
207+ . forEach ( entityLink => {
208+ const entityInfo = getDiscogsEntityInfo ( entityLink , discogsType ) ;
209+ if ( ! entityInfo || entityLink . getAttribute ( processedEntityUrlAttribute ) === entityInfo . clean_url ) return ;
210+
211+ keepDiscogsListIndicatorWithEntity ( entityLink ) ;
212+ removeDiscogsListMbIndicators ( entityLink ) ;
213+ insertDiscogsListEntitySearchLink ( entityLink , mbType , mark ) ;
214+ entityLink . setAttribute ( processedEntityUrlAttribute , entityInfo . clean_url ) ;
215+ urlsData . push ( {
216+ url : entityInfo . clean_url ,
217+ mb_type : mbType ,
218+ key : getCacheKeyFromInfo ( entityInfo . key , mbType ) ,
219+ insert_func : link => {
220+ if ( ! entityLink . isConnected || getDiscogsEntityInfo ( entityLink , discogsType ) ?. key !== entityInfo . key ) return ;
221+ removeDiscogsListEntitySearchLink ( entityLink ) ;
222+ insertDiscogsListMbLink ( entityLink , link ) ;
223+ } ,
224+ } ) ;
225+ } ) ;
226+
227+ if ( urlsData . length > 0 ) mbLinks . searchAndDisplayMbLinks ( urlsData ) ;
228+ } ) ;
229+ } ;
230+
231+ const scheduleScan = root => {
232+ pendingRoots . add ( root ) ;
233+ if ( scanScheduled ) return ;
234+ scanScheduled = true ;
235+ setTimeout ( ( ) => {
236+ scanScheduled = false ;
237+ const roots = Array . from ( pendingRoots ) ;
238+ pendingRoots . clear ( ) ;
239+ addEntityLinks ( roots ) ;
240+ } ) ;
241+ } ;
242+
243+ scheduleScan ( document ) ;
244+
245+ const observer = new MutationObserver ( mutations => {
246+ mutations . forEach ( mutation => {
247+ if ( mutation . type === 'attributes' ) {
248+ scheduleScan ( mutation . target ) ;
249+ } else {
250+ mutation . addedNodes . forEach ( node => {
251+ if ( node . nodeType === Node . ELEMENT_NODE ) scheduleScan ( node ) ;
252+ } ) ;
253+ }
254+ } ) ;
255+ } ) ;
256+ observer . observe ( document . body , {
257+ childList : true ,
258+ subtree : true ,
259+ attributes : true ,
260+ attributeFilter : [ 'href' ] ,
261+ } ) ;
262+ }
263+
96264////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
97265// Display links of equivalent MusicBrainz entities //
98266////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
0 commit comments