Gazelle extract featured artists from description

Tries to recognize and add featured artists from selected text in group description

目前为 2020-08-13 提交的版本。查看 最新版本

// ==UserScript==
// @name         Gazelle extract featured artists from description
// @namespace    https://gf.qytechs.cn/cs/users/321857-anakunda
// @version      1.36
// @description  Tries to recognize and add featured artists from selected text 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://orpheus.network/torrents.php?*id=*
// @match        https://notwhat.cd/torrents.php?*id=*
// @grant        RegExp
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_log
// @require      https://gf.qytechs.cn/scripts/388280-xpathlib/code/XPathLib.js
// ==/UserScript==

const multiArtistParsers = [
  /\s*[,;\u3001](?!\s*(?:[JjSs]r)\b)(?:\s*[Aa]nd\s+)?\s*/,
  /\s+[\/\|\×]\s+/,
];
const pseudoArtistParsers = [
  /^(?:#??N[\/\-]?A|[JS]r\.?)$/i,
  /^(?:traditional|lidová)$/i,
  /\b(?:traditional|lidová)$/,
  /^(?:tradiční|lidová)\s+/,
  /^(?:[Aa]nonym)/,
  /^(?:[Ll]iturgical\b|[Ll]iturgick[áý])/,
  /^(?:auditorium|[Oo]becenstvo|[Pp]ublikum)$/,
  /^(?:Various\s+Composers)$/i,
  /^(?:Guests|Friends)$/i,
];
const ampersandParsers = [
  /\s+(?:vs\.?|X)\s+(?!\s*(?:[\&\/\+\,\;]|and))/i,
  /\s*[;\/\|\×]\s*(?!\s*(?:\s*[\&\/\+\,\;]|and))/i,
  /\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 featParsers = [
  /\s+(?:[Ww]ith)\s+(?!his\b|her\b|Friends$|Strings$)(.*?)\s*$/,
  /(?:\s+[\-\−\—\–\_])?\s+(?:[Ff](?:eaturing|t\.))\s+(.*?)\s*$/,
  /(?:\s+[\-\−\—\–\_])?\s+(?:[Ff](?:ea)?t\.)\s+(.*?)\s*$/,
  /\s+\[\s*f(?:eat(?:\.|uring)|t\.)\s+([^\[\]]+?)\s*\]/i,
  /\s+\(\s*f(?:eat(?:\.|uring)|t\.)\s+([^\(\)]+?)\s*\)/i,
  /\s+\[\s*(?:(?:en\s+)?duo\s+)?avec\s+([^\[\]]+?)\s*\]/i,
  /\s+\(\s*(?:(?:en\s+)?duo\s+)?avec\s+([^\(\)]+?)\s*\)/i,
  /\s+\[\s*(?:with|w\/)\s+(?![Hh]is\b|[Hh]er\b|Friends$|Strings$)([^\[\]]+?)\s*\]/,
  /\s+\(\s*(?:with|w\/)\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,
  /^(Remixes)\b/,
  /\s+\(([^\(\)]+?)(?:[\'\’\`]s)?\s+(?:(?:Extended|Enhanced)\s+)?Remix\)/i,
  /\s+\[([^\[\]]+?)(?:[\'\’\`]s)?\s+(?:(?:Extended|Enhanced)\s+)?Remix\]/i,
  /\s+\([^\(\)]*\b(?:(Extended|Enhanced)\s+)?Remix(?:ed)?\s+by\s+([^\(\)]+)\)/i,
  /\s+\[[^\[\]]*\b(?:(Extended|Enhanced)\s+)?Remix(?:ed)?\s+by\s+([^\[\]]+)\]/i,
];
const otherArtistsParsers = [
  [/^(.*?)\s+(?:under|(?:conducted)\s+by)\s+(.*)$/, 4],
  [/^()(.*?)\s+\(conductor\)$/i, 4],
  //[/^()(.*?)\s+\(.*\)$/i, 1],
];
const artistStrips = [
  /\s+(?:aka|AKA)\.?\s+(.*)$/,
  /\s+\(([^\(\)]+)\)$/,
  /\s+\[([^\[\]]+)\]$/,
  /\s+\{([^\{\}]+)\}$/,
];

const siteApiTimeframeStorageKey = document.location.hostname + ' API time frame';
const gazelleApiFrame = 10500;
var artist_index, siteArtistsCache = {}, notSiteArtistsCache = [], xhr = new XMLHttpRequest;
var modal = null, btnAdd = null, btnCustom = null, customCtrls = [], sel = null, ajaxRejects = 0;
var prefs = {
  set: function(prop, def) { this[prop] = GM_getValue(prop, def) }
};

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;
}
`;

  var 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);

  var style = document.createElement('style');
  document.head.appendChild(style);
  style.id = 'artist-parser-form';
  style.type = 'text/css';
  style.innerHTML = styleSheet;
  var 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 = do_parse;
  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;
  var 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;">&nbsp;&nbsp;<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('[<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+(?:\s*[\-\−\—\~\–\.\:]\s*|\s+)(.+?)\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', () => {
	var cs = window.getComputedStyle(modal);
	if (!btnAdd || window.getComputedStyle(modal).visibility != 'hidden') return;
	var 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 do_parse(expr, flags = '') {
  closeModal();
  if (!sel) return;
  var preset = getSelectedRadio('parse-preset');
  if (preset == null) return;
  prefs.preset = preset.value;
  var order = preset.order;
  var custom_parse_order = getSelectedRadio('parse-order');
  var 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];
  }
  const guest_parser = /^(.*?)(?:\s+(?:feat(?:\.|uring)|with|meets)\s+(.*))?$/;
  function extr_artists(kind) { return document.querySelectorAll('ul#artist_list > li.' + kind + ' > a') }
  var artists = [
	extr_artists('artist_main'),
	extr_artists('artist_guest'),
	extr_artists('artists_remix'),
	extr_artists('artists_composers'),
	extr_artists('artists_conductors'),
	extr_artists('artists_dj'),
	extr_artists('artists_producer'),
  ];
  cleanupArtistsForm();
  var addedartists = [];
  for (var i = 0; i < artists.length; ++i) addedartists[i] = [];
  artist_index = 0;
  sel.split(/(?:\r?\n)+/).forEach(function(line) {
	if (!line || !line.trim()) return;
	if (line.search(/^\s*(?:Recorded|Mastered)\b/i) >= 0) return;
	line = line.replace(/\s+\(tracks?\b[^\(\)]+\)/, '').replace(/\s+\[tracks?\b[^\[\]]+\]/, '')
	let matches = /^\s*(?:Produced)[ \-\−\—\~\–]by (.+?)\s*$/.exec(line);
	if (matches != null) splitAmpersands(matches[1]).forEach(producer => { add_artist(producer, 7) });
	else if (rx instanceof RegExp && (matches = rx.exec(line)) != null) {
	  if (!Array.isArray(order) || order.length < 2) {
		let title = matches[1];
		if (/^(.+?) - /.test(title)) {
		  let artist = RegExp.$1;
		  if ((matches = featParsers.slice(0, 3).reduce((m, rx) => m || rx.exec(artist), null)) != null) {
			splitAmpersands(artist.slice(0, matches.index)).forEach(artist => { add_artist(artist, 1) });
			splitAmpersands(matches[1]).forEach(artist => { add_artist(artist, 2) });
		  } else splitAmpersands(artist).forEach(artist => { add_artist(artist, 1) });
		}
		if ((matches = featParsers.slice(1).reduce((m, rx) => m || rx.exec(title), null)) != null)
		  splitAmpersands(matches[1]).forEach(artist => { add_artist(artist, 2) });
	  } else if (matches[order[0]]) {
		let role = deduce_artist(matches[order[1]]);
	  	splitAmpersands(matches[order[0]]).forEach(artist => { add_artist(artist, role) });
	  } else splitAmpersands(matches[order[1]]).forEach(artist => { add_artist(artist, 2) });
	}
  });
  prefs.custom_pattern = customCtrls[0].value;
  prefs.custom_pattern_order = custom_parse_order != null ? custom_parse_order.value : 1;
  for (i in prefs) { if (typeof prefs[i] != 'function') GM_setValue(i, prefs[i]) }
  return;

  function deduce_artist(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|rirector\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 add_artist(name, type = 1) {
	if (!name || !type) return false;
	if (/^(?:(?:Special\s+)?Guests?):?$/i.test(name)) return false;
	// avoid dupes
	var n = name.toLowerCase();
	for (var i of artists[0]) { if (n == i.textContent.toLowerCase()) return false }
	if (type >= 2) for (i of artists[type - 1]) { if (n == i.textContent.toLowerCase()) return false }
	for (i of addedartists[0]) { if (n == i.toLowerCase()) return false }
	if (type >= 2) for (i of addedartists[type - 1]) { if (n == i.toLowerCase()) return false }
	var id = get_artist_field(artist_index);
	if (id == null) {
	  AddArtistField();
	  id = get_artist_field(artist_index);
	  if (id == null) return false;
	}
	id.value = name;
	id.nextElementSibling.value = type;
	addedartists[type - 1].push(name);
	++artist_index;
	return true;
  }
}

function get_artist_field(index) {
  var id = document.getElementById('artist');
  if (index <= 0) return id;
  for (var i = 0; i < index; ++i) {
	do { id = id.nextElementSibling } while (id != null && (id.localName != 'input' || id.name != 'aliasname[]'));
	if (id == null) break;
  }
  return id;
}

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 (var i of document.getElementsByName(name)) { if (i.checked) return i }
  return null;
}

function setRadiosValue(name, val) {
  for (var 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) {
  var pre = document.createElement('pre');
  var text = document.createTextNode(string);
  pre.appendChild(text);
  return pre.innerHTML;
}

function cleanupArtistsForm() {
  var id = get_artist_field(0);
  do {
	id.value = null;
	id = id.nextElementSibling;
	if (id == null) break;
	id.value = 1;
	do { id = id.nextElementSibling } while (id != null && (id.localName != 'input' || id.name != 'aliasname[]'));
  } while (id != null);
}

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)) || getSiteArtist(artist);
}

function strip(art) {
  return artistStrips.reduce(function(acc, rx, ndx) {
	return ndx != 1 || rx.test(acc)/* && !notMonospaced(RegExp.$1)*/ ? acc.replace(rx, '') : acc;
  }, art);
}

function getSiteArtist(artist) {
  //if (isOPS) return undefined;
  if (!artist || notSiteArtistsCache.includesCaseless(artist)) return null;
  var key = Object.keys(siteArtistsCache).find(it => it.toLowerCase() == artist.toLowerCase());
  if (key) return siteArtistsCache[key];
  var now = Date.now();
  try { var apiTimeFrame = JSON.parse(window.localStorage[siteApiTimeframeStorageKey]) } catch(e) { apiTimeFrame = {} }
  if (!apiTimeFrame.timeStamp || now > apiTimeFrame.timeStamp + gazelleApiFrame) {
	apiTimeFrame.timeStamp = now;
	apiTimeFrame.requestCounter = 1;
  } else ++apiTimeFrame.requestCounter;
  window.localStorage[siteApiTimeframeStorageKey] = JSON.stringify(apiTimeFrame);
  if (apiTimeFrame.requestCounter > 5) {
	console.debug('getSiteArtist() request exceeding AJAX API time frame: /ajax.php?action=artist&artistname="' +
		artist + '" (' + apiTimeFrame.requestCounter + ')');
	++ajaxRejects;
	return undefined;
  }
  try {
	var requestUrl = '/ajax.php?action=artist&artistname=' + encodeURIComponent(artist);
	xhr.open('GET', requestUrl, false);
	//if (isRED && prefs.redacted_api_key) xhr.setRequestHeader('Authorization', prefs.redacted_api_key);
	xhr.send();
	if (xhr.status == 404) {
	  notSiteArtistsCache.pushUniqueCaseless(artist);
	  return null;
	}
	if (xhr.readyState != XMLHttpRequest.DONE || xhr.status < 200 || xhr.status >= 400) {
	  console.warn('getSiteArtist("' + artist + '") error:', xhr, 'url:', document.location.origin + requestUrl);
	  return undefined; // error
	}
	let response = JSON.parse(xhr.responseText);
	if (response.status != 'success') {
	  notSiteArtistsCache.pushUniqueCaseless(artist);
	  return null;
	}
	siteArtistsCache[artist] = response.response;
	if (prefs.diag_mode) console.log('getSiteArtist("' + artist + '") success:', siteArtistsCache[artist]);
	return (siteArtistsCache[artist]);
  } catch(e) {
	console.error('UA::getSiteArtist("' + artist + '"):', e, xhr);
	return undefined;
  }
}

function splitArtists(str, parsers = multiArtistParsers) {
  var result = [str];
  parsers.forEach(function(parser) {
	for (var i = result.length; i > 0; --i) {
	  var j = result[i - 1].split(parser).map(strip);
	  if (j.length > 1 && j.every(twoOrMore) && !j.some(artist => pseudoArtistParsers.some(rx => rx.test(artist)))
		  && !getSiteArtist(result[i - 1])) result.splice(i - 1, 1, ...j);
	}
  });
  return result;
}

function splitAmpersands(artists) {
  if (typeof artists == 'string') var result = splitArtists(artists);
  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 || !j.every(looksLikeTrueName) || getSiteArtist(result[i - 1])) continue;
	  result.splice(i - 1, 1, ...j.filter(function(artist) {
		return !result.includesCaseless(artist) && !pseudoArtistParsers.some(rx => rx.test(artist));
	  }));
	}
  });
  return result;
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址