// ==UserScript==
// @name Extract artists from description for Gazelle
// @namespace https://gf.qytechs.cn/cs/users/321857-anakunda
// @version 1.42.1
// @description Tries to extract artists from selected text or tracklist in group description
// @author Anakunda
// @copyright 2020, Anakunda (https://gf.qytechs.cn/cs/users/321857-anakunda)
// @license GPL-3.0-or-later
// @match https://redacted.ch/torrents.php?id=*
// @match https://redacted.ch/torrents.php?*&id=*
// @match https://orpheus.network/torrents.php?id=*
// @match https://orpheus.network/torrents.php?*&id=*
// @match https://notwhat.cd/torrents.php?id=*
// @match https://notwhat.cd/torrents.php?*&id=*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// ==/UserScript==
const vaParser = /^(?:Various(?:\s+Artists)?|Varios(?:\s+Artistas)?|V\/?A|\<various\s+artists\>|Různí(?:\s+interpreti)?)$/i;
const multiArtistParsers = [
/\s*[\,\;\u3001](?!\s*(?:[JjSs]r)\b)(?:\s*(?:[Aa]nd|\&)\s+)?\s*/,
/\s+(?:[\/\|\×]|meets)\s+/i,
];
const ampersandParsers = [
/\s+(?:meets|vs\.?|X)\s+(?!\s*(?:[\&\/\+\,\;]|and))/i,
/\s*[;\/\|\×]\s*(?!\s*(?:\s*[\&\/\+\,\;]|and))/i,
/(?:\s*,)?\s+(?:[\&\+]|and)\s+(?!his\b|her\b|Friends$|Strings$)/i, // /\s+(?:[\&\+]|and)\s+(?!(?:The|his|her|Friends)\b)/i,
/\s*\+\s*(?!(?:his\b|her\b|Friends$|Strings$))/i,
];
const featArtistParsers = [
///\s+(?:meets)\s+(.+?)\s*$/i,
/* 0 */ /\s+(?:[Ww](?:ith|\.?\/)|[Aa]vec)\s+(?!his\b|her\b|Friends$|Strings$)(.+?)\s*$/,
/* 1 */ /(?:\s+[\-\−\—\–\_])?\s+(?:[Ff]eaturing\s+|(?:[Ff]eat|[Ff]t|FT)\.\s*|[Ff]\.?\/\s+)([^\(\)\[\]\{\}]+?)(?=\s*(?:[\(\[\{].*)?$)/,
/* 2 */ /\s+\[\s*f(?:eat(?:\.|uring)|t\.|\.?\/)\s+([^\[\]]+?)\s*\]/i,
/* 3 */ /\s+\(\s*f(?:eat(?:\.|uring)|t\.|\.?\/)\s+([^\(\)]+?)\s*\)/i,
/* 4 */ /\s+\[\s*(?:(?:en\s+)?duo\s+)?avec\s+([^\[\]]+?)\s*\]/i,
/* 5 */ /\s+\(\s*(?:(?:en\s+)?duo\s+)?avec\s+([^\(\)]+?)\s*\)/i,
/* 6 */ /\s+\[\s*(?:with|[Ww]\.?\/)\s+(?![Hh]is\b|[Hh]er\b|Friends$|Strings$)([^\[\]]+?)\s*\]/,
/* 7 */ /\s+\(\s*(?:with|[Ww]\.?\/)\s+(?![Hh]is\b|[Hh]er\b|Friends$|Strings$)([^\(\)]+?)\s*\)/,
];
const remixParsers = [
/\s+\((?:The\s+)?Remix(?:e[sd])?\)/i,
/\s+\[(?:The\s+)?Remix(?:e[sd])?\]/i,
/\s+(?:The\s+)?Remix(?:e[sd])?\s*$/i,
/^(?:The\s+)?(?:Remixes)\b|\b(?:The\s+)?(?:Remixes)$/,
/\s+\(([^\(\)]+?)[\'\’\`]s[^\(\)]*\s(?:(?:Re)?Mix|Reworx)\)/i,
/\s+\[([^\[\]]+?)[\'\’\`]s[^\[\]]*\s(?:(?:Re)?Mix|Reworx)\]/i,
/\s+\(([^\(\)]+?)\s+(?:(?:Extended|Enhanced)\s+)?(?:Remix|Reworx)\)/i,
/\s+\[([^\[\]]+?)\s+(?:(?:Extended|Enhanced)\s+)?(?:Remix|Reworx)\]/i,
/\s+\(Remix(?:ed)?\s+by\s+([^\(\)]+)\)/i,
/\s+\[Remix(?:ed)?\s+by\s+([^\[\]]+)\]/i,
];
const arrParsers = [
/\s+\(arr(?:anged\s+by|\.)\s+([^\(\)]+?)\s*\)/i,
/\s+\[arr(?:anged\s+by|\.)\s+([^\[\]]+?)\s*\]/i,
];
const prodParsers = [
/\s+\(prod(?:uced(?:\s+by)?|\.\s+by)\s+([^\(\)]+?)\s*\)/i,
/\s+\[prod(?:uced(?:\s+by)?|\.\s+by)\s+([^\[\]]+?)\s*\]/i,
];
const otherArtistsParsers = [
[/^(.*?)\s+(?:under|(?:conducted)\s+by)\s+(.*)$/, 4],
[/^()(.*?)\s+\(conductor\)$/i, 4],
//[/^()(.*?)\s+\(.*\)$/i, 1],
];
const pseudoArtistParsers = [
/* 0 */ vaParser,
/* 1 */ /^(?:#??N[\/\-]?A|[JS]r\.?|Unknown(?:\s+Artist)?)$/i,
/* 2 */ /^(?:auditorium|[Oo]becenstvo|[Pp]ublikum)$/,
/* 3 */ /^(?:(Special\s+)??Guests?|Friends|(?:Studio\s+)?Orchestra)$/i,
/* 4 */ /^(?:Various\s+Composers)$/i,
/* 5 */ /^(?:[Aa]nonym)/,
/* 6 */ /^(?:traditional|trad\.|lidová)$/i,
/* 7 */ /\b(?:traditional|trad\.|lidová)$/,
/* 8 */ /^(?:tradiční|lidová)\s+/,
/* 9 */ /^(?:[Ll]iturgical\b|[Ll]iturgick[áý])/,
];
const siteApiTimeframeStorageKey = 'AJAX time frame', gazelleApiFrame = 10500;
let groupArtists, xhr = new XMLHttpRequest;
let modal = null, btnAdd = null, btnCustom = null, customCtrls = [], sel = null, ajaxRejects = 0;
let prefs = {
set: function(prop, def) { this[prop] = GM_getValue(prop, def) }
};
try { var siteArtistsCache = JSON.parse(sessionStorage.siteArtistsCache) } catch(e) { siteArtistsCache = [ ] }
try { var notSiteArtistsCache = JSON.parse(sessionStorage.notSiteArtistsCache) } catch(e) { notSiteArtistsCache = [ ] }
let redacted_api_key = GM_getValue('redacted_api_key');
Array.prototype.includesCaseless = function(str) {
if (typeof str != 'string') return false;
str = str.toLowerCase();
return this.find(elem => typeof elem == 'string' && elem.toLowerCase() == str) != undefined;
};
Array.prototype.pushUnique = function(...items) {
if (Array.isArray(items) && items.length > 0) items.forEach(it => { if (!this.includes(it)) this.push(it) });
return this.length;
};
Array.prototype.pushUniqueCaseless = function(...items) {
if (Array.isArray(items) && items.length > 0) items.forEach(it => { if (!this.includesCaseless(it)) this.push(it) });
return this.length;
};
(function() {
'use strict';
const styleSheet = `
.modal {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transform: scale(1.1);
transition: visibility 0s linear 0.25s, opacity 0.25s 0s, transform 0.25s;
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
font-size: 17px;
transform: translate(-50%, -50%);
background-color: FloralWhite;
color: black;
width: 31rem;
border-radius: 0.5rem;
padding: 2rem 2rem 2rem 2rem;
font-family: monospace;
}
.show-modal {
opacity: 1;
visibility: visible;
transform: scale(1.0);
transition: visibility 0s linear 0s, opacity 0.25s 0s, transform 0.25s;
}
input[type="text"] { cursor: text; }
input[type="radio"] { cursor: pointer; }
.lbl { cursor: pointer; }
.tooltip {
position: relative;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip .tooltiptext::after {
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #555 transparent transparent transparent;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
button.splitter {
position: relative;
width: 20pt;
height: 20pt;
text-align: center;
font-weight: bold;
font-size: 10pt;
top: -1pt;
background-color: darkolivegreen;
color: white;
}
`;
let addBox = document.querySelector('form.add_form[name="artists"]');
if (addBox == null) return;
btnAdd = document.createElement('input');
btnAdd.id = 'add-artists-from-selection';
btnAdd.value = 'Extract from selection';
btnAdd.onclick = add_from_selection;
btnAdd.type = 'button';
btnAdd.style.marginLeft = '5px';
btnAdd.style.visibility = 'hidden';
addBox.appendChild(btnAdd);
let style = document.createElement('style');
document.head.appendChild(style);
style.id = 'artist-parser-form';
style.type = 'text/css';
style.innerHTML = styleSheet;
let el, elem = [];
elem.push(document.createElement('div'));
elem[elem.length - 1].className = 'modal';
elem[elem.length - 1].id = 'add-from-selection-form';
modal = elem[0];
elem.push(document.createElement('div'));
elem[elem.length - 1].className = 'modal-content';
elem.push(document.createElement('input'));
elem[elem.length - 1].id = 'btnFill';
elem[elem.length - 1].type = 'submit';
elem[elem.length - 1].value = 'Capture';
elem[elem.length - 1].style = "position: fixed; right: 30px; width: 80px; top: 30px;";
elem[elem.length - 1].onclick = doParse;
elem.push(document.createElement('input'));
elem[elem.length - 1].id = 'btnCancel';
elem[elem.length - 1].type = 'button';
elem[elem.length - 1].value = 'Cancel';
elem[elem.length - 1].style = "position: fixed; right: 30px; width: 80px; top: 65px;";
elem[elem.length - 1].onclick = closeModal;
let presetIndex = 0;
function addPreset(val, label = 'Custom', rx = null, order = [1, 2]) {
elem.push(document.createElement('div'));
el = document.createElement('input');
elem[elem.length - 1].style.paddingBottom = '10px';
el.id = 'parse-preset-' + val;
el.name = 'parse-preset';
el.value = val;
if (val == 1) el.checked = true;
el.type = 'radio';
el.onchange = update_custom_ctrls;
if (rx) {
el.rx = rx;
el.order = order;
}
if (val == 999) btnCustom = el;
elem[elem.length - 1].appendChild(el);
el = document.createElement('label');
el.style.marginLeft = '10px';
el.style.marginRight = '10px';
el.htmlFor = 'parse-preset-' + val;
el.className = 'lbl';
el.innerHTML = label;
elem[elem.length - 1].appendChild(el);
if (val != 999) return;
el = document.createElement('input');
el.type = 'text';
el.id = 'custom-pattern';
el.style.width = '20rem';
el.style.fontFamily = 'monospace';
el.autoComplete = "on";
addTooltip(el, 'RegExp to parse lines, first two captured groups are used');
customCtrls.push(elem[elem.length - 1].appendChild(el));
el = document.createElement('input');
el.type = 'radio';
el.name = 'parse-order';
el.id = 'parse-order-1';
el.value = 1;
el.checked = true;
el.style.marginLeft = '1rem';
addTooltip(el, 'Captured regex groups assigned in order $1: artist(s), $2: assignment');
customCtrls.push(elem[elem.length - 1].appendChild(el));
el = document.createElement('label');
el.htmlFor = 'parse-order-1';
el.textContent = '→';
el.style.marginLeft = '5px';
elem[elem.length - 1].appendChild(el);
el = document.createElement('input');
el.type = 'radio';
el.name = 'parse-order';
el.id = 'parse-order-2';
el.value = 2;
el.style.marginLeft = '10px';
addTooltip(el, 'Captured regex groups assigned in order $1: assignment, $2: artist(s)');
customCtrls.push(elem[elem.length - 1].appendChild(el));
el = document.createElement('label');
el.htmlFor = 'parse-order-2';
el.textContent = '←';
el.style.marginLeft = '5px';
elem[elem.length - 1].appendChild(el);
}
addPreset(++presetIndex, escapeHTML('<artist(s)>[ - <assignment>]'), /^\s*(.+?)(?:\:|\s+[\-\−\—\~\–]+\s+(.*?))?\s*$/);
addPreset(++presetIndex, escapeHTML('<artist>[, <assignment>]') +
'<span style="font-family: initial;"> <i>(HRA style)</i></span>', /^\s*(.+?)(?:\:|\s*,\s*(.*?))?\s*$/);
addPreset(++presetIndex, escapeHTML('<artist(s)>[: <assignment>]'), /^\s*(.+?)(?:\:|\s*:+\s*(.*?))?(?:\s*,)?\s*$/);
addPreset(++presetIndex, escapeHTML('<artist(s)>[ (<assignment>)]'), /^\s*(.+?)(?:\:|\s+(?:\([^\(\)]+\)|\[[^\[\]]+\]|\{[^\{\}]+\}))?(?:\s*,)?\s*$/);
addPreset(++presetIndex, escapeHTML('<artist(s)>[ | <assignment>]'), /^\s*(.+?)(?:\s*\|\s*(.*?))?(?:\s*,)?\s*$/);
addPreset(++presetIndex, escapeHTML('[<assignment> - ]<artist(s)>'), /^\s*(?:(.*?)\s+[\-\−\—\~\–]+\s+)?(.+?)\:?(?:\s*,)?\s*$/, [2, 1]);
addPreset(++presetIndex, escapeHTML('[<assignment>: ]<artist(s)>'), /^\s*(?:(.*?)\s*:+\s*)?(.+?)\:?(?:\s*,)?\s*$/, [2, 1]);
addPreset(++presetIndex, escapeHTML('[<assignment> | ]<artist(s)>'), /^\s*(?:(.*?)\s*\|\s*)?(.+?)\:?(?:\s*,)?\s*$/, [2, 1]);
addPreset(++presetIndex, escapeHTML('<artist>[ / <assignment>]'), /^\s*(.+?)(?:\:|\s*\/+\s*(.*?))?(?:\s*,)?\s*$/);
addPreset(++presetIndex, escapeHTML('<artist>[; <assignment>]'), /^\s*(.+?)(?:\:|\s*;\s*(.*?))?(?:\s*,)?\s*$/);
addPreset(++presetIndex, escapeHTML('[<assignment> / ]<artist(s)>'), /^\s*(?:(.*?)\s*\/+\s*)?(.+?)\:?(?:\s*,)?\s*$/, [2, 1]);
addPreset(++presetIndex, '<span style="font-family: initial;">From tracklist</span>',
/^\s*((?:\d+|[A-Z](?:\d+)?)(?:[\-\.](?:\d+|[A-Za-z])|[A-Za-z])?)(?:\s*[\-\−\—\~\–\.\:]\s*|\s+)(.+?)(?:\s+(\((?:\d+:)?\d+:\d+\)|\[(?:\d+:)?\d+:\d+\]))?\s*$/, []);
addPreset(999);
elem.slice(2).forEach(k => { elem[1].appendChild(k) });
elem[0].appendChild(elem[1]);
document.body.appendChild(elem[0]);
window.addEventListener("click", windowOnClick);
document.addEventListener('selectionchange', function(evt) {
let cs = window.getComputedStyle(modal);
if (!btnAdd || window.getComputedStyle(modal).visibility != 'hidden') return;
let sel = document.getSelection();
showHideAddbutton();
});
})();
function add_from_selection() {
sel = document.getSelection();
if (sel.isCollapsed || modal == null) return;
prefs.set('preset', 1);
prefs.set('custom_pattern', '^\\s*(.+?)(?:\\s*:+\\s*(.*?)|\\:)?\\s*$');
prefs.set('custom_pattern_order', 1);
setRadiosValue('parse-preset', prefs.preset);
customCtrls[0].value = prefs.custom_pattern;
setRadiosValue('parse-order', prefs.custom_pattern_order);
sel = sel.toString();
update_custom_ctrls();
modal.classList.add("show-modal");
}
function doParse(expr, flags = '') {
closeModal();
if (!sel) return;
let preset = getSelectedRadio('parse-preset');
if (preset == null) return;
prefs.preset = preset.value;
let order = preset.order,
custom_parse_order = getSelectedRadio('parse-order'),
rx = preset.rx;
if (!rx && preset.value == 999 && custom_parse_order != null) {
rx = new RegExp(customCtrls[0].value);
order = custom_parse_order != null ?
custom_parse_order.value == 1 ? [1, 2] : custom_parse_order.value == 2 ? [2, 1] : null : [1, 2];
}
groupArtists = [
'artist_main', 'artist_guest', 'artists_remix', 'artists_composers',
'artists_conductors', 'artists_dj', 'artists_producer', 'artists_arranger',
].map(category => Array.from(document.querySelectorAll(`ul#artist_list > li.${category} > a`)).map(a => a.textContent.trim()));
cleanupArtistsForm();
sel.split(/(?:\r?\n)+/).forEach(function(line) {
line = line.trim().replace(/\s+\(tracks?\b[^\(\)]+\)/, '').replace(/\s+\[tracks?\b[^\[\]]+\]/, '');
if (!line || /^\s*(?:Recorded|Mastered)\b/i.test(line)) return;
let matches = /^\s*(?:Produced)[ \-\−\—\~\–]by (.+?)\s*$/.exec(line);
if (matches != null) splitAmpersands(matches[1]).forEach(producer => { addArtist(producer, 7) });
else if (rx instanceof RegExp && (matches = rx.exec(line)) != null) {
if (!Array.isArray(order) || order.length < 2) {
let title = matches[2];
if (/^(.+?) [\-\−\—\–] /.test(title)) {
let artist = RegExp.$1;
if ((matches = featArtistParsers.slice(0, 2).reduce((m, rx) => m || rx.exec(artist), null)) != null) {
splitAmpersands(artist.slice(0, matches.index)).forEach(artist => { addArtist(artist, 1) });
splitAmpersands(matches[1]).forEach(artist => { addArtist(artist, 2) });
} else splitAmpersands(artist).forEach(artist => { addArtist(artist, 1) });
}
if ((matches = featArtistParsers.slice(1).reduce((m, rx) => m || rx.exec(title), null)) != null)
splitAmpersands(matches[1]).forEach(guest => { addArtist(guest, 2) });
if ((matches = remixParsers.slice(4).reduce((m, rx) => m || rx.exec(title), null)) != null)
splitAmpersands(matches[1]).forEach(remixer => { addArtist(remixer, 3) });
if ((matches = prodParsers.reduce((m, rx) => m || rx.exec(title), null)) != null)
splitAmpersands(matches[1]).forEach(producer => { addArtist(producer, 7) });
// if ((matches = arrParsers.reduce((m, rx) => m || rx.exec(title), null)) != null)
// splitAmpersands(matches[1]).forEach(arranger => { addArtist(arranger, 8) });
} else if (matches[order[0]]) {
let role = deduceArtist(matches[order[1]]);
splitAmpersands(matches[order[0]]).forEach(artist => { addArtist(artist, role) });
} else splitAmpersands(matches[order[1]]).forEach(artist => { addArtist(artist, 2) });
}
});
prefs.custom_pattern = customCtrls[0].value;
prefs.custom_pattern_order = custom_parse_order != null ? custom_parse_order.value : 1;
for (let i in prefs) { if (typeof prefs[i] != 'function') GM_setValue(i, prefs[i]) }
if (siteArtistsCache.length > 0) sessionStorage.siteArtistsCache = JSON.stringify(siteArtistsCache);
if (notSiteArtistsCache.length > 0) sessionStorage.notSiteArtistsCache = JSON.stringify(notSiteArtistsCache);
return;
function deduceArtist(str) {
if (/\b(?:remix)/i.test(str)) return 3; // remixer
if (/\b(?:composer|libretto|lyric\w*|written[ \-\−\—\~\–]by)\b/i.test(str)) return 4; // composer
if (/\b(?:conduct|director\b|direction\b)/i.test(str)) return 5; // conductor
if (/\b(?:compiler\b)/i.test(str)) return 6; // compiler
if (/\b(?:producer\b|produced[ \-\−\—\~\–]by\b)/i.test(str)) return 7; // producer
return 2;
}
function addArtist(name, type = 1) {
if (!name || !(type > 0) || pseudoArtistParsers.some(rx => rx.test(name))) return false;
// avoid dupes
if (groupArtists[type - 1].includesCaseless(name)) return false;
switch (type) {
case 1: if ([4, 5].some(cat => groupArtists[cat].includesCaseless(name))) return false; break;
case 2: if ([0, 4, 5].some(cat => groupArtists[cat].includesCaseless(name))) return false; break;
}
let input = assignFreeArtistField();
if (input == null) throw 'could not allocate free artist slot';
input.value = name;
const importance = input.nextElementSibling;
importance.value = type;
groupArtists[type - 1].push(name);
if (ampersandParsers.some(rx => rx.test(name))) {
let button = document.createElement('button');
button.className = 'splitter';
button.textContent = '↔';
button.onclick = function(evt) {
let artists = [input.value];
ampersandParsers.forEach(function(rx) {
for (let index = artists.length - 1; index >= 0; --index) {
artists.splice(index, 1, ...artists[index].split(rx));
}
});
if (artists.length > 1) {
const artistUsed = artist => artist && parseInt(importance.value) > 0
&& groupArtists[parseInt(importance.value) - 1].includesCaseless(artist);
input.value = !artistUsed(artists[0]) ? artists[0] : '';
artists.slice(1).forEach(function(artist) {
if (artistUsed(artist)) return;
let input = assignFreeArtistField();
if (input == null) throw 'could not allocate free artist slot';
input.value = artist;
input.nextElementSibling.value = importance.value;
});
}
evt.target.remove();
};
input.insertAdjacentElement('afterend', button);
}
return true;
}
}
function closeModal() {
if (modal == null) return;
showHideAddbutton();
modal.classList.remove("show-modal");
}
function windowOnClick(event) {
if (modal != null && event.target === modal) closeModal();
}
function update_custom_ctrls() {
function en(elem) {
if (elem == null || btnCustom == null) return;
elem.disabled = !btnCustom.checked;
elem.style.opacity = btnCustom.checked ? 1 : 0.5;
}
customCtrls.forEach(k => { en(k) });
}
function getSelectedRadio(name) {
for (let i of document.getElementsByName(name)) { if (i.checked) return i }
return null;
}
function setRadiosValue(name, val) {
for (let i of document.getElementsByName(name)) { if (i.value == val) i.checked = true }
}
function showHideAddbutton() {
//btnAdd.style.visibility = document.getSelection().type == 'Range' ? 'visible' : 'hidden';
btnAdd.style.visibility = document.getSelection().isCollapsed ? 'hidden' : 'visible';
}
function escapeHTML(string) {
let pre = document.createElement('pre'), text = document.createTextNode(string);
pre.appendChild(text);
return pre.innerHTML;
}
function cleanupArtistsForm() {
document.querySelectorAll('div#AddArtists > input[name="aliasname[]"]').forEach(function(input) {
input.value = '';
input.nextElementSibling.value = 1;
});
document.querySelectorAll('div#AddArtists > button.splitter').forEach(button => { button.remove() });
}
function assignFreeArtistField() {
function findFreeSlot() {
for (let input of document.querySelectorAll('div#AddArtists > input[name="aliasname[]"]'))
if (input.value.length <= 0) return input;
return null;
}
return findFreeSlot() || (AddArtistField(), findFreeSlot());
}
function addTooltip(elem, text) {
if (elem == null) return;
elem.classList.add('tooltip');
var tt = document.createElement('span');
tt.className = 'tooltiptext';
tt.textContent = text;
elem.appendChild(tt);
}
function twoOrMore(artist) { return artist.length >= 2 && !pseudoArtistParsers.some(rx => rx.test(artist)) };
function looksLikeTrueName(artist, index = 0) {
return twoOrMore(artist)
&& (index == 0 || !/^(?:his\b|her\b|Friends$|Strings$)/i.test(artist))
&& artist.split(/\s+/).length >= 2
&& !pseudoArtistParsers.some(rx => rx.test(artist)) || isSiteArtist(artist);
}
function strip(art, level = 0) {
return typeof art == 'string' ? [
/\s+(?:aka|AKA|Aka)\.?\s+(.*)$/g,
/\s*\([^\(\)]+\)/g,
/\s*\[[^\[\]]+\]/g,
/\s*\{[^\{\}]+\}/g,
].slice(level).reduce((acc, rx) => acc.replace(rx, ''), art) : undefined;
}
function isSiteArtist(artist) {
if (!artist || notSiteArtistsCache.includesCaseless(artist)) return false;
if (siteArtistsCache.includesCaseless(artist)) return true;
let now = Date.now();
try { var apiTimeFrame = JSON.parse(localStorage[siteApiTimeframeStorageKey]) } catch(e) { apiTimeFrame = { } }
if (!apiTimeFrame.timeStamp || now > apiTimeFrame.timeStamp + gazelleApiFrame) {
apiTimeFrame.timeStamp = now;
apiTimeFrame.requestCounter = 1;
} else ++apiTimeFrame.requestCounter;
localStorage[siteApiTimeframeStorageKey] = JSON.stringify(apiTimeFrame);
if (apiTimeFrame.requestCounter > 5) {
if (groupArtists.some(art => art.includesCaseless(artist))) return true;
console.debug('isSiteArtist() request exceeding AJAX API time frame: /ajax.php?action=artist&artistname="' +
artist + '" (' + apiTimeFrame.requestCounter + ')');
++ajaxRejects;
btnAdd.disabled = true;
setTimeout(() => { btnAdd.disabled = false }, apiTimeFrame.timeStamp + gazelleApiFrame - now);
return undefined;
}
try {
let requestUrl = '/ajax.php?action=artist&artistname=' + encodeURIComponent(artist);
xhr.open('GET', requestUrl, false);
if (document.location.hostname == 'redacted.ch' && redacted_api_key) xhr.setRequestHeader('Authorization', redacted_api_key);
xhr.send();
if (xhr.status == 404) {
notSiteArtistsCache.push(artist);
return false;
}
if (xhr.readyState != XMLHttpRequest.DONE || xhr.status < 200 || xhr.status >= 400) {
console.warn('isSiteArtist("' + artist + '") error:', xhr, 'url:', document.location.origin + requestUrl);
return undefined; // error
}
let response = JSON.parse(xhr.responseText);
if (response.status != 'success') {
notSiteArtistsCache.push(artist);
return false;
}
if (!response.response) return false;
siteArtistsCache.push(artist);
return true;
} catch(e) {
console.error('isSiteArtist("' + artist + '"):', e, xhr);
return undefined;
}
}
function splitArtists(str, parsers = multiArtistParsers) {
let result = [str];
parsers.forEach(function(parser) {
for (let i = result.length; i > 0; --i) {
let j = result[i - 1].split(parser).map(strip);
if (j.length > 1 && j.every(twoOrMore) && !j.some(artist => pseudoArtistParsers.some(rx => rx.test(artist)))
&& !isSiteArtist(result[i - 1])) result.splice(i - 1, 1, ...j);
}
});
return result;
}
function splitAmpersands(artists) {
if (typeof artists == 'string') var result = splitArtists(strip(artists, 1));
else if (Array.isArray(artists)) result = Array.from(artists); else return [];
ampersandParsers.forEach(function(ampersandParser) {
for (let i = result.length; i > 0; --i) {
let j = result[i - 1].split(ampersandParser).map(strip);
if (j.length <= 1 || isSiteArtist(result[i - 1]) || !j.every(looksLikeTrueName)) continue;
result.splice(i - 1, 1, ...j.filter(function(artist) {
return !result.includesCaseless(artist) && !pseudoArtistParsers.some(rx => rx.test(artist));
}));
}
});
return result;
}