// ==UserScript==
// @name Gazelle extract featured artists from description
// @namespace https://gf.qytechs.cn/cs/users/321857-anakunda
// @version 1.1
// @description Tries to recognize and add featured artists from selected text in description
// @author Anakunda
// @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==
var artist_index;
var modal = null, btnAdd = null, btnCustom = null, customCtrls = [], sel = null;
var prefs = {
set: function(prop, def) { this[prop] = GM_getValue(prop, def) }
};
(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 {
content: "";
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;"> <i>(HRA style)</i></span>',
/^\s*(.*?)(?:\s*,\s*(.*?))?\s*$/);
addPreset(++presetIndex, escapeHTML('<artist(s)>[: <assignment>]'), /^\s*(.*?)(?:\s*:+\s*(.*?))?\s*$/);
addPreset(++presetIndex, escapeHTML('[<assignment> - ]<artist(s)>'), /^\s*(?:(.*?)\s*[\-\−\—\~\–]+\s*)?(.*?)\s*$/, [2, 1]);
addPreset(++presetIndex, escapeHTML('[<assignment>: ]<artist(s)>'), /^\s*(?:(.*?)\s*:+\s*)?(.*?)\s*$/, [2, 1]);
addPreset(++presetIndex, escapeHTML('<artist>[ / <assignment>]'), /^\s*(.*?)(?:\s*\/+\s*(.*?))?\s*$/);
addPreset(++presetIndex, escapeHTML('<artist>[; <assignment>]'), /^\s*(.*?)(?:\s*;\s*(.*?))?\s*$/);
addPreset(++presetIndex, escapeHTML('[<assignment> / ]<artist(s)>'), /^\s*(?:(.*?)\s*\/+\s*)?(.*?)\s*$/, [2, 1]);
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);
if (custom_parse_order != null) {
order = custom_parse_order.value == 1 ? [1, 2] : custom_parse_order.value == 2 ? [2, 1] : null;
} else {
order = [1, 2];
}
}
const artistSplit = /\s*[\,\;\/\|]\s*/;
function extr_artists(kind) { return document.querySelectorAll('ul#artist_list > li.artist_' + kind + ' > a') }
var artists = [
extr_artists('main'),
extr_artists('guest'),
extr_artists('remixer'),
extr_artists('composer'),
extr_artists('conductor'),
extr_artists('compiler'),
extr_artists('producer'),
];
cleanupArtistsForm();
var addedartists = new Array(7);
addedartists.fill([]);
artist_index = 0;
for (var line of sel.split(/[\r\n]+/)) {
if (!line || !line.trim()) continue;
if (line.search(/^\s*(?:Recorded\b)/i) >= 0) continue;
var matches = line.match(/^\s*Produced by (.+?)\s*$/);
if (matches) {
add_artist(matches[1], 7);
} else if (matches = rx.exec(line)) {
if (matches[order[0]]) {
matches[order[0]].split(artistSplit).forEach(k => { add_artist(k, deduce_artist(matches[order[1]])) });
} else {
matches[order[1]].split(artistSplit).forEach(k => { add_artist(k) });
}
}
}
prefs.custom_pattern = customCtrls[0].value;
prefs.custom_pattern_order = custom_parse_order != null ? custom_parse_order.value : 1;
for (var i in prefs) { if (typeof prefs[i] != 'function') GM_setValue(i, prefs[i]) }
return;
function deduce_artist(str) {
var result = 2; // guest by default
if (str) {
if (str.search(/\b(?:remix)/i) >= 0) result = 3; // remixer
if (str.search(/\b(?:composer\b|libretto\b|lyric)/i) >= 0) result = 4; // composer
if (str.search(/\b(?:conduct|rirector\b)/i) >= 0) result = 5; // conductor
if (str.search(/\b(?:compiler\b)/i) >= 0) result = 5; // conductor
if (str.search(/\b(?:producer\b|produced by\b)/i) >= 0) result = 7; // producer
}
return result;
}
function add_artist(name, type = 1) {
if (!name || !type) return false;
// avoid dupes
for (var i of artists[0]) { if (name == i.textContent) return false }
if (type >= 2) for (i of artists[type - 1]) { if (name == i.textContent) return false }
for (i of addedartists[0]) { if (name == i) return false }
if (type >= 2) for (i of addedartists[type - 1]) { if (name == i) return false }
var id = get_artist_field(artist_index);
if (id == null) {
add_artist_field();
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 add_artist_field() { exec(function() { AddArtistField() }) }
function exec(fn) {
let script = document.createElement('script');
script.type = 'application/javascript';
script.textContent = '(' + fn + ')();';
document.body.appendChild(script); // run the script
document.body.removeChild(script); // clean up
}
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);
}