Skip to content

Commit 48390e0

Browse files
authored
feat(discogs_importer): add user Collection and Wantlist support when linking MB entities (#1005)
- adds support linking MB entities on user-specific pages https://www.discogs.com/user/{username}/collection and https://www.discogs.com/mywantlist - Closes #912
1 parent 7ba1cf3 commit 48390e0

1 file changed

Lines changed: 169 additions & 1 deletion

File tree

discogs_importer.user.js

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
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
@@ -17,6 +17,8 @@
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
2123
this.$ = 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 (/^\/user\/[^/]+\/collection\/?$/.test(window.location.pathname)) {
111+
return {
112+
getEntitySelector: discogsType => `[role="row"] [role="cell"][data-field="title"] a[href*="/${discogsType}/"]`,
113+
};
114+
}
115+
116+
if (/^\/mywantlist\/?$/.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

Comments
 (0)