Skip to content

Instantly share code, notes, and snippets.

@mwiencek
Last active February 29, 2024 21:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mwiencek/99b71a20e523fc7dfc97864b4eb6cbc3 to your computer and use it in GitHub Desktop.
Save mwiencek/99b71a20e523fc7dfc97864b4eb6cbc3 to your computer and use it in GitHub Desktop.
"Move track relationships to the release-level" userscript
// ==UserScript==
// @name Move track relationships to the release-level
// @version 2024.2.29
// @description Allows mass-moving incorrect recording- and work-level relationships to the release-level in the release relationship editor.
// @downloadURL https://gist.github.com/mwiencek/99b71a20e523fc7dfc97864b4eb6cbc3/raw
// @updateURL https://gist.github.com/mwiencek/99b71a20e523fc7dfc97864b4eb6cbc3/raw
// @supportURL https://gist.github.com/mwiencek/99b71a20e523fc7dfc97864b4eb6cbc3
// @namespace 790382e7-8714-47a7-bfbd-528d0caa2333
// @author mwiencek
// @licence MIT
// @include /^https?:\/\/(beta\.|test\.)?musicbrainz\.org\/release\/[^/]+\/edit-relationships/
// @grant none
// @run-at document-idle
// ==/UserScript==
function entrypoint() {
function runScript() {
const {linkedEntities, tree: wbt, relationshipEditor} = MB;
const flattenLinkTypeTree = (linkType) => [
linkType,
...(linkType.children ? linkType.children.flatMap(flattenLinkTypeTree) : []),
];
const linkTypeTree = new Map(
Object.entries(linkedEntities.link_type_tree).flatMap(
([entityTypes, linkTypes]) => (
linkTypes.flatMap(flattenLinkTypeTree).map((linkType) => [entityTypes + '-' + linkType.name, linkType])
)));
const releaseLevelLinkTypes = {};
for (const [key, linkType] of linkTypeTree) {
const [entityType0, entityType1, linkTypeName] = key.split('-', 3);
let otherEntityType;
if (
(entityType0 === 'url' || entityType1 === 'url') ||
(entityType0 === 'series' || entityType1 === 'series') ||
linkTypeName === 'part of'
) {
// skip URLs, series, part-of
continue;
} else if (entityType0 === 'recording' || entityType0 === 'work') {
otherEntityType = entityType1;
} else if (entityType1 === 'recording' || entityType1 === 'work') {
otherEntityType = entityType0;
} else {
continue;
}
const releaseKey = [
...([otherEntityType, 'release'].sort()),
linkTypeName,
].join('-');
const releaseLinkType = linkTypeTree.get(releaseKey);
if (releaseLinkType) {
releaseLevelLinkTypes[linkType.id] = [linkType, releaseLinkType];
}
}
const uiDiv = document.createElement('div');
uiDiv.innerHTML =
'<label>' +
'Move relationships to the release-level: ' +
'<select>' +
'<option value="all">*all relationships*</option>' +
'<option value="all-recording-level">*all recording-level relationships*</option>' +
'<option value="all-work-level">*all work-level relationships*</option>' +
'</select> ' +
'</label>' +
'<button type="button">Go</button>';
document.getElementById('content').insertBefore(
uiDiv,
document.querySelector('div.release-relationship-editor'),
);
const select = uiDiv.querySelector('select');
const button = uiDiv.querySelector('button');
for (
const [trackLevelLinkType, releaseLevelLinkType] of
Object.values(releaseLevelLinkTypes).sort((a, b) => (
a[0].type0.localeCompare(b[0].type0) ||
a[0].type1.localeCompare(b[0].type1) ||
a[0].name.localeCompare(b[0].name)
))
) {
const trackLevelLinkTypeEntityTypes = `${trackLevelLinkType.type0}-${trackLevelLinkType.type1}`;
const option = document.createElement('option');
option.value = trackLevelLinkType.id;
option.textContent = `${trackLevelLinkTypeEntityTypes} ${trackLevelLinkType.name}`;
select.appendChild(option);
}
button.onclick = function () {
const selection = select.value;
const replacements = [];
for (const [source, targetTypeGroups] of wbt.iterate(relationshipEditor.state.relationshipsBySource)) {
const sourceType = source.entityType;
if (
(selection === 'all-recording-level' && sourceType !== 'recording') ||
(selection === 'all-work-level' && sourceType !== 'work') ||
(sourceType !== 'recording' && sourceType !== 'work')
) {
continue;
}
for (const [otherEntityType, linkTypeGroups] of wbt.iterate(targetTypeGroups)) {
for (const linkTypeGroup of wbt.iterate(linkTypeGroups)) {
if (/^[0-9]+$/.test(selection) && String(linkTypeGroup.typeId) !== selection) {
continue;
}
const [trackLevelLinkType, releaseLevelLinkType] = releaseLevelLinkTypes[linkTypeGroup.typeId] ?? [];
if (!releaseLevelLinkType) {
continue;
}
const origOtherEntityProp = trackLevelLinkType.type0 === sourceType ? 'entity1' : 'entity0';
const releaseProp = releaseLevelLinkType.type0 === 'release' ? 'entity0' : 'entity1';
const otherEntityProp = releaseLevelLinkType.type0 === 'release' ? 'entity1' : 'entity0';
for (const phraseGroup of wbt.iterate(linkTypeGroup.phraseGroups)) {
for (const takeOutTheTrash of wbt.iterate(phraseGroup.relationships)) {
const letItBe = {
...takeOutTheTrash,
_lineage: [...takeOutTheTrash._lineage, '"Move track relationships to the release-level" userscript'],
_original: null,
_status: 1,
attributes: wbt.fromDistinctAscArray(wbt.toArray(takeOutTheTrash.attributes).filter((linkAttr) => (
releaseLevelLinkType.attributes[linkedEntities.link_attribute_type[linkAttr.typeID].root_id] != null
))),
id: relationshipEditor.getRelationshipStateId(null),
linkTypeID: releaseLevelLinkType.id,
linkOrder: 0,
[releaseProp]: relationshipEditor.state.entity,
[releaseProp + '_credit']: '',
[otherEntityProp]: takeOutTheTrash[origOtherEntityProp],
[otherEntityProp + '_credit']: takeOutTheTrash[origOtherEntityProp + '_credit'],
};
letItBe._original = letItBe;
replacements.push([takeOutTheTrash, letItBe]);
}
}
}
}
}
for (const [takeOutTheTrash, letItBe] of replacements) {
if (takeOutTheTrash._status !== 3) {
relationshipEditor.dispatch({
type: 'remove-relationship',
relationship: takeOutTheTrash,
});
}
relationshipEditor.dispatch({
type: 'update-relationship-state',
oldRelationshipState: null,
newRelationshipState: letItBe,
sourceEntity: relationshipEditor.state.entity,
});
}
};
}
const interval = setInterval(function () {
if (Object.keys((window.MB?.linkedEntities?.link_type_tree) ?? {}).length) {
runScript();
clearInterval(interval);
}
}, 250);
}
const script = document.createElement('script');
script.textContent = `(${entrypoint.toString()}());`;
document.body.appendChild(script);
@kellnerd
Copy link

Small fix to make it run on beta: // @include /^https?:\/\/(beta\.|test\.)?musicbrainz\.org\/release\/[^/]+\/edit-relationships/

@mwiencek
Copy link
Author

Thanks, I released 2024.2.29 with your change, plus a couple of other small changes.

@jesus2099
Copy link

Wow thanks! I certainly bookmark this as a reference to how to manipulate the relationship editor!! :)

@mwiencek
Copy link
Author

@jesus2099 I am also happy to help on IRC if anything is confusing. :)

@jesus2099
Copy link

Oh yes, sure! Thanks! I never think of that!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment