// ==UserScript==
// @name MusicBrainz: Fix featured artists
// @description Tries to detect artist names in artist and track fields and allows you to extract those. Found entries are added to the corresponding editor for fast adding.
// @supportURL https://github.com/JensBee/userscripts
// @namespace http://www.jens-bertram.net/userscripts/fix-featured-artists
// @icon https://wiki.musicbrainz.org/-/images/3/39/MusicBrainz_Logo_Square_Transparent.png
// @license MIT
// @version 2.3.1beta
//
// @require https://greasyfork.org/scripts/5140-musicbrainz-function-library/code/MusicBrainz%20function%20library.js?version=21997
//
// @grant none
// @include *://musicbrainz.org/recording/*/edit
// @include *://*.musicbrainz.org/recording/*/edit
// @include *://musicbrainz.org/recording/create
// @include *://*.musicbrainz.org/recording/create
// @include *://musicbrainz.org/release/*/edit
// @include *://*.musicbrainz.org/release/*/edit
// @include *://musicbrainz.org/release-group/*/edit
// @include *://*.musicbrainz.org/release-group/*/edit
// @include *://musicbrainz.org/release/add
// @include *://*.musicbrainz.org/release/add
// @include *://musicbrainz.org/artist/*/edit
// @include *://*.musicbrainz.org/artist/*/edit
// @include *://musicbrainz.org/artist/*/split
// @include *://*.musicbrainz.org/artist/*/split
// ==/UserScript==
//**************************************************************************//
var mbz = {};
mbz.fix_feat = {
splitPoints: [ // order matters
'\\s&\\s',
'\\s+\\s',
',\\s',
'\\s/\\s',
'\\s\\(?and\\s',
'\\s\\(?with\\s',
'\\s\\(?meets\\s',
'\\s\\(?feat\\.\\s',
'\\s\\(?ft\\.\\s',
'\\s\\(?featuring\\s',
],
splitPointsRx : [],
btn: {
add: '<button class="nobutton add-artist-credit mbz-fix-feat-add-credit" '
+ 'type="button" title="Add Artist Credit">'
+ '<div class="add-item icon img" title="Add artist credit"></div>'
+ '</button>',
addAll: '<button class="nobutton add-artist-credit '
+ 'mbz-fix-feat-add-all-credits" type="button" title="Add all artist credits">'
+ '<div class="add-item icon img" title="Add all new artist credits"></div>'
+ '</button>',
remove: '<button class="icon remove-item mbz-fix-feat-remove-credit" '
+ 'type="button" title="Remove Artist Credit">'
+ '<div class="remove-item icon img" title="Remove this credit"></div>'
+ '</button>',
removeAll: '<button class="icon remove-item mbz-fix-feat-remove-all-credits" '
+ 'type="button" title="Remove all new artist credits">'
+ '<div class="remove-item icon img" title="Remove all credits"></div>'
+ '</button>',
trigger: '<button>Try detect artists</button>',
triggerShort: '<button title="Try detect artists">FixFeat</button>',
triggerTrackList: '<button title="Try detect artists in track name" '
+ 'class="icon mbz-fix-feat">'
},
rowDiv: '<div class="row"><label>Fix featured artists:</label></div>',
rowTab: '<tr><td><label>Fix featured artists:</label></td></tr>',
_init: function() {
// create RegEx objects
for (let splitPoint of this.splitPoints) {
this.splitPointsRx.push(new RegExp(splitPoint, 'gi'));
}
// append style
MBZ.Html.addStyle(''
+ '#release-editor #track-ac-bubble {width:66%!important}'
+ 'tr.MBZ-FixFeat-Ruler td {height:2px;padding:0!important;}'
+ 'tr.MBZ-FixFeat-Ruler td:nth-child(2),'
+ '#track-ac-bubble tr.MBZ-FixFeat-Ruler td {border-top:1px dotted #666;}'
+ '#track-ac-bubble .MBZ-FixFeat-Item .mbz-fix-feat-add-credit {'
+ 'width:170px;'
+ 'margin-left:0!important;'
+ '}'
+ '#track-ac-bubble .MBZ-FixFeat-ItemAddAll td {text-align:right;}'
+ '#track-ac-bubble .MBZ-FixFeat-ItemAddAll .mbz-fix-feat-add-credit {'
+ 'width:auto;'
+ 'margin-left:0!important;'
+ '}'
+ 'input.MBZ-FixFeat-MaySplit {background-color:#FFFFD0;}'
);
},
/**
* Check, if there's something to split
*/
hasSplitPoints: function(str) {
var cnt = 0;
str = str.replace(/\s+/, ' ');
for (let splitPoint of this.splitPoints) {
if (str.match(splitPoint)) {
cnt++;
}
}
return cnt;
},
/**
* Split something.
*/
splitArtists: function(str) {
var artists = [];
var artistsCleaned = [];
str = str.replace(/\s+/, ' ');
for (let splitPointRx of this.splitPointsRx) {
str = str.replace(splitPointRx, '|SPLT|');
}
artists = str.split('|SPLT|');
for (let idx in artists) {
var artist = artists[idx].trim();
// skip empty and dupes
if (artist != '' && artistsCleaned.indexOf(artist) == -1) {
artistsCleaned.push(artist);
}
if (idx == artistsCleaned.length -1) {
// remove possibly unbalanced parenthesis
artistsCleaned[idx] = artistsCleaned[idx].replace(/\)$/, '');
}
}
return artistsCleaned;
}
};
mbz.fix_feat.BubbleEditor = function(bubbleEditorApi) {
var b = null;
var bubbleApi = null;
var initialized = false;
var self = this;
this.getBubble = function() {
if (!b || b.length == 0) {
return null;
}
return b;
};
this.getBubbleApi = function() {
return bubbleApi;
};
this.setBubble = function(bubble) {
if (!bubble || bubble.length == 0) {
console.err("No bubble.");
} else {
b = bubble;
bubbleApi = bubbleEditorApi;
initialized = true;
b.on('click', 'button', function() {
var btn = $(this);
if (btn.hasClass('mbz-fix-feat-remove-credit')) {
// remove row
self.removeButtonRow.call(self, btn);
return false;
} else if (btn.hasClass('mbz-fix-feat-add-credit')) {
// add artist
bubbleApi.addArtist(btn.data('artist'), true);
// remove row
self.removeButtonRow.call(self, btn);
return false;
} else if (btn.hasClass('mbz-fix-feat-add-all-credits')) {
btn.remove();
b.find('.MBZ-FixFeat-Item button.mbz-fix-feat-add-credit').click();
self.clear();
return false;
} else if (btn.hasClass('mbz-fix-feat-remove-all-credits')) {
self.clear();
return false;
}
});
}
}
};
mbz.fix_feat.BubbleEditor.prototype = {
removeButtonRow: function(btn) {
var b = this.getBubble();
btn.remove();
// remove lefotovers
$.each(b.find('.MBZ-FixFeat-Item'), function() {
// if one button removed itself, remove the whole item
if ($(this).find('button.mbz-fix-feat-add-credit').length == 0
|| $(this).find('button.mbz-fix-feat-remove-credit').length == 0) {
$(this).remove();
}
});
var items = b.find('.MBZ-FixFeat-Item');
if (items.length == 0) {
// remove other elements, if no credit is left
this.clear();
} else if (items.length == 1) {
b.find('tr.MBZ-FixFeat-ItemAddAll').remove();
}
},
/**
* Attach the list of found entities.
* @artists Array of artists to attach
* @return true if something was added
*/
attachArtists: function(artists) {
// check, if there's something to add
if (artists.length == 0) {
console.debug("No artists to attach.");
return false;
}
var b = this.getBubble();
if (!b) {
console.debug("No bubble.");
return;
}
var api = this.getBubbleApi();
// clear any previous attached items
this.clear();
// show bubble
api.tryOpen($('#open-ac'));
var rows = [];
var ruler = '';
switch(api.type) {
case MBZ.BubbleEditor.types.artistCredits:
rows = b.find('.row-form tr');
ruler = '<tr class="MBZ-FixFeat MBZ-FixFeat-Ruler">'
+ '<td></td><td colspan="2"></td></tr>';
break;
case MBZ.BubbleEditor.types.trackArtistCredits:
rows = api.getCreditRows();
ruler = '<tr class="MBZ-FixFeat MBZ-FixFeat-Ruler">'
+ '<td colspan="3"></td></tr>';
break;
}
if (rows.length > 0) {
artists.reverse();
var self = this;
var addButtons = function(target, idx) {
var artist = artists[idx];
// add button
var btnAdd = $(mbz.fix_feat.btn.add);
btnAdd.data('artist', artist);
target.append(btnAdd.prepend(artist)).append(
// remove button
$(mbz.fix_feat.btn.remove)
);
};
var target = null;
switch(api.type) {
case MBZ.BubbleEditor.types.artistCredits:
// target is last row
target = $(rows.get(rows.length -1));
// append a row for each entity
for (let idx in artists) {
var aCell = $('<td colspan="3">');
addButtons(aCell, idx);
target.after($('<tr class="MBZ-FixFeat MBZ-FixFeat-Item">')
.append(aCell));
}
break;
case MBZ.BubbleEditor.types.trackArtistCredits:
target = b.find('tr:has(button.add-item)');
var rowCode = '<tr class="MBZ-FixFeat">';
var row = $(rowCode);
// append a row for two entries
target.after(row);
for (let idx in artists) {
var aCell = $('<td class="MBZ-FixFeat-Item">');
aCell.data('id', idx);
addButtons(aCell, idx);
if ((idx + 1) < artists.length && ((idx + 1) % 2) == 0) {
var oldRow = row;
row = $(rowCode);
oldRow.after(row);
}
row.append(aCell);
}
break;
}
// append add all button, if more than one artist is present
if (artists.length > 1) {
target.after($('<tr class="MBZ-FixFeat MBZ-FixFeat-ItemAddAll">').append(
$('<td colspan="3">').append(
$(mbz.fix_feat.btn.addAll).prepend('<b>All new credits</b>')
).append($(mbz.fix_feat.btn.removeAll))
));
}
// append ruler before/after attached items
var aItems = b.find('.MBZ-FixFeat');
aItems.first().before(ruler)
aItems.last().after(ruler);
}
return true;
},
clear: function() {
var b = this.getBubble();
if (b) {
b.find('.MBZ-FixFeat').remove();
}
},
};
/**
* Show only a link to the split artist editor on single artist edit page,
* if we are able to split the name.
*/
mbz.fix_feat.artistPage = {
init: function() {
var strEl = $('#id-edit-artist\\.name');
var str = strEl.val();
var lnk = 'Please use the <a href="'
+ window.location.toString().replace(/\/edit/, '/split') + '">'
+ 'split artist editor</a>.';
if (mbz.fix_feat.hasSplitPoints(str)) {
var row = $('<div class="row"></div>');
row.append($(mbz.fix_feat.rowDiv)).append(lnk);
strEl.after(row);
}
}
};
/**
* Parse entries on release pages.
*/
mbz.fix_feat.Release = function () {
var initialized = false;
var bubbleEditor = null;
var currentBubbleRow = null;
var entryCode = {
item: '<span class="MBZ-FixFeat-Item" style="display:block">',
row: '<tr class="MBZ-FixFeat"><td colspan="3"></td></tr>'
};
this.init = function() {
if (initialized) {
return;
}
initialized = true;
var strEl = $('#release-artist');
var canSplit = false;
// init ac-bubble editor, if release artist name is splitable
if (strEl.length > 0 && mbz.fix_feat.hasSplitPoints(strEl.val())) {
var acbEdit = new mbz.fix_feat.BubbleEditor(
MBZ.BubbleEditor.ArtistCredits);
MBZ.BubbleEditor.ArtistCredits.onAppear({cb: acbEdit.setBubble});
var row = $(mbz.fix_feat.rowTab);
var btn = $(mbz.fix_feat.btn.trigger);
btn.click(function(){
btn.text('Rescan');
var artists = mbz.fix_feat.splitArtists(strEl.val());
if (artists.length > 0) {
if (!acbEdit.attachArtists(
MBZ.BubbleEditor.ArtistCredits.removePresentArtists(artists)
)) {
btn.remove();
row.find('td').last().append('<em>No new artists found.</em>');
}
}
return false;
});
row.append($('<td colspan="2"></td>').append(btn));
strEl.parentsUntil('table').filter('tr').next().after(row);
}
var trackList = MBZ.TrackList.getList();
if (trackList) {
trackList.on('click', 'button', function(){
if ($(this).parent().hasClass('credits-button')) {
currentBubbleRow = $(this).parents('tr');
};
});
// check rows that may be splitted
var stoppedTyping;
trackList.on('keypress', 'input[type="text"]', function() {
if (stoppedTyping) clearTimeout(stoppedTyping);
var el = $(this);
stoppedTyping = setTimeout(function() {
scanRow(el.parentsUntil('table').filter('tr'));
}, 1000);
});
}
// re-check rows that may be splitted on row changes
MBZ.TrackList.onContentChange({cb: scanRows});
// now initialize the track artists credits bubble editor
bubbleEditor = new mbz.fix_feat.BubbleEditor(
MBZ.BubbleEditor.TrackArtistCredits);
MBZ.BubbleEditor.TrackArtistCredits.onAppear({cb: bubbleAppears});
};
function bubbleAppears(bubble) {
bubbleEditor.setBubble(bubble);
var btn = $(mbz.fix_feat.btn.triggerShort);
btn.click(function(){
scanBubble(btn);
return false;
});
btn.attr('type', 'button');
bubble.find('div.buttons').first().append(btn);
};
var creditEditor = {
addAll: function(row) {
var items = row.find('.MBZ-FixFeat-Item');
if (items.length > 0) {
var artists = [];
$.each(items, function() {
artists.push($(this).text().trim());
});
}
},
checkCreditsCount: function(row) {
if (row.hasClass('MBZ-FixFeat')
&& row.find('.MBZ-FixFeat-Item').length == 0) {
creditEditor.clear(row);
}
},
clear: function(row) {
row.find('.MBZ-FixFeat-Item').remove();
if (row.next().hasClass('MBZ-FixFeat')) {
row.next().remove();
}
}
};
function scanBubble(btn) {
var artistsSplitted = [];
// add all artist credits listed
for (let artist of MBZ.BubbleEditor.TrackArtistCredits.getArtistCredits()) {
artistsSplitted = artistsSplitted.concat(
mbz.fix_feat.splitArtists(artist));
}
// add value from current track title
if (currentBubbleRow) {
var fromTrackTitle = mbz.fix_feat.splitArtists(
currentBubbleRow.find('td.title input').val());
if (fromTrackTitle.length > 1) { // first entry should be track title
fromTrackTitle.shift();
artistsSplitted = artistsSplitted.concat(fromTrackTitle);
}
}
// attach all artist we gathered
bubbleEditor.attachArtists(
MBZ.BubbleEditor.TrackArtistCredits.removePresentArtists(artistsSplitted)
);
};
function scanRow(row) {
row = $(row);
if (row.hasClass('track')) {
var title = row.find('td.title input');
if (mbz.fix_feat.hasSplitPoints(title.val())) {
title.addClass('MBZ-FixFeat-MaySplit');
} else {
title.removeClass('MBZ-FixFeat-MaySplit');
}
}
};
function scanRows(tl, mutations) {
if (mutations) {
MBZ.Util.Mutations.forAddedTagName(mutations, 'tr', scanRow);
}
};
};
/**
* Generic way to catch artist credit bubble editors.
*/
mbz.fix_feat.acBubble = {
/**
* Initialize the editor.
* @strEl jQuery Element containing the artists name.
*/
init: function(strEl) {
if (strEl.length > 0 && mbz.fix_feat.hasSplitPoints(strEl.val())) {
var bEdit = new mbz.fix_feat.BubbleEditor(MBZ.BubbleEditor.ArtistCredits);
MBZ.BubbleEditor.ArtistCredits.onAppear({cb: bEdit.setBubble});
var row = $(mbz.fix_feat.rowDiv);
var btn = $(mbz.fix_feat.btn.trigger);
btn.click(function(){
btn.text('Rescan');
var artists = mbz.fix_feat.splitArtists(strEl.val());
if (artists.length > 0) {
bEdit.attachArtists(
MBZ.BubbleEditor.ArtistCredits.removePresentArtists(artists)
);
}
return false;
});
row.append(btn);
$('#open-ac').parent().after(row);
}
}
};
/**
* Main initializer function.
*/
mbz.fix_feat.init = function() {
mbz.fix_feat._init();
var pageType = MBZ.Util.getMbzPageType();
if (pageType.indexOf("artist") > -1) {
if (pageType.indexOf("split") > -1) {
mbz.fix_feat.acBubble.init($('#entity-artist'));
} else {
mbz.fix_feat.artistPage.init();
}
} else if (pageType.indexOf("recording") > -1) {
mbz.fix_feat.acBubble.init($('#entity-artist'));
} else if (pageType.indexOf("release") > -1) {
// init observer, since component may need time to load
var instance = new mbz.fix_feat.Release();
MBZ.BubbleEditor.ArtistCredits.onAppear({cb: instance.init});
} else if (pageType.indexOf("release-group") > -1) {
mbz.fix_feat.acBubble.init($('#entity-artist'));
}
};
mbz.fix_feat.init();
Donate for the site OpenUserJS
Are you sure you want to go to an external site to donate a monetary value?
WARNING: Some countries laws may supersede the payment processors policy such as the GDPR and PayPal. While it is highly appreciated to donate, please check with your countries privacy and identity laws regarding privacy of information first. Use at your utmost discretion.