// ==UserScript==
// @name RED (+ NWCD, Orpheus) Upload Assistant
// @namespace https://greasyfork.org/users/321857-anakunda
// @version 1.139
// @description Accurate filling the upload and group edit forms based on foobar2000's playlist selection via pasted output of copy command, release consistency check, two tracklist layouts, basic colours customization, featured artists extraction, image URl fetching from store and more. As alternative to copied playlist, URL to product in supported webstore can be used -- see below for the list.
// @author Anakunda
// @iconURL https://redacted.ch/favicon.ico
// @match https://redacted.ch/upload.php*
// @match https://redacted.ch/torrents.php?action=editgroup*
// @match https://redacted.ch/requests.php?action=new*
// @match https://redacted.ch/requests.php?action=edit*
// @match https://notwhat.cd/upload.php*
// @match https://notwhat.cd/torrents.php?action=editgroup*
// @match https://notwhat.cd/requests.php?action=new*
// @match https://notwhat.cd/requests.php?action=edit*
// @match https://orpheus.network/upload.php*
// @match https://orpheus.network/torrents.php?action=editgroup*
// @match https://orpheus.network/requests.php?action=new*
// @match https://orpheus.network/requests.php?action=edit*
// @connect file://*
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_log
// ==/UserScript==
// The pattern for built-in copy command or custom Text Tools quick copy command, which is handled by this helper is:
// [$fix_eol(%album artist%,)]$char(30)[$fix_eol(%album%,)]$char(30)[$if3(%date%,%ORIGINAL RELEASE DATE%,%year%)]$char(30)[$if3(%releasedate%,%retail date%,%date%,%year%)]$char(30)[$fix_eol($if3(%label%,%publisher%,%COPYRIGHT%),)]$char(30)[$fix_eol($if3(%catalog%,%CATALOGNUMBER%,%CATALOG NUMBER%,%labelno%,%catalog #%,%barcode%,%UPC%,%EAN%,%MCN%),)]$char(30)[%country%]$char(30)%__encoding%$char(30)%__codec%$char(30)[%__codec_profile%]$char(30)[%__bitrate%]$char(30)[%__bitspersample%]$char(30)[%__samplerate%]$char(30)[%__channels%]$char(30)[$if3(%media%,%format%,%source%,%MEDIATYPE%,%SOURCEMEDIA%,%discogs_format%)]$char(30)[$fix_eol(%genre%,)]|[$fix_eol(%style%,)]$char(30)[$num(%discnumber%,0)]$char(30)[$num($if2(%totaldiscs%,%disctotal%),0)]$char(30)[$fix_eol(%discsubtitle%,)]$char(30)[%track number%]$char(30)[$num($if2(%totaltracks%,%TRACKTOTAL%),0)]$char(30)[$fix_eol(%title%,)]$char(30)[$fix_eol(%track artist%,)]$char(30)[$if($strcmp($fix_eol(%performer%,),$fix_eol(%artist%,)),,$fix_eol(%performer%,))]$char(30)[$fix_eol($if3(%composer%,%writer%,%SONGWRITER%,%author%,%LYRICIST%),)]$char(30)[$fix_eol(%conductor%,)]$char(30)[$fix_eol(%remixer%,)]$char(30)[$fix_eol($if2(%compiler%,%mixer%),)]$char(30)[$fix_eol($if2(%producer%,%producedby%),)]$char(30)%length_seconds_fp%$char(30)%length_samples%$char(30)[%replaygain_album_gain%]$char(30)[%album dynamic range%]$char(30)[%__tool%][ | %ENCODER%][ | %ENCODER_OPTIONS%]$char(30)[$fix_eol($if2(%url%,%www%),)]$char(30)$directory_path(%path%)$char(30)[$replace($replace($if2(%comment%,%description%),$char(13),$char(29)),$char(10),$char(28))]$char(30)$trim([RELEASETYPE=$replace($if2(%RELEASETYPE%,%RELEASE TYPE%), ,_) ][COMPILATION=%compilation% ][ISRC=%isrc% ][EXPLICIT=%EXPLICIT% ][ASIN=%ASIN% ][DISCOGS_ID=%discogs_release_id% ][SOURCEID=%SOURCEID% ][BPM=%BPM% ])
//
// Fetch release description from URL:
//
// Supported sites for music:
// - qobuz.com
// - highresaudio.com
// - bandcamp.com
// - prestomusic.com
// - discogs.com
// - supraphonline.cz
// - bontonland.cz
//
// Supported sites for ebooks:
// - martinus.cz, martinus.sk
// - goodreads.com
// - databazeknih.cz
//
// Supported sites for applications:
// - sanet.st
'use strict';
var prefs = {
set: function(prop, def) { this[prop] = GM_getValue(prop, def) },
save: function() {
for (var iter in this) { if (typeof this[iter] != 'function') GM_setValue(iter, this[iter]) }
},
};
prefs.set('remap_texttools_newlines', 0); // convert underscores to linebreaks (ambiguous)
prefs.set('clean_on_apply', 0); // clean the input box on successfull fill
prefs.set('keep_meaningles_composers', 0); // keep composers from file tags also for non-composer emphasis works
prefs.set('always_hide_dnu_list', 0); // risky!
prefs.set('single_threshold', 8 * 60); // Max length of single in s
prefs.set('EP_threshold', 28 * 60); // Max time of EP in s
prefs.set('auto_preview_cover', 1);
prefs.set('auto_rehost_cover', 1);
prefs.set('fetch_tags_from_artist', 0); // add n most used tags from release artist (if one) - experimental
prefs.set('always_request_perfect_flac', 0);
prefs.set('request_default_bounty', 0);
// tracklist specific
prefs.set('tracklist_style', 1); // 1: classical, 2: propertional right aligned
prefs.set('max_tracklist_width', 80); // right margin of the right aligned tracklist. should not exceed the group description width on any device
prefs.set('title_separator', '. '); // divisor of track# and title
prefs.set('pad_leader', ' ');
prefs.set('tracklist_head_color', '#4682B4'); // #a7bdd0
prefs.set('tracklist_single_color', '#708080');
// classical tracklist only components colouring
prefs.set('tracklist_disctitle_color', '#008B8B');
prefs.set('tracklist_classicalblock_color', 'Olive');
prefs.set('tracklist_tracknumber_color', '#8899AA');
prefs.set('tracklist_artist_color', '#889B2F');
prefs.set('tracklist_composer_color', '#556B2F');
prefs.set('tracklist_duration_color', '#4682B4');
document.head.appendChild(document.createElement('style')).innerHTML = `
.ua-messages { text-indent: -2em; margin-left: 2em; }
.ua-messages-bg { padding: 15px; text-align: left; background-color: darkslategray; }
.ua-critical { color: red; font-weight: bold; }
.ua-warning { color: #ff8d00; font-weight: 500; }
.ua-info { color: white; }
.ua-button { vertical-align: middle; background-color: transparent; }
.ua-input {
width: 610px; height: 3em;
margin-top: 8px; margin-bottom: 8px;
background-color: antiquewhite;
font-size: small;
}
`;
const isUpload = document.URL.toLowerCase().includes('/upload\.php');
const isEdit = document.URL.toLowerCase().includes('/torrents.php?action=editgroup');
const isRequest = document.URL.toLowerCase().includes('/requests.php?action=new');
const isRequestEdit = document.URL.toLowerCase().includes('/requests.php?action=edit');
var ref, tbl, elem, child, tb, messages = null, domparser = new DOMParser(), dom;
if (isUpload) {
ref = document.querySelector('form#upload_table > div#dynamic_form');
if (ref == null) return;
common1();
let x = [];
x.push(document.createElement('tr'));
x[0].classList.add('ua-button');
child = document.createElement('input');
child.id = 'fill-from-text';
child.value = 'Fill from text (overwrite)';
child.type = 'button';
child.style.width = '13em';
child.onclick = fill_from_text;
x[0].append(child);
elem.append(x[0]);
x.push(document.createElement('tr'));
x[1].classList.add('ua-button');
child = document.createElement('input');
child.id = 'fill-from-text-weak';
child.value = 'Fill from text (keep values)';
child.type = 'button';
child.style.width = '13em';
child.onclick = fill_from_text;
x[1].append(child);
elem.append(x[1]);
common2();
ref.parentNode.insertBefore(tbl, ref);
} else if (isEdit) {
ref = document.querySelector('form.edit_form > div > div > input[type="submit"]');
if (ref == null) return;
ref = ref.parentNode;
ref.parentNode.insertBefore(document.createElement('br'), ref);
common1();
child = document.createElement('input');
child.id = 'append-from-text';
child.value = 'Fill from text (append)';
child.type = 'button';
child.onclick = fill_from_text;
elem.append(child);
common2();
tbl.style.marginBottom = '10px';
ref.parentNode.insertBefore(tbl, ref);
} else if (isRequest) {
ref = document.getElementById('categories');
if (ref == null) return;
ref = ref.parentNode.parentNode.nextElementSibling;
ref.parentNode.insertBefore(document.createElement('br'), ref);
common1();
child = document.createElement('input');
child.id = 'fill-from-text-weak';
child.value = 'Fill from URL';
child.type = 'button';
child.onclick = fill_from_text;
elem.append(child);
common2();
child = document.createElement('td');
child.colSpan = 2;
child.append(tbl);
elem = document.createElement('tr');
elem.append(child);
ref.parentNode.insertBefore(elem, ref);
} else if (isRequestEdit) {
ref = document.querySelector('input#button[type="submit"]');
if (ref == null) return;
ref = ref.parentNode.parentNode;
ref.parentNode.insertBefore(document.createElement('br'), ref);
common1();
child = document.createElement('input');
child.id = 'append-from-text';
child.value = 'Fill from text (append)';
child.type = 'button';
child.onclick = fill_from_text;
elem.append(child);
common2();
tbl.style.marginBottom = '10px';
elem = document.createElement('tr');
child = document.createElement('td');
child.colSpan = 2;
child.append(tbl);
elem.append(child);
ref.parentNode.insertBefore(elem, ref);
}
if ((ref = document.getElementById('image') || document.querySelector('input[name="image"]')) != null) {
ref.ondblclick = clear0;
ref.onmousedown = clear1;
ref.ondrop = clear0;
}
function clear0() { this.value = '' }
function clear1(e) { if (e.button == 1) this.value = '' }
function common1() {
tbl = document.createElement('tr');
tbl.style.backgroundColor = 'darkgoldenrod';
tbl.style.verticalAlign = 'middle';
elem = document.createElement('td');
elem.style.textAlign = 'center';
child = document.createElement('textarea');
child.id = 'UA data';
child.name = 'UA data';
child.classList.add('ua-input');
child.spellcheck = false;
//child.ondblclick = clear0;
child.onmousedown = clear1;
child.ondrop = clear0;
//child.onpaste = fill_from_text;
elem.append(child);
tbl.append(elem);
elem = document.createElement('td');
elem.style.textAlign = 'center';
}
function common2() {
tbl.append(elem);
tb = document.createElement('tbody');
tb.append(tbl);
tbl = document.createElement('table');
tbl.id = 'upload assistant';
tbl.append(tb);
}
// if (prefs.always_hide_dnu_list && (ref = document.querySelector('div#content > div:first-of-type')) != null) {
// ref.style.display = 'none'; // Hide DNU list (warning - risky!)
// }
if ((ref = document.getElementById('yadg_input')) != null) ref.ondrop = clear0;
Array.prototype.includesCaseless = function(str) {
return typeof str == 'string' && this.find(it => it.toLowerCase() == str.toLowerCase()) != undefined;
};
Array.prototype.pushUnique = function(item) {
return item && !this.includes(item) ? this.push(item) : false;
};
Array.prototype.pushUniqueCaseless = function(item) {
return item && !this.includesCaseless(item) ? this.push(item) : false;
};
// Array.prototype.getUnique = function(prop) {
// return this.every((it) => it[prop] && it[prop] == this[0][prop]) ? this[0][prop] : null;
// };
Array.prototype.equalTo = function(arr) {
return arr instanceof Array && arr.length == this.length && arr.sort().toString() == this.sort().toString();
}
const excludedCountries = [
/\b(?:United States|USA?)\b/,
/\b(?:United Kingdom|Great Britain|England|GB|UK)\b/,
/\b(?:Europe|European Union|EU)\b/,
/\b(?:Unknown)\b/,
];
class TagManager extends Array {
constructor() {
super();
this.presubstitutions = [
[/\b(?:Singer\/Songwriter)\b/i, 'singer.songwriter'],
[/\b(?:Pop\/Rock)\b/i, 'pop.rock'],
[/\b(?:Folk\/Rock)\b/i, 'folk.rock'],
];
this.substitutions = [
[/^(?:Alternative)(?:\s+and\s+|\s*[&+]\s*)(?:Indie)$/i, 'alternative', 'indie'],
[/^(?:Alternativ)(?:\s+und\s+|\s*[&+]\s*)(?:indie)$/i, 'alternative', 'indie'],
[/^(?:Alternatif)(?:\s+et\s+|\s*[&+]\s*)(?:Inde)$/i, 'alternative', 'indie'],
[/^Pop(?:\s+and\s+|\s*[&+]\s*)Rock$/i, 'pop', 'rock'],
[/^Pop\s*(?:[\-\−\—\–]\s*)?Rock$/i, 'pop.rock'],
[/^Rock(?:\s+and\s+|\s*[&+]\s*)Pop$/i, 'pop', 'rock'],
[/^Rock\s*(?:[\-\−\—\–]\s*)?Pop$/i, 'pop.rock'],
[/^Alt\.Rock$/i, 'alternative.rock'],
[/^AOR$/, 'album.oriented.rock'],
[/^Synth[\s\-\−\—\–]+Pop$/i, 'synthpop'],
[/^Soul(?:\s+and\s+|\s*[&+]\s*)Funk$/i, 'soul', 'funk'],
[/^Funk(?:\s+and\s+|\s*[&+]\s*)Soul$/i, 'soul', 'funk'],
[/^World(?:\s+and\s+|\s*[&+]\s*)Country$/i, 'world.music', 'country'],
[/^Jazz Fusion\s*&\s*Jazz Rock$/i, 'jazz.fusion', 'jazz.rock'],
[/^(?:Singer(?:\s+and\s+|\s*[&+]\s*))?Songwriter$/i, 'singer.songwriter'],
[/^(?:R\s*(?:[\'\’\`][Nn](?:\s+|[\'\’\`]\s*)|&\s*)B|RnB)$/i, 'rhytm.and.blues'],
[/\b(?:Soundtracks?)$/i, 'score'],
[/^(?:Electro)$/i, 'electronic'],
[/^(?:Metal)$/i, 'heavy.metal'],
[/^(?:NonFiction)$/i, 'non.fiction'],
[/^(?:Rap)$/i, 'hip.hop'],
[/^(?:NeoSoul)$/i, 'neo.soul'],
[/^(?:NuJazz)$/i, 'nu.jazz'],
[/^(?:Hardcore)$/i, 'hardcore.punk'],
[/^(?:garage)$/i, 'garage.rock'],
[/^(?:Ambiente)$/i, 'ambient'],
[/^(?:Neo[\s\-\−\—\–]+Classical)$/i, 'neoclassical'],
[/^(?:Bluesy[\s\-\−\—\–]+Rock)$/i, 'blues.rock'],
[/^(?:Be[\s\-\−\—\–]+Bop)$/i, 'bebop'],
[/^(?:Chill)[\s\-\−\—\–]+(?:Out)$/i, 'chillout'],
[/^(?:Atmospheric)[\s\-\−\—\–]+(?:Black)$/, 'atmospheric.black.metal'],
[/^GoaTrance$/i, 'goa.trance'],
// Country aliases
[/^(?:Canada)$/i, 'canadian'],
[/^(?:Australia)$/i, 'australian'],
[/^(?:Japan)$/i, 'japanese'],
[/^(?:Taiwan)$/i, 'thai'],
[/^(?:China)$/i, 'chinese'],
[/^(?:Russia|USSR)$/i, 'russian'],
[/^(?:France)$/i, 'french'],
[/^(?:Germany)$/i, 'german'],
[/^(?:Spain)$/i, 'spanish'],
[/^(?:Italy)$/i, 'italian'],
[/^(?:Sweden)$/i, 'swedish'],
[/^(?:Norway)$/i, 'norwegian'],
[/^(?:Finland)$/i, 'finnish'],
[/^(?:Greece)$/i, 'greek'],
[/^(?:Netherlands|Holland)$/i, 'dutch'],
[/^(?:Belgium)$/i, 'belgian'],
[/^(?:Denmark)$/i, 'danish'],
[/^(?:Austria)$/i, 'austrian'],
[/^(?:Portugal)$/i, 'portugese'],
[/^(?:Switzerland)$/i, 'swiss'],
[/^(?:Czech\s+Republic|Czechia)$/i, 'czech'],
[/^(?:Slovak\s+Republic|Slovakia)$/i, 'slovak'],
[/^(?:Poland)$/i, 'polish'],
[/^(?:Hungary)$/i, 'hungarian'],
[/^(?:Yugoslavia)$/i, 'yugoslav'],
[/^(?:Brazil)$/i, 'brazilian'],
[/^(?:Mexico)$/i, 'mexican'],
[/^(?:Argentina)$/i, 'argentinean'],
[/^(?:Jamaica)$/i, 'jamaican'],
];
this.additions = [
[/^(?:(?:(?:Be|Post|Neo)[\s\-\−\—\–]*)?Bop|Modal|Fusion|Free[\s\-\−\—\–]+Improvisation|Jazz[\s\-\−\—\–]+Fusion|Big[\s\-\−\—\–]*Band)$/i, 'jazz'],
[/^(?:(?:Free|Cool|Avant[\s\-\−\—\–]*Garde|Contemporary|Vocal|Instrumental|Crossover|Modal|Mainstream|Modern|Soul|Smooth|Piano|Latin|Afro[\s\-\−\—\–]*Cuban)[\s\-\−\—\–]+Jazz)$/i, 'jazz'],
[/^(?:Opera)$/i, 'classical'],
[/\b(?:Chamber[\s\-\−\—\–]+Music)\b/i, 'classical'],
[/\b(?:Orchestral[\s\-\−\—\–]+Music)\b/i, 'classical'],
[/^(?:Symphony)$/i, 'classical'],
];
this.removals = [
/^(?:Unknown)$/i,
/^(?:Other)$/i,
/^(?:Ostatni)$/i,
].concat(excludedCountries);
}
add(...tags) {
var added = 0;
for (var tag of tags) {
if (typeof tag != 'string') continue;
this.presubstitutions.forEach(k => { if (k[0].test(tag)) tag = tag.replace(k[0], k[1]) });
tag.split(/\s*[\,\/\;\>\|]+\s*/).forEach(function(tag) {
tag = tag.normalize("NFD").
replace(/[\u0300-\u036f]/g, '').
replace(/\(.*?\)|\[.*?\]|\{.*?\}/g, '').
trim();
if (tag.length <= 0 || tag == '?') return null;
function test(obj) {
return obj instanceof RegExp && obj.test(tag)
|| typeof obj == 'string' && tag.toLowerCase() == obj.toLowerCase();
}
if (this.removals.some(k => test(k))) {
addMessage('Warning: bad tag \'' + tag + '\' found', 'ua-warning');
return;
}
for (var k of this.additions) {
if (test(k[0])) added += this.add(...k.slice(1));
}
for (k of this.substitutions) {
if (test(k[0])) { added += this.add(...k.slice(1)); return; }
}
tag = tag.
replace(/\bAlt\.(?=\s+)/i, 'Alternative').
replace(/^[3-9]0s$/i, '19$0').
replace(/^[0-2]0s$/i, '20$0').
replace(/\b(Psy)[\s\-\−\—\–]+(Trance|Core|Chill)\b/i, '$1$2').
replace(/\s*(?:[\'\’\`][Nn](?:\s+|[\'\’\`]\s*)|[\&\+]\s*)/, ' and ').
replace(/[\s\-\−\—\–\_\.\,\'\`\~]+/g, '.').
replace(/[^\w\.]+/g, '').
toLowerCase();
if (tag.length >= 2 && !this.includes(tag)) {
this.push(tag);
++added;
}
}.bind(this));
}
return added;
}
toString() {
return this.length > 0 ? this.sort().join(', ') : null;
}
};
if ((ref = document.getElementById('categories')) != null) {
ref.addEventListener('change', function(e) {
elem = document.getElementById('upload assistant');
if (elem != null) elem.style.visibility = this.value < 4
|| ['Music', 'Applications', 'E-Books', 'Audiobooks'].includes(this.value) ? 'visible' : 'collapse';
});
}
return;
function fill_from_text(e) {
var overwrite = this.id == 'fill-from-text';
var clipBoard = document.getElementById('UA data');
if (clipBoard == null) return false;
const urlParser = /^\s*(https?:\/\/[\S]+)\s*$/i;
messages = document.getElementById('UA messages');
//let promise = clientInformation.clipboard.readText().then(text => clipBoard = text);
//if (typeof clipBoard != 'string') return false;
var i, matches, url, category = document.getElementById('categories');
if (category == null && document.getElementById('releasetype') != null
|| category != null && (category.value == 0 || category.value == 'Music')) return fill_from_text_music();
if (category != null && (category.value == 1 || category.value == 'Applications')) return fill_from_text_apps();
if (category != null && (category.value == 2 || category.value == 3
|| category.value == 'E-Books' || category.value == 'Audiobooks')) return fill_from_text_books();
return category == null ? fill_from_text_apps() || fill_from_text_books() : false;
function fill_from_text_music() {
if (messages != null) messages.parentNode.removeChild(messages);
const divs = ['—', '⸺', '⸻'];
var track, tracks = [], totalDiscs = 1, media;
const multiArtistParser = /\s*(?:[;\/\|]|,(?!\s*(?:[JjSs]r)\b)(?:\s*and\s+)?)\s*/;
const ampersandParser = /\s+(?:[\&\+]|and)(?!\s*(?:The|his|her|Friends)\b)\s+/i;
const featParsers = [
/\s+(?:meets)\s+(.*?)\s*$/,
/\s+(?:featuring|with)\s+(.*?)\s*$/,
/\s+feat\.\s+(.*?)\s*$/,
/\s+\[(?:feat(?:\.|uring)|with)\s+([^\[\]]+?)\s*\]/,
/\s+\((?:feat(?:\.|uring)|with)\s+([^\(\)]+?)\s*\)/,
];
if (urlParser.test(clipBoard.value)) return init_from_url_music(RegExp.$1);
function ruleLink(rule) { return ' (<a href="https://redacted.ch/rules.php?p=upload#r' + rule + '" target="_blank">' + rule + '</a>)' }
var albumBitrate = 0, totalTime = 0;
for (iter of clipBoard.value.split(/[\r\n]+/)) {
if (!iter.trim()) continue; // skip empty lines
let metaData = iter.split('\x1E');
track = {
artist: metaData.shift().trim() || undefined,
album: metaData.shift().trim() || undefined,
album_year: safeParseYear(metaData.shift().trim()),
release_date: metaData.shift().trim() || undefined,
label: metaData.shift().trim() || undefined,
catalog: metaData.shift().trim() || undefined,
country: metaData.shift().trim() || undefined,
encoding: metaData.shift().trim() || undefined,
codec: metaData.shift().trim() || undefined,
codec_profile: metaData.shift().trim() || undefined,
bitrate: safeParseInt(metaData.shift()),
bd: safeParseInt(metaData.shift()),
sr: safeParseInt(metaData.shift()),
channels: safeParseInt(metaData.shift()),
media: metaData.shift().trim() || undefined,
genre: metaData.shift().trim() || undefined,
discnumber: safeParseInt(metaData.shift()),
totaldiscs: safeParseInt(metaData.shift()),
discsubtitle: metaData.shift().trim() || undefined,
tracknumber: metaData.shift().trim() || undefined,
totaltracks: safeParseInt(metaData.shift()),
title: metaData.shift().trim() || undefined,
track_artist: metaData.shift().trim() || undefined,
performer: metaData.shift().trim() || undefined,
composer: metaData.shift().trim() || undefined,
conductor: metaData.shift().trim() || undefined,
remixer: metaData.shift().trim() || undefined,
compiler: metaData.shift().trim() || undefined,
producer: metaData.shift().trim() || undefined,
duration: safeParseFloat(metaData.shift()),
samples: safeParseInt(metaData.shift()),
rg: metaData.shift().trim() || undefined,
dr: metaData.shift().trim() || undefined,
vendor: metaData.shift().trim() || undefined,
url: metaData.shift().trim() || undefined,
dirpath: metaData.shift() || undefined,
comment: metaData.shift().trim() || undefined,
identifiers: {},
};
if (!track.artist) {
addMessage('FATAL: main artist must be defined in every track' + ruleLink('2.3.16.4'), 'ua-critical', true);
clipBoard.value = '';
throw new Error('artist missing');
}
if (!track.album) {
addMessage('FATAL: album title must be defined in every track' + ruleLink('2.3.16.4'), 'ua-critical', true);
clipBoard.value = '';
throw new Error('album mising');
}
if (!track.tracknumber) {
addMessage('FATAL: all track numbers must be defined' + ruleLink('2.3.16.4'), 'ua-critical', true);
clipBoard.value = '';
throw new Error('tracknumber missing');
}
if (!track.title) {
addMessage('FATAL: all track titles must be defined' + ruleLink('2.3.16.4'), 'ua-critical', true);
clipBoard.value = '';
throw new Error('track title missing');
}
if (track.duration != undefined && isUpload && (isNaN(track.duration) || track.duration <= 0)) {
addMessage('FATAL: invalid track #' + track.tracknumber + ' length: ' + track.duration, 'ua-critical');
clipBoard.value = '';
throw new Error('invalid duration');
}
if (track.codec && !['FLAC', 'MP3', 'AAC', 'DTS', 'AC3'].includes(track.codec)) {
addMessage('FATAL: disallowed codec present (' + track.codec + ')', 'ua-critical');
clipBoard.value = '';
throw new Error('invalid format');
}
if (track.discnumber > totalDiscs) totalDiscs = track.discnumber;
if (track.comment == '.') track.comment = undefined;
if (track.comment) {
track.comment = track.comment.replace(/\x1D/g, '\r').replace(/\x1C/g, '\n');
if (prefs.remap_texttools_newlines) track.comment = track.comment.replace(/__/g, '\r\n').replace(/_/g, '\n') // ambiguous
}
if (track.dr != null) track.dr = parseInt(track.dr); // DR0
metaData.shift().trim().split(/\s+/).forEach(function(it) {
if (/([\w\-]+)[=:](.*)/.test(it)) track.identifiers[RegExp.$1.toUpperCase()] = RegExp.$2.replace(/\x1B/g, ' ');
});
totalTime += track.duration || NaN;
albumBitrate += (track.duration || NaN) * (track.bitrate || NaN);
tracks.push(track);
function safeParseInt(x) { return typeof x != 'string' ? null : x.length <= 0 ? undefined : parseInt(x) }
function safeParseFloat(x) { return typeof x != 'string' ? null : x.length <= 0 ? undefined : parseFloat(x) }
function safeParseYear(x) { return typeof x != 'string' ? null : x.length <= 0 ? undefined : extract_year(x) || NaN }
}
if (tracks.length <= 0) {
addMessage('FATAL: no tracks found', 'ua-critical', true);
clipBoard.value = '';
throw new Error('no tracks');
}
if (!tracks.every(it => it.discnumber > 0) && !tracks.every(it => !it.discnumber)) {
addMessage('FATAL: inconsistent release (mix of tracks with and without disc number)', 'ua-critical', true);
clipBoard.value = '';
throw new Error('inconsistent disc numbering');
}
var release = {};
['catalogs', 'bds', 'genres', 'srs', 'urls', 'comments', 'trackArtists', 'bitrates',
'drs', 'rgs', 'dirpaths'].forEach(it => { release[it] = [] });
function setUniqueProperty(propName, propNameLiteral) {
let homogenous = new Set(tracks.map(it => it[propName]).filter(it => it != undefined && it != null));
if (homogenous.size > 1) {
var diverses = '', it = homogenous.values(), val;
while (!(val = it.next()).done) diverses += '<br>\t' + val.value;
addMessage('FATAL: mixed releases not accepted (' + propNameLiteral + ') - supposedly user compilation' + diverses, 'ua-critical', true);
clipBoard.value = '';
throw new Error('mixed release (' + propNameLiteral + ')');
}
release[propName] = homogenous.values().next().value;
}
setUniqueProperty('artist', 'album artist', true);
setUniqueProperty('album', 'album title', true);
setUniqueProperty('album_year', 'album year');
setUniqueProperty('release_date', 'release date');
setUniqueProperty('encoding', 'encoding');
setUniqueProperty('codec', 'codec');
setUniqueProperty('codec_profile', 'codec profile');
setUniqueProperty('vendor', 'vendor');
setUniqueProperty('media', 'media');
setUniqueProperty('channels', 'channels');
setUniqueProperty('label', 'label');
setUniqueProperty('country', 'country');
tracks.forEach(function(iter) {
push_unique('trackArtists', 'track_artist');
push_unique('catalogs', 'catalog');
push_unique('bitrates', 'bitrate');
push_unique('bds', 'bd');
push_unique('rgs', 'rg');
push_unique('drs', 'dr');
if (iter.sr) {
if (typeof release.srs[iter.sr] != 'number') {
release.srs[iter.sr] = iter.duration;
} else {
release.srs[iter.sr] += iter.duration;
}
}
push_unique('dirpaths', 'dirpath');
push_unique('comments', 'comment');
push_unique('genres', 'genre');
push_unique('urls', 'url');
function push_unique(relProp, prop) {
if (iter[prop] !== undefined && iter[prop] !== null && (typeof iter[prop] != 'string'
|| iter[prop].length > 0) && !release[relProp].includes(iter[prop])) release[relProp].push(iter[prop]);
}
});
function validatorFunc(arr, validator, str) {
if (arr.length <= 0 || !arr.some(validator)) return true;
addMessage('FATAL: disallowed ' + str + ' present (' + arr.filter(validator) + ')', 'ua-critical');
clipBoard.value = '';
throw new Error('disallowed ' + str);
}
validatorFunc(release.bds, (bd) => ![16, 24].includes(bd), 'bit depths');
validatorFunc(Object.keys(release.srs),
(sr) => sr < 44100 || sr > 192000 || sr % 44100 != 0 && sr % 48000 != 0, 'sample rates');
var composerEmphasis = false, isFromDVD = false, isClassical = false;
var yadg_prefil = '', releaseType, editionTitle, isVA, iter, rx;
var tags = new TagManager();
albumBitrate /= totalTime;
if (tracks.every(it => /^single$/i.test(it.identifiers.RELEASETYPE))
|| totalTime > 0 && totalTime <= prefs.single_threshold) {
releaseType = getReleaseIndex('Single');
} else if (tracks.every(it => it.identifiers.RELEASETYPE == 'EP')
|| totalTime > 0 && totalTime <= prefs.EP_threshold) {
releaseType = getReleaseIndex('EP');
} else if (tracks.every(it => /^soundtrack$/i.test(it.identifiers.RELEASETYPE))) {
releaseType = getReleaseIndex('Soundtrack');
tags.add('score');
composerEmphasis = true;
}
// Processing artists: recognition, splitting and dividing to categores
const remixParsers = [
/\s+\(Remix(?:e[sd])?\)/i,
/\s+\[Remix(?:e[sd])?\]/i,
/\s+Remix(?:e[sd])?\s*$/i,
/\s+\(([^\(\)]+?)(?:[\'\’\`]s)? remix\)/i,
/\s+\[([^\[\]]+?)(?:[\'\’\`]s)? remix\]/i,
/\s+\(remix(?:ed)? by ([^\(\)]+)\)/i,
/\s+\[remix(?:ed)? by ([^\[\]]+)\]/i,
];
const otherArtistsParsers = [
[/^(.*?)\s+(?:under|(?:conducted) by)\s+(.*)$/, 4],
[/^()(.*?)\s+\(conductor\)$/i, 4],
//[/^()(.*?)\s+\(.*\)$/i, 1],
];
const noAkas = /\s+(?:aka|AKA)\s+(.*)/;
const invalidArtist = /^(?:#?N\/?A|[JS]r\.?)$/i;
isVA = release.artist == 'VA' || /^(?:Various(?:\s+Artists?)?)$/i.test(release.artist);
var artists = [], xhr = new XMLHttpRequest();
for (iter = 0; iter < 7; ++iter) artists[iter] = [];
if (!isVA) addArtists(0, yadg_prefil = spliceGuests(release.artist));
featParsers.slice(3).forEach(function(rx) {
if (rx.test(release.album)) {
addArtists(1, RegExp.$1);
addMessage('Warning: featured artist(s) in album title (' + release.album + ')', 'ua-warning');
release.album = release.album.replace(rx, '');
}
});
remixParsers.slice(3).forEach(function(rx) {
if (rx.test(release.album)) addArtists(2, RegExp.$1);
})
for (iter of tracks) {
addTrackPerformers(iter.track_artist);
addTrackPerformers(iter.performer);
addArtists(2, iter.remixer);
addArtists(3, iter.composer);
addArtists(4, iter.conductor);
addArtists(5, iter.compiler);
addArtists(6, iter.producer);
if (iter.title) {
featParsers.slice(3).forEach(function(rx) {
if (rx.test(iter.title)) {
addArtists(1, RegExp.$1);
iter.track_artist = (!isVA && (!iter.track_artist || iter.track_artist.includes(RegExp.$1)) ?
iter.artist : iter.track_artist) + ' feat. ' + RegExp.$1;
addMessage('Warning: featured artist(s) in track title (#' + iter.tracknumber + ': ' + iter.title + ')', 'ua-warning');
iter.title = iter.title.replace(rx, '');
}
});
remixParsers.slice(3).forEach(function(rx) {
if (rx.test(iter.title)) addArtists(2, RegExp.$1);
});
}
}
// Split ampersands
for (iter = 0; iter < Math.round(tracks.length / 2); ++iter) {
for (let ndx = 0; ndx < 7; ++ndx) {
for (i = artists[ndx].length; i > 0; --i) {
let j = artists[ndx][i - 1].split(ampersandParser);
if (j.length >= 2 && j.every(twoOrMore) && !getSiteArtist(artists[ndx][i - 1])
&& (j.some(it1 => artists.some(it2 => it2.includesCaseless(it1))) || j.every(looksLikeTrueName))) {
artists[ndx].splice(i - 1, 1, ...j.filter(function(it) {
return !artists[ndx].includesCaseless(it) && (ndx != 1 || !artists[0].includesCaseless(it));
}));
}
}
}
}
function addArtists(index, str) {
if (str) splitArtists(str).forEach(function(it) {
it = index != 0 ? it.replace(noAkas, '') : guessOtherArtists(it);
if (it.length > 0 && !invalidArtist.test(it) && !artists[index].includesCaseless(it)
&& (index != 1 || !artists[0].includesCaseless(it))) artists[index].push(it);
});
}
function addTrackPerformers(str) {
if (str) splitArtists(spliceGuests(str, 1)).forEach(function(it) {
it = guessOtherArtists(it);
if (it.length > 0 && !invalidArtist.test(it) && !artists[0].includesCaseless(it)
&& (isVA || !artists[1].includesCaseless(it))) artists[isVA ? 0 : 1].push(it);
});
}
function splitArtists(str) {
var j = str.split(multiArtistParser);
return j.length == 1 || j.every(twoOrMore)
&& !j.some(a => invalidArtist.test(a)) && !getSiteArtist(str) ? j : [ str ];
}
function spliceGuests(str, level) {
(level ? featParsers.slice(level) : featParsers).forEach(function(it) {
if (it.test(str)) {
addArtists(1, RegExp.$1);
str = str.replace(it, '');
}
});
return str;
}
function guessOtherArtists(name) {
otherArtistsParsers.forEach(function(it) {
if (!it[0].test(name)) return;
addArtists(it[1], RegExp.$2);
name = RegExp.$1;
});
return name.replace(noAkas, '');
}
function getSiteArtist(artist) {
if (!artist) return null;
xhr.open('GET', 'https://' + document.domain + '/ajax.php?action=artist&artistname=' + encodeURIComponent(artist), false);
xhr.send();
if (xhr.readyState != 4 || xhr.status != 200) {
console.log('getSiteArtist("' + artist + '"): XMLHttpRequest readyState:' + xhr.readyState + ' status:' + xhr.status);
return undefined; // error
}
var response = JSON.parse(xhr.responseText);
return response.status == 'success' ? response.response : null;
}
function twoOrMore(artist) { return artist.length >= 2 && !invalidArtist.test(artist) };
function looksLikeTrueName(artist, index) {
return (index == 0 || !/^(?:The|his|her|Friends)\s+/i.test(artist)) && artist.split(/\s+/).length >= 2
|| getSiteArtist(artist);
}
if (element_writable(document.getElementById('artist'))) {
const artistSel = 'input[name="artists[]"]';
let artist_index = 0;
feedArtistCategory(artists[0].filter(k => !artists[4].includesCaseless(k)), 1);
feedArtistCategory(artists[1].filter(k => !artists[0].includesCaseless(k) && !artists[4].includesCaseless(k)), 2);
for (i = 2; i < 7; ++i) feedArtistCategory(artists[i], i + 1);
if (overwrite) while (document.getElementById('artist_' + artist_index) != null) {
exec(function() { RemoveArtistField() });
}
function feedArtistCategory(list, type) {
for (iter of list.sort()) {
if (isUpload) {
var id = 'artist';
if (artist_index > 0) id += '_' + artist_index;
while ((ref = document.getElementById(id)) == null) add_artist();
} else {
ref = document.querySelectorAll(artistSel);
if (ref.length < artist_index + 1) {
for (i = ref.length; i < artist_index + 1; ++i) add_artist();
ref = document.querySelectorAll(artistSel);
}
ref = ref[artist_index];
}
if (ref == null) continue;
ref.value = iter;
ref.nextElementSibling.value = type;
++artist_index;
}
}
}
// Processing album title
const remasterParsers = [
/\s+\(([^\(\)]*\b(?:Remaster(?:ed)?\b[^\(\)]*|Reissue|Edition|Version|Promo|Enhanced|Release))\)$/i,
/\s+\[([^\[\]]*\b(?:Remaster(?:ed)?\b[^\[\]]*|Reissue|Edition|Version|Promo|Enhanced|Release))\]$/i,
/\s+-\s+([^\[\]\(\)\-\−\—\–]*\b(?:(?:Remaster(?:ed)?|Bonus\s+Track)\b[^\[\]\(\)\-\−\—\–]*|Reissue|Edition|Version|Promo|Enhanced|Release))$/i
];
const mediaParsers = [
[/\s+(?:\[(?:LP|Vinyl|12"|7")\]|\((?:LP|Vinyl|12"|7")\))$/, 'Vinyl'],
[/\s+(?:\[SA-?CD\]|\(SA-?CD\))$/, 'SACD'],
[/\s+(?:\[(?:Blu[\s\-\−\—\–]?Ray|B[DR])\]|\((?:Blu[\s\-\−\—\–]?Ray|B[DR])\))$/, 'Blu-Ray'],
[/\s+(?:\[DVD(?:-?A)?\]|\(DVD(?:-?A)?\))$/, 'DVD'],
];
const releaseTypeParsers = [
[/\s+(?:-\s+Single|\[Single\]|\(Single\))$/i, 'Single', true, true],
[/\s+(?:(?:-\s+)?EP|\[EP\]|\(EP\))$/, 'EP', true, true],
[/\s+\((?:Live|En\s+directo?|Ao\s+Vivo)\b[^\(\)]*\)$/i, 'Live album', false, false],
[/\s+\[(?:Live|En\s+directo?|Ao\s+Vivo)\b[^\[\]]*\]$/i, 'Live album', false, false],
[/(?:^Live\s+(?:[aA]t|[Ii]n)\b|^Directo?\s+[Ee]n\b|\bUnplugged\b|\bAcoustic\s+Stage\b|\s+Live$)/, 'Live album', false, false],
[/\b(?:Best [Oo]f|Greatest Hits|Complete\s+(.+?\s+)(?:Albums|Recordings))\b/, 'Anthology', false, false],
];
var album = release.album;
releaseTypeParsers.forEach(function(it) {
if (it[0].test(album)) {
if (it[2] || !releaseType) releaseType = getReleaseIndex(it[1]);
if (it[3]) album = album.replace(it[0], '');
}
});
rx = '\\b(?:Soundtrack|Score|Motion\\s+Picture|Series|Television|Original(?:\\s+\\w+)?\\s+Cast|Music\\s+from|(?:Musique|Bande)\\s+originale)\\b';
if (reInParenthesis(rx).test(album) || reInBrackets(rx).test(album)) {
if (!releaseType) releaseType = getReleaseIndex('Soundtrack');
tags.add('score');
composerEmphasis = true;
}
remixParsers.forEach(function(rx) {
if (rx.test(album) && !releaseType) releaseType = getReleaseIndex('Remix');
});
remasterParsers.forEach(function(rx) {
if (rx.test(album)) {
album = album.replace(rx, '');
editionTitle = RegExp.$1;
}
});
mediaParsers.forEach(function(it) {
if (it[0].test(album)) {
album = album.replace(it[0], '');
media = it[1];
}
});
if (element_writable(ref = document.getElementById('title') || document.querySelector('input[name="title"]'))) {
ref.value = album;
}
if (yadg_prefil) yadg_prefil += ' ';
yadg_prefil += album;
if (element_writable(ref = document.getElementById('yadg_input'))) {
ref.value = yadg_prefil || '';
if (yadg_prefil && (ref = document.getElementById('yadg_submit')) != null && !ref.disabled) ref.click();
}
if (element_writable(ref = document.getElementById('year'))) {
ref.value = release.album_year || '';
}
i = release.release_date && extract_year(release.release_date);
if (element_writable(ref = document.getElementById('remaster_year'))
|| !isUpload && i > 0 && (ref = document.querySelector('input[name="year"]')) != null && !ref.disabled) {
ref.value = i || '';
}
//if (tracks.every(it => it.identifiers.EXPLICIT == '0')) editionTitle = 'Clean' + (editionTitle ? ' / ' + editionTitle : '');
if (element_writable(ref = document.getElementById('remaster_title'))) {
ref.value = editionTitle || '';
}
rx = /\s*[\,\;]\s*/g;
if (element_writable(ref = document.getElementById('remaster_record_label') || document.querySelector('input[name="recordlabel"]'))) {
ref.value = release.label && release.label.replace(rx, ' / ') || '';
}
if (element_writable(ref = document.getElementById('remaster_catalogue_number') || document.querySelector('input[name="cataloguenumber"]'))) {
let barcode = tracks.every(function(it, ndx, arr) {
return it.identifiers.BARCODE && it.identifiers.BARCODE == arr[0].identifiers.BARCODE;
}) && tracks[0].identifiers.BARCODE;
ref.value = release.catalogs.length >= 1 && release.catalogs.map(k => k.replace(rx, ' / ')).join(' / ')
|| barcode || '';
}
var br_isSet = (ref = document.getElementById('bitrate')) != null && ref.value;
if (element_writable(ref = document.getElementById('format'))) {
ref.value = release.codec || '';
ref.onchange(); //exec(function() { Format() });
}
if (isRequest) {
if (prefs.always_request_perfect_flac) reqSelectFormats('FLAC');
else if (release.codec) reqSelectFormats(release.codec);
}
var sel;
if (release.encoding == 'lossless') {
sel = release.bds.includes(24) ? '24bit Lossless' : 'Lossless';
} else if (release.bitrates.length >= 1) {
let lame_version = release.codec == 'MP3' && /^LAME(\d+)\.(\d+)/i.test(release.vendor) ?
parseInt(RegExp.$1) * 1000 + parseInt(RegExp.$2) : undefined;
if (release.codec == 'MP3' && release.codec_profile == 'VBR V0') {
sel = lame_version >= 3094 ? 'V0 (VBR)' : 'APX (VBR)'
} else if (release.codec == 'MP3' && release.codec_profile == 'VBR V1') {
sel = 'V1 (VBR)'
} else if (release.codec == 'MP3' && release.codec_profile == 'VBR V2') {
sel = lame_version >= 3094 ? sel = 'V2 (VBR)' : 'APS (VBR)'
} else if (release.bitrates.length == 1 && [192, 256, 320].includes(Math.round(release.bitrates[0]))) {
sel = Math.round(release.bitrates[0]);
} else {
sel = 'Other';
}
}
if ((ref = document.getElementById('bitrate')) != null && !ref.disabled && (overwrite || !br_isSet)) {
ref.value = sel || '';
ref.onchange(); //exec(function() { Bitrate() });
if (sel == 'Other' && (ref = document.getElementById('other_bitrate')) != null) {
ref.value = Math.round(release.bitrates.length == 1 ? release.bitrates[0] : albumBitrate);
if ((ref = document.getElementById('vbr')) != null) ref.checked = release.bitrates.length > 1;
}
}
if (isRequest) {
if (prefs.always_request_perfect_flac) {
reqSelectBitrates('Lossless', '24bit Lossless');
} else if (sel) reqSelectBitrates(sel);
}
if (release.media) {
sel = undefined;
[
[/\b(?:WEB|File|Download|digital\s+media)\b/i, 'WEB'],
[/\bCD\b/, 'CD'],
[/\b(?:SA-?CD|[Hh]ybrid)\b/, 'SACD'],
[/\b(?:[Bb]lu[\-\−\—\–\s]?[Rr]ay|BRD?|BD)\b/, 'Blu-Ray'],
[/\bDVD(?:-?A)?\b/, 'DVD'],
[/\b(?:[Vv]inyl\b|LP\b|12"|7")/, 'Vinyl'],
].forEach(k => { if (k[0].test(release.media)) sel = k[1] });
media = sel || media;
}
if (element_writable(ref = document.getElementById('media'))) ref.value = media || '';
if (isRequest) {
if (prefs.always_request_perfect_flac) reqSelectMedias('WEB', 'CD', 'Blu-Ray', 'DVD', 'SACD');
else if (media) reqSelectMedias(media);
}
function isRedBook(t) {
return t.bd == 16 && t.sr == 44100 && t.channels == 2 && t.samples > 0 && t.samples % 588 == 0;
}
if (!media) {
if (tracks.every(isRedBook)) {
addMessage('Info: media not determined - CD estimated', 'ua-info');
} else if (tracks.some(t => t.bd > 16 || (t.sr > 0 && t.sr != 44100) || t.samples > 0 && t.samples % 588 != 0)) {
addMessage('Info: media not determined - NOT CD', 'ua-info');
}
} else if (media != 'CD' && tracks.every(isRedBook)) {
addMessage('Info: CD as source media is estimated (' + media + ')', 'ua-info');
}
if (media == 'WEB') for (iter of tracks) {
if (iter.duration > 29.5 && iter.duration < 30.5) {
addMessage('Warning: track ' + iter.tracknumber + ' possible preview', 'ua-warning');
}
}
if (release.genres.length >= 1) {
release.genres.forEach(function(genre) {
if (/\b(?:Classical|Symphony|Symphonic(?:al)?$|Chamber|Choral|Orchestral|Etude|Opera|Duets|Klassik)\b/i.test(genre)
&& !/\b(?:metal|rock|pop)\b/i.test(genre)) {
composerEmphasis = true;
isClassical = true
}
if (/\b(?:Jazz|Vocal)\b/i.test(genre) && !/\b(?:Nu|Future|Acid)[\s\-\−\—\–]*Jazz\b/i.test(genre)
&& !/\bElectr(?:o|ic)[\s\-\−\—\–]?Swing\b/i.test(genre)) {
composerEmphasis = true;
}
if (/\b(?:Soundtracks?|Score|Films?|Games?|Video|Series?|Theatre|Musical)\b/i.test(genre)) {
composerEmphasis = true;
if (!releaseType) releaseType = getReleaseIndex('Soundtrack');
tags.add('score');
composerEmphasis = true;
}
tags.add(genre);
});
if (release.genres.length > 1) {
addMessage('Warning: inconsistent genre accross album: ' + release.genres, 'ua-warning');
}
}
if (release.country) {
if (!excludedCountries.some(it => it.test(release.country))) tags.add(release.country);
}
if (element_writable(ref = document.getElementById('tags'))) {
ref.value = tags.length >= 1 ? tags.toString() : '';
if (artists[0].length == 1 && prefs.fetch_tags_from_artist > 0) setTimeout(function() {
var artist = getSiteArtist(artists[0][0]);
if (!artist) return;
tags.add(...artist.tags.sort((a, b) => b.count - a.count).map(it => it.name).slice(0, prefs.fetch_tags_from_artist));
var ref = document.getElementById('tags');
ref.value = tags.toString();
}, 3000);
}
if (isClassical && !tracks.every(it => it.composer)) {
addMessage('FATAL: all tracks composers must be defined' + ruleLink('2.3.17'), 'ua-critical', true);
return false;
}
if (!releaseType) {
if (isVA) {
releaseType = getReleaseIndex('Compilation');
} else if (tracks.every(it => it.identifiers.COMPILATION == 1)) {
releaseType = getReleaseIndex('Anthology');
}
}
if ((ref = document.getElementById('releasetype')) != null && !ref.disabled && (overwrite || ref.value == 0)) {
ref.value = releaseType || getReleaseIndex('Album');
}
if (!composerEmphasis && !prefs.keep_meaningles_composers) {
document.querySelectorAll('input[name="artists[]"]').forEach(function(i) {
if (['4', '5'].includes(i.nextElementSibling.value)) i.value = '';
});
}
const doubleParsParsers = [
/\(+(\([^\(\)]*\))\)+/,
/\[+(\[[^\[\]]*\])\]+/,
/\{+(\{[^\{\}]*\})\}+/,
];
for (iter of tracks) {
doubleParsParsers.forEach(function(rx) {
if (rx.test(iter.title)) {
addMessage('Warning: doubled parentheses in track #' + iter.tracknumber +
' title ("' + iter.title + '")', 'ua-warning');
//iter.title.replace(rx, RegExp.$1);
}
});
}
if (tracks.length > 1 && array_homogenous(tracks.map(k => k.title))) {
addMessage('Warning: all tracks having same title: ' + tracks[0].title, 'ua-warning');
}
var description;
url = release.urls.length == 1 && release.urls[0];
if (!url && tracks.every(it => it.identifiers.DISCOGS_ID && it.identifiers.DISCOGS_ID == tracks[0].identifiers.DISCOGS_ID)) {
url = 'https://www.discogs.com/release/' + tracks[0].identifiers.DISCOGS_ID;
}
if (isRequest || isRequestEdit) { // isRequest
description = []
if (release.release_date) {
i = new Date(release.release_date);
description.push((i < new Date() ? 'Released' : 'Releasing') + ' ' +
(isNaN(i) ? release.release_date : i.toString().replace(/\s+\d+:.*/, '')));
}
if (url) description.push('[url]' + url + '[/url]');
if (release.catalogs.length == 1 && /^\d{10,}$/.test(release.catalogs[0])
|| tracks.every(it => it.identifiers.BARCODE && it.identifiers.BARCODE == tracks[0].identifiers.BARCODE)
&& /^\d{10,}$/.test(tracks[0].identifiers.BARCODE)) {
description.push('[url=https://www.google.com/search?q=' + RegExp.lastMatch + ']Find more stores...[/url]');
}
if (release.comments.length == 1) description.push(release.comments[0]);
description = description.join('\n\n');
if (description.length > 0) {
ref = document.getElementById('description');
if (element_writable(ref)) {
ref.value = description;
} else if (isRequestEdit && ref != null && !ref.disabled) {
ref.value = ref.textLength > 0 ? ref.value.concat('\n\n', description) : ref.value = description;
preview(0);
}
}
} else {
var ripinfo, dur;
const vinylTest = /^((?:Vinyl|LP) rip by\s+)(.*)$/im;
description = artists[0].length >= 3 ?
'[size=4]' + joinArtists(artists[0], '[artist]', '[/artist]') + ' – ' + release.album + '[/size]\n\n' : '';
// ============================================= The Playlist =============================================
if (tracks.length > 1) {
gen_full_tracklist();
} else { // single
description += '[align=center]';
description += isRED() ? '[pad=20|20|20|20]' : '';
description += '[size=4][b][color=' + prefs.tracklist_artist_color + ']' + release.artist + '[/color][hr]';
//description += '[color=' + prefs.tracklist_single_color + ']';
description += tracks[0].title;
//description += '[/color]'
description += '[/b]';
if (tracks[0].composer) {
description += '\n[i][color=' + prefs.tracklist_composer_color + '](' + tracks[0].composer + ')[/color][/i]';
}
description += '\n\n[color=' + prefs.tracklist_duration_color +'][' +
makeTimeString(tracks[0].duration) + '][/color][/size]';
if (isRED()) description += '[/pad]';
description += '[/align]';
}
if (release.comments.length == 1 && release.comments[0]) {
if (matches = release.comments[0].match(vinylTest)) {
ripinfo = release.comments[0].slice(matches.index).trim().split(/[\r\n]+/);
description = description.concat('\n\n', release.comments[0].slice(0, matches.index).trim());
} else {
description += '\n\n' + release.comments[0];
}
}
if (element_writable(ref = document.getElementById('album_desc'))) {
ref.value = description;
preview(0);
}
if ((ref = document.getElementById('body')) != null && !ref.disabled) {
let editioninfo;
if (editionTitle) {
editioninfo = '[size=5][b]' + editionTitle;
if (release.release_date && (i = extract_year(release.release_date)) > 0) editioninfo += ' (' + i + ')';
editioninfo = editioninfo.concat('[/b][/size]\n\n');
} else { editioninfo = '' }
ref.value = ref.textLength > 0 ?
ref.value.concat('\n\n', editioninfo, description) : editioninfo + description;
preview(0);
}
var lineage = '', comment = '', drinfo, srcinfo;
if (element_writable(ref = document.getElementById('release_samplerate'))) {
ref.value = Object.keys(release.srs).length == 1 ? Math.floor(Object.keys(release.srs)[0] / 1000) :
Object.keys(release.srs).length > 1 ? '999' : null;
}
if (Object.keys(release.srs).length > 0) {
let kHz = Object.keys(release.srs).sort((a, b) => release.srs[b] - release.srs[a])
.map(f => f / 1000).join('/').concat('kHz');
if (release.bds.some(bd => bd > 16)) {
drinfo = '[hide=DR' + (release.drs.length == 1 ? release.drs[0] : '') + '][pre][/pre]';
if (media == 'Vinyl') {
let hassr = ref == null || Object.keys(release.srs).length > 1;
lineage = hassr ? kHz + ' ' : '';
if (ripinfo) {
ripinfo[0] = ripinfo[0].replace(vinylTest, '$1[color=blue]$2[/color]');
if (hassr) { ripinfo[0] = ripinfo[0].replace(/^Vinyl\b/, 'vinyl') }
lineage += ripinfo[0] + '\n\n[u]Lineage:[/u]' + ripinfo.slice(1).map(k => '\n' + k).join('');
} else {
lineage += (hassr ? 'Vinyl' : ' vinyl') + ' rip by [color=blue][/color]\n\n[u]Lineage:[/u]';
}
drinfo += '\n\n[img][/img]\n[img][/img]\n[img][/img][/hide]';
} else if (['Blu-Ray', 'DVD', 'SACD'].includes(media)) {
lineage = ref ? '' : kHz;
if (release.channels) add_channel_info();
if (media == 'SACD' || isFromDVD) {
lineage += ' from DSD64 using foobar2000\'s SACD decoder (direct-fp64)';
lineage += '\nOutput gain +0dB';
}
drinfo += '[/hide]';
//add_rg_info();
} else { // WEB Hi-Res
if (ref == null || Object.keys(release.srs).length > 1) lineage = kHz;
if (release.channels && release.channels != 2) add_channel_info();
add_dr_info();
//if (lineage.length > 0) add_rg_info();
if (release.bds.length > 1) release.bds.filter(bd => bd != 24).forEach(function(bd) {
let hybrid_tracks = tracks.filter(k => k.bd == bd).map(k => k.tracknumber);
if (hybrid_tracks.length < 1) return;
if (lineage) lineage += '\n';
lineage += 'Note: track';
if (hybrid_tracks.length > 1) lineage += 's';
lineage += ' #' + hybrid_tracks.sort().join(', ') +
(hybrid_tracks.length > 1 ? ' are' : ' is') + ' ' + bd + 'bit lossless';
});
if (Object.keys(release.srs).length == 1 && Object.keys(release.srs)[0] == 88200) {
drinfo += '[/hide]';
} else {
drinfo = null;
}
}
} else { // 16bit or lossy
if (Object.keys(release.srs).some(f => f != 44100)) lineage = kHz;
if (release.channels && release.channels != 2) add_channel_info();
//add_dr_info();
//if (lineage.length > 0) add_rg_info();
if (['AAC', 'Opus', 'Vorbis'].includes(release.codec) && release.vendor) {
let _encoder_settings = release.vendor;
if (release.codec == 'AAC' && /^qaac\s+[\d\.]+/i.test(release.vendor)) {
let enc = [];
if (matches = release.vendor.match(/\bqaac\s+([\d\.]+)\b/i)) enc[0] = matches[1];
if (matches = release.vendor.match(/\bCoreAudioToolbox\s+([\d\.]+)\b/i)) enc[1] = matches[1];
if (matches = release.vendor.match(/\b(AAC-\S+)\s+Encoder\b/i)) enc[2] = matches[1];
if (matches = release.vendor.match(/\b([TC]VBR|ABR|CBR)\s+(\S+)\b/)) { enc[3] = matches[1]; enc[4] = matches[2]; }
if (matches = release.vendor.match(/\bQuality\s+(\d+)\b/i)) enc[5] = matches[1];
_encoder_settings = 'Converted by Apple\'s ' + enc[2] + ' encoder (' + enc[3] + '-' + enc[4] + ')';
}
if (lineage) lineage += '\n\n';
lineage += _encoder_settings;
}
}
}
function add_dr_info() {
if (release.drs.length != 1 || document.getElementById('release_dynamicrange') != null) return false;
if (lineage.length > 0) lineage += ' | ';
if (release.drs[0] < 4) lineage += '[color=red]';
lineage += 'DR' + release.drs[0];
if (release.drs[0] < 4) lineage += '[/color]';
return true;
}
function add_rg_info() {
if (release.rgs.length != 1) return false;
if (lineage.length > 0) lineage += ' | ';
lineage += 'RG'; //lineage += 'RG ' + rgs[0];
return true;
}
function add_channel_info() {
if (!release.channels) return false;
let chi = getChanString(release.channels);
if (lineage.length > 0 && chi.length > 0) lineage += ', ';
lineage += chi;
return chi.length > 0;
}
if (url) srcinfo = '[url]' + url + '[/url]';
if ((ref = document.getElementById('release_lineage')) != null) {
if (element_writable(ref)) {
if (drinfo) comment = drinfo;
if (lineage && srcinfo) lineage += '\n\n';
if (srcinfo) lineage += srcinfo;
ref.value = lineage;
preview(1);
}
} else {
comment = lineage;
if (comment && drinfo) comment += '\n\n';
if (drinfo) comment += drinfo;
if (comment && srcinfo) comment += '\n\n';
if (srcinfo) comment += srcinfo;
}
if (element_writable(ref = document.getElementById('release_desc'))) {
ref.value = comment;
if (comment.length > 0) preview(isNWCD() ? 2 : 1);
}
if (release.encoding == 'lossless' && release.codec == 'FLAC'
&& release.bds.includes(24) && release.dirpaths.length == 1) {
var uri = new URL(release.dirpaths[0] + '\\foo_dr.txt');
GM_xmlhttpRequest({
method: 'GET',
url: uri.href,
responseType: 'blob',
onload: function(response) {
if (response.readyState != 4 || !response.responseText) return;
var rlsDesc = document.getElementById('release_lineage') || document.getElementById('release_desc');
if (rlsDesc == null) return;
var value = rlsDesc.value;
matches = value.match(/(^\[hide=DR\d*\]\[pre\])\[\/pre\]/im);
if (matches == null) return;
var index = matches.index + matches[1].length;
rlsDesc.value = value.slice(0, index).concat(response.responseText, value.slice(index));
}
});
}
}
if (element_writable(document.getElementById('image') || document.querySelector('input[name="image"]'))) {
if (/^https?:\/\/(\w+\.)?discogs\.com\/release\/[\w\-]+\/?$/i.test(url)) url += '/images';
GM_xmlhttpRequest({ method: 'GET', url: url, onload: fetch_image_from_store });
}
// } else if (element_writable(document.getElementById('image') || document.querySelector('input[name="image"]'))
// && ((ref = document.getElementById('album_desc')) != null || (ref = document.getElementById('body')) != null)
// && ref.textLength > 0 && (matches = ref.value.matchAll(/\b(https?\/\/[\w\-\&\_\?\=]+)/i)) != null) {
if (element_writable(ref = document.getElementById('release_dynamicrange'))) {
ref.value = release.drs.length == 1 ? release.drs[0] : '';
}
if (isRequest && prefs.request_default_bounty > 0) {
let amount = prefs.request_default_bounty < 1024 ? prefs.request_default_bounty : prefs.request_default_bounty / 1024;
if ((ref = document.getElementById('amount_box')) != null && !ref.disabled) ref.value = amount;
if ((ref = document.getElementById('unit')) != null && !ref.disabled) {
ref.value = prefs.request_default_bounty < 1024 ? 'mb' : 'gb';
}
exec(function() { Calculate() });
}
if (prefs.clean_on_apply) clipBoard.value = '';
prefs.save();
return true;
function gen_full_tracklist() { // ========================= TACKLIST =========================
description += isRED() ? '[pad=5|0|0|0]' : '';
description += '[size=4][color=' + prefs.tracklist_head_color + '][b]Tracklisting[/b][/color][/size]';
if (isRED()) '[/pad]';
description += '\n'; //'[hr]';
let classical_units = new Set();
if (isClassical && !tracks.some(k => k.discsubtitle)) {
for (track of tracks) {
if (matches = track.title.match(/^(.+?)\s*:\s+(.*)$/)) {
classical_units.add(track.classical_unit_title = matches[1]);
track.classical_title = matches[2];
} else {
track.classical_unit_title = null;
}
}
for (let unit of classical_units.keys()) {
let group_performer = array_homogenous(tracks.filter(k => k.classical_unit_title === unit).map(k => k.track_artist));
let group_composer = array_homogenous(tracks.filter(k => k.classical_unit_title === unit).map(k => k.composer));
for (track of tracks) {
if (track.classical_unit_title !== unit) continue;
if (group_composer) track.classical_unit_composer = track.composer;
if (group_performer) track.classical_unit_performer = track.track_artist;
}
}
}
let block = 1, lastdisc, lastsubtitle, lastside, vinyl_trackwidth;
let lastwork = classical_units.size > 0 ? null : undefined;
let volumes = new Map(tracks.map(k => [k.discnumber, undefined]));
volumes.forEach(function(val, key) {
volumes.set(key, array_homogenous(tracks.filter(k => k.discnumber == key).map(k => k.discsubtitle)));
});
if (media == 'Vinyl') {
let max_side_track = undefined;
rx = /^([A-Z])(\d+)?(\.(\d+))?/i;
for (iter of tracks) {
if (matches = iter.tracknumber.match(rx)) {
max_side_track = Math.max(parseInt(matches[2]) || 1, max_side_track || 0);
}
}
if (typeof max_side_track == 'number') {
max_side_track = max_side_track.toString().length;
vinyl_trackwidth = 1 + max_side_track;
for (iter of tracks) {
if (matches = iter.tracknumber.match(rx)) {
iter.tracknumber = matches[1].toUpperCase();
if (matches[2]) iter.tracknumber += matches[2].padStart(max_side_track, '0');
}
}
}
}
function prologue(prefix, postfix) {
function block1() {
if (block == 3) description += postfix;
description += '\n';
block = 1;
}
function block2() {
if (block == 3) description += postfix;
description += '\n';
block = 2;
}
function block3() {
if (block == 2) { description += '[hr]' } else { description += '\n' }
if (block != 3) description += prefix;
block = 3;
}
if (totalDiscs > 1 && iter.discnumber != lastdisc) {
block1();
description += '[size=3][color=' + prefs.tracklist_disctitle_color + '][b]';
if (iter.identifiers.VOL_MEDIA && tracks.filter(it => it.discnumber == iter.discnumber)
.every(it => it.identifiers.VOL_MEDIA == iter.identifiers.VOL_MEDIA)) {
description += iter.identifiers.VOL_MEDIA.toUpperCase() + ' ';
}
description += 'Disc ' + iter.discnumber;
if (iter.discsubtitle && (!volumes.has(iter.discnumber) || volumes.get(iter.discnumber))) {
description += ' - ' + iter.discsubtitle;
lastsubtitle = iter.discsubtitle;
}
description += '[/b][/color][/size]';
lastdisc = iter.discnumber;
}
if (iter.discsubtitle != lastsubtitle) {
block1();
if (iter.discsubtitle) {
description += '[size=2][color=' + prefs.tracklist_disctitle_color + '][b]' +
iter.discsubtitle + '[/b][/color][/size]';
}
lastsubtitle = iter.discsubtitle;
}
if (iter.classical_unit_title !== lastwork) {
if (iter.classical_unit_composer || iter.classical_unit_title || iter.classical_unit_performer) {
block2();
description += '[size=2][color=' + prefs.tracklist_classicalblock_color + '][b]';
if (iter.classical_unit_composer) description += iter.classical_unit_composer + ': ';
if (iter.classical_unit_title) description += iter.classical_unit_title;
description += '[/b]';
if (iter.classical_unit_performer) description += ' (' + iter.classical_unit_performer + ')';
description += '[/color][/size]';
} else {
if (block != 2) block1();
}
lastwork = iter.classical_unit_title;
}
block3();
if (media == 'Vinyl') {
var m = /^([A-Z])(\d+)$/.exec(iter.tracknumber);
if (lastside && m != null && m[1] != lastside) description += '\n';
lastside = m != null && m[1];
}
} // prologue
var varying_composer = !array_homogenous(tracks.map(k => k.composer));
for (iter of tracks.sort(function(a, b) {
var d = a.discnumber - b.discnumber;
var t = a.tracknumber - b.tracknumber;
return isNaN(d) || d == 0 ? isNaN(t) ? a.tracknumber.localeCompare(b.tracknumber) : t : d;
})) {
let title = '';
let ttwidth = vinyl_trackwidth || (totalDiscs > 1 && iter.discnumber > 0 ?
tracks.filter(it => it.discnumber == iter.discnumber) : tracks).reduce(function (accumulator, it) {
return Math.max(accumulator, iter.tracknumber.toString().length);
}, 2);
if (prefs.tracklist_style == 1) {
// STYLE 1 ----------------------------------------
prologue('[size=2]', '[/size]\n');
track = '[b][color=' + prefs.tracklist_tracknumber_color + ']';
track += isNaN(parseInt(iter.tracknumber)) ? iter.tracknumber : iter.tracknumber.padStart(ttwidth, '0');
track += '[/color][/b]' + prefs.title_separator;
if (iter.track_artist && !iter.classical_unit_performer) {
title = '[color=' + prefs.tracklist_artist_color + ']' + iter.track_artist + '[/color] - ';
}
title += iter.classical_title || iter.title;
if (iter.composer && composerEmphasis && varying_composer && !iter.classical_unit_composer) {
title = title.concat(' [color=', prefs.tracklist_composer_color, '](', iter.composer, ')[/color]');
}
description += track + title;
if (iter.duration) description += ' [i][color=' + prefs.tracklist_duration_color +'][' +
makeTimeString(iter.duration) + '][/color][/i]';
} else if (prefs.tracklist_style == 2) {
// STYLE 2 ----------------------------------------
prologue('[size=2][pre]', '[/pre][/size]');
track = isNaN(parseInt(iter.tracknumber)) ? iter.tracknumber : iter.tracknumber.padStart(ttwidth, '0');
track += prefs.title_separator;
if (iter.track_artist && !iter.classical_unit_performer) title = iter.track_artist + ' - ';
title += iter.classical_title || iter.title;
if (iter.composer && composerEmphasis && varying_composer && !iter.classical_unit_composer) {
title = title.concat(' (', iter.composer, ')');
}
dur = iter.duration ? '[' + makeTimeString(iter.duration) + ']' : null;
let l = 0, j, left, padding, spc;
let width = prefs.max_tracklist_width - track.length;
if (dur) width -= dur.length + 1;
while (title.length > 0) {
j = width;
if (title.length > width) {
while (j > 0 && title[j] != ' ') { --j }
if (j <= 0) j = width;
}
left = title.slice(0, j).trim();
if (++l <= 1) {
description += track + left;
if (dur) {
spc = width - left.length;
padding = (spc < 2 ? ' '.repeat(spc) : ' ' + prefs.pad_leader.repeat(spc - 1)) + ' ';
description += padding + dur;
}
width = prefs.max_tracklist_width - track.length - 2;
} else {
description += '\n' + ' '.repeat(track.length) + left;
}
title = title.slice(j).trim();
}
}
}
if (prefs.tracklist_style == 1 && totalTime > 0) {
description += '\n\n' + divs[0].repeat(10) + '\n[color=' + prefs.tracklist_duration_color +
']Total time: [i]' + makeTimeString(totalTime) + '[/i][/color][/size]';
} else if (prefs.tracklist_style == 2) {
if (totalTime > 0) {
dur = '[' + makeTimeString(totalTime) + ']';
description = description.concat('\n\n', divs[0].repeat(32).padStart(prefs.max_tracklist_width));
description = description.concat('\n', 'Total time:'.padEnd(prefs.max_tracklist_width - dur.length), dur);
}
description = description.concat('[/pre][/size]');
}
}
function getChanString(n) {
if (!n) return null;
const chanmap = [
'mono',
'stereo',
'2.1',
'4.0 surround sound',
'5.0 surround sound',
'5.1 surround sound',
'7.0 surround sound',
'7.1 surround sound',
];
return n >= 1 && n <= 8 ? chanmap[n - 1] : n + 'chn surround sound';
}
function init_from_url_music(url) {
if (!/^https?:\/\//i.test(url)) return false;
var artist, album, albumYear, releaseDate, channels, label, composer, bd, sr = 44.1,
description, compiler, producer, totalTracks, discSubtitle, discNumber, trackNumber,
title, trackArtist, catalogue, encoding, format, bitrate, duration, country;
if (url.toLowerCase().includes('qobuz.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
dom = domparser.parseFromString(response.responseText, "text/html");
if (dom == null) return;
if ((ref = dom.querySelector('h2.album-meta__artist')) != null) artist = ref.textContent.trim();
if ((ref = dom.querySelector('h1.album-meta__title')) != null) album = ref.textContent.trim();
ref = dom.querySelector('div.album-meta > ul > li:first-of-type');
if (ref != null) releaseDate = normalizeDate(ref.textContent);
ref = dom.querySelector('p.album-about__copyright');
albumYear = ref != null && extract_year(ref.textContent) || extract_year(releaseDate);
let genres = [];
dom.querySelectorAll('section#about > ul > li').forEach(function(k) {
if (/\b(\d+)\s*(?:dis[ck]|disco|disque)(?:s?\b|\(s\))/i.test(k.textContent)) {
totalDiscs = parseInt(RegExp.$1);
}
if (/\b(\d+)\s*(?:track|pist[ae]|tracce)(?:s?\b|\(s\))/i.test(k.textContent)) {
totalTracks = parseInt(RegExp.$1);
}
if (k.textContent.includes('Label')) label = k.children[0].textContent.trim()
else if (k.textContent.includes('Composer')) {
composer = k.children[0].textContent.trim();
if (/\bVarious\b/i.test(composer)) composer = null;
} else if (k.textContent.includes('Genre') && k.children.length > 0) {
k.querySelectorAll('a').forEach(k => { genres.push(k.textContent.trim()) });
if (genres.length > 0 && ['Pop/Rock'].includes(genres[0])) genres.shift();
if (genres.length > 0 && ['Metal', 'Heavy Metal'].some(genre => genres.includes(genre))) {
while (genres.length > 1) genres.shift();
}
}
});
bd = 16; channels = 2;
dom.querySelectorAll('span.album-quality__info').forEach(function(k) {
if (/\b([\d\.\,]+)\s*kHz\b/i.test(k.textContent) != null) sr = parseFloat(RegExp.$1.replace(/,/g, '.'));
if (/\b(\d+)[\-\s]*Bits?\b/i.test(k.textContent) != null) bd = parseInt(RegExp.$1);
if (/\b(?:Stereo)\b/i.test(k.textContent)) channels = 2;
if (/\b(\d)\.(\d)\b/.test(k.textContent)) channels = parseInt(RegExp.$1) + parseInt(RegExp.$2);
});
get_desc_from_node('section#description > p', response.finalUrl, true);
if ((ref = dom.querySelector('a[title="Qobuzissime"]')) != null) {
description += '\x1C[align=center][url=https://www.qobuz.com' + ref.pathname +
'][img]https://ptpimg.me/4z35uj.png[/img][/url][/align]';
}
ref = dom.querySelectorAll('div.player__tracks > div.track > div.track__items');
let works = dom.querySelectorAll('div.player__tracks > p.player__work');
if (!totalTracks) totalTracks = ref.length;
ref.forEach(function(k) {
discSubtitle = null;
works.forEach(function(j) {
if (j.compareDocumentPosition(k) == Node.DOCUMENT_POSITION_FOLLOWING) discSubtitle = j
});
discSubtitle = discSubtitle != null ? discSubtitle.textContent.trim() : undefined;
if (/^\s*(?:dis[ck]|disco|disque)\s+(\d+)\s*$/i.test(discSubtitle)) {
discNumber = parseInt(RegExp.$1);
discSubtitle = undefined;
} else discNumber = undefined;
if (discNumber > totalDiscs) totalDiscs = discNumber;
trackNumber = parseInt(k.querySelector('span[itemprop="position"]').textContent.trim());
title = k.querySelector('span.track__item--name').textContent.trim().replace(/\s+/g, ' ');
duration = timeStringToTime(k.querySelector('span.track__item--duration').textContent);
trackArtist = undefined;
track = [
artist,
album,
albumYear,
releaseDate,
label,
undefined, // catalogue
undefined, // country
'lossless',
'FLAC',
undefined,
undefined,
bd,
sr * 1000,
channels,
'WEB',
genres.join(', '),
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
undefined,
];
tracks.push(track.join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fill_from_text_music();
} });
return true;
} else if (url.toLowerCase().includes('highresaudio.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
dom = domparser.parseFromString(response.responseText, "text/html");
if (dom == null) return;
ref = dom.querySelector('h1 > span.artist');
if (ref != null) artist = ref.textContent.trim();
ref = dom.getElementById('h1-album-title');
if (ref != null) album = ref.firstChild.textContent.trim();
let genres = [], format;
dom.querySelectorAll('div.album-col-info-data > div > p').forEach(function(k) {
if (/\b(?:Genre|Subgenre)\b/i.test(k.firstChild.textContent)) genres.push(k.lastChild.textContent.trim());
if (/\b(?:Label)\b/i.test(k.firstChild.textContent)) label = k.lastChild.textContent.trim();
if (/\b(?:Album[\s\-]Release)\b/i.test(k.firstChild.textContent)) {
albumYear = normalizeDate(k.lastChild.textContent);
}
if (/\b(?:HRA[\s\-]Release)\b/i.test(k.firstChild.textContent)) {
releaseDate = normalizeDate(k.lastChild.textContent);
}
});
i = 0;
dom.querySelectorAll('tbody > tr > td.col-format').forEach(function(k) {
if (/^(FLAC)\s*([\d\.\,]+)\b/.exec(k.textContent) != null) {
format = RegExp.$1;
sr = parseFloat(RegExp.$2.replace(/,/g, '.'));
++i;
}
});
if (i > 1) sr = undefined; // ambiguous
get_desc_from_node('div#albumtab-info > p', response.finalUrl);
ref = dom.querySelectorAll('ul.playlist > li.pltrack');
totalTracks = ref.length;
ref.forEach(function(k) {
discSubtitle = k;
while ((discSubtitle = discSubtitle.previousElementSibling) != null) {
if (discSubtitle.nodeName == 'LI' && discSubtitle.className == 'plinfo') {
discSubtitle = discSubtitle.textContent.replace(/\s*:$/, '').trim();
if (/\b(?:DIS[CK]|Volume|CD)\s*(\d+)\b/i.exec(discSubtitle)) discNumber = parseInt(RegExp.$1);
break;
}
}
//if (discnumber > totalDiscs) totalDiscs = discnumber;
trackNumber = parseInt(k.querySelector('span.track').textContent.trim());
title = k.querySelector('span.title').textContent.trim().replace(/\s+/g, ' ');
duration = timeStringToTime(k.querySelector('span.time').textContent);
trackArtist = undefined;
track = [
artist,
album,
albumYear,
releaseDate,
label,
undefined, // catalogue
undefined, // country
'lossless',
'FLAC', //format,
undefined,
undefined,
24,
sr * 1000,
2,
'WEB',
genres.join(', '),
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
undefined,
];
tracks.push(track.join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fill_from_text_music();
} });
return true;
} else if (url.toLowerCase().includes('bandcamp.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
dom = domparser.parseFromString(response.responseText, "text/html");
if (dom == null) return;
ref = dom.querySelector('span[itemprop="byArtist"] > a');
if (ref != null) artist = ref.textContent.trim();
ref = dom.querySelector('h2[itemprop="name"]');
if (ref != null) album = ref.textContent.trim();
ref = dom.querySelector('div.tralbum-credits');
if (ref != null && /\breleased\s+(.*?\b\d{4})\b/i.test(ref.textContent)) {
releaseDate = RegExp.$1;
albumYear = releaseDate;
}
ref = dom.querySelector('p#band-name-location > span.title');
if (ref != null) label = ref.textContent.trim();
let tags = new TagManager;
dom.querySelectorAll('div.tralbum-tags > a.tag').forEach(k => { tags.add(k.textContent.trim()) });
description = [];
dom.querySelectorAll('div.tralbumData').forEach(function(k) {
if (!k.classList.contains('tralbum-tags')) description.push(html2php(k, response.finalUrl))
});
description = description.join('\n\n').replace(/\n/g, '\x1C').replace(/\r/g, '\x1D');
ref = dom.querySelectorAll('table.track_list > tbody > tr[itemprop="tracks"]');
totalTracks = ref.length;
ref.forEach(function(k) {
trackNumber = parseInt(k.querySelector('div.track_number').textContent);
title = k.querySelector('span.track-title').textContent.trim().replace(/\s+/g, ' ');
duration = timeStringToTime(k.querySelector('span.time').textContent);
trackArtist = undefined;
track = [
artist,
album,
albumYear,
releaseDate,
label,
undefined, // catalogue
undefined, // country
undefined, //'lossless',
undefined, //'FLAC',
undefined,
undefined,
undefined,
undefined,
2,
'WEB',
tags.toString(),
discNumber,
totalDiscs,
undefined,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
undefined,
];
tracks.push(track.join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fill_from_text_music();
} });
return true;
} else if (url.toLowerCase().includes('prestomusic.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
dom = domparser.parseFromString(response.responseText, "text/html");
if (dom == null) return;
artist = getArtists(dom.querySelector('div.c-product-block__contributors > p'));
ref = dom.querySelector('h1.c-product-block__title');
if (ref != null) album = ref.lastChild.textContent.trim();
dom.querySelectorAll('div.c-product-block__metadata > ul > li').forEach(function(k) {
if (k.firstChild.textContent.includes('Release Date')) {
releaseDate = extract_year(k.lastChild.textContent);
} else if (k.firstChild.textContent.includes('Label')) {
label = k.lastChild.textContent.trim();
} else if (k.firstChild.textContent.includes('Catalogue No')) {
catalogue = k.lastChild.textContent.trim();
}
});
albumYear = releaseDate;
let genre;
if (/\/jazz\//i.test(response.finalUrl)) genre = 'Jazz';
if (/\/classical\//i.test(response.finalUrl)) genre = 'Classical';
get_desc_from_node('div#about > div > p', response.finalUrl, true);
ref = dom.querySelectorAll('div#related > div > ul > li');
composer = [];
ref.forEach(function(k) {
if (k.parentNode.previousElementSibling.textContent.includes('Composers')) {
composer.push(k.firstChild.textContent.trim().replace(/^(.*?)\s*,\s+(.*)$/, '$2 $1'));
}
});
composer = composer.join(', ') || undefined;
ref = dom.querySelectorAll('div.has--sample');
totalTracks = ref.length;
let tracknumber = 0;
ref.forEach(function(k) {
tracknumber = ++tracknumber;
title = k.querySelector('p.c-track__title').textContent.trim().replace(/\s+/g, ' ');
duration = timeStringToTime(k.querySelector('div.c-track__duration').textContent);
if (k.classList.contains('c-track')) {
trackArtist = getArtists(k.parentNode.parentNode.querySelector(':scope > div.c-track__details > ul > li'));
discSubtitle = k.parentNode.parentNode.querySelector(':scope > div > div > div > p.c-track__title');
discSubtitle = discSubtitle != null ? discSubtitle.textContent.trim() : undefined;
} else {
trackArtist = getArtists(k.querySelector(':scope > div.c-track__details > ul > li'));
discSubtitle = undefined;
}
if (trackArtist == artist) trackArtist = undefined;
track = [
artist,
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
undefined, //encoding,
undefined, //format,
undefined,
undefined, //bitrate,
undefined, //bd,
undefined, //sr * 1000,
2,
'WEB',
genre,
discNumber,
totalDiscs,
discSubtitle,
tracknumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
undefined,
];
tracks.push(track.join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fill_from_text_music();
function getArtists(elem) {
if (elem == null) return undefined;
var artists = [];
elem.textContent.trim().split(multiArtistParser).forEach(function(it) {
artists.push(it.replace(/\s*\([^\(\)]*\)$/, ''));
});
return artists.join(', ');
}
} });
return true;
} else if (url.toLowerCase().includes('discogs.com/') && /\/releases?\/(\d+)\b/i.test(url)) {
GM_xmlhttpRequest({ method: 'GET', url: 'https://api.discogs.com/releases/' + RegExp.$1, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
var json = JSON.parse(response.responseText);
if (json == null) return;
const removeArtistNdx = /\s*\(\d+\)$/;
function getArtists(root) {
function filterArtists(rx, anv = true) {
return root.extraartists instanceof Array && rx instanceof RegExp ?
root.extraartists
.filter(it => rx.test(it.role))
.map(it => (anv && it.anv || it.name || '').replace(removeArtistNdx, '')) : [];
}
var artists = [];
for (var ndx = 0; ndx < 7; ++ndx) artists[ndx] = [];
ndx = 0;
if (root.artists) root.artists.forEach(function(it) {
artists[ndx].push((it.anv || it.name).replace(removeArtistNdx, ''));
if (/^feat/i.test(it.join)) ndx = 1;
});
return [
artists[0],
artists[1].concat(filterArtists(/^(?:featuring)$/i)),
artists[2].concat(filterArtists(/\b(?:Remixed[\s\-]By|Remixer)\b/i)),
artists[3].concat(filterArtists(/\b(?:(?:Written|Composed)[\s\-]By|Composer)\b/i, false)),
artists[4].concat(filterArtists(/\b(?:Conducted[\s\-]By|Conductor)\b/i)),
artists[5].concat(filterArtists(/\b(?:Compiled[\s\-]By|Compiler)\b/i)),
artists[6].concat(filterArtists(/\b(?:Produced[\s\-]By|Producer)\b/i)),
// filter off from performers
filterArtists(/\b(?:(?:Mixed)[\s\-]By|Mixer)\b/i),
filterArtists(/\b(?:(?:Written|Composed)[\s\-]By|Composer)\b/i, true),
];
}
var albumArtists = getArtists(json);
if (albumArtists[0].length > 0) {
artist = albumArtists[0].join('; ');
if (albumArtists[1].length > 0) artist += ' feat. ' + albumArtists[1].join('; ');
}
album = json.title;
var editions = [];
if (editions.length > 0) album += ' (' + editions.join(' / ') + ')';
releaseDate = json.released;
albumYear = json.year;
label = [];
catalogue = [];
json.labels.forEach(function(it) {
//if (it.entity_type_name != 'Label') return;
if (!/^Not On Label\b/i.test(it.name)) label.pushUniqueCaseless(it.name.replace(removeArtistNdx, ''));
catalogue.pushUniqueCaseless(it.catno);
});
description = '';
if (json.companies && json.companies.length > 0) {
description = '[b]Companies, etc.[/b]\n';
let type_names = new Set(json.companies.map(it => it.entity_type_name));
type_names.forEach(function(type_name) {
description += '\n' + type_name + ' – ' + json.companies
.filter(it => it.entity_type_name == type_name)
.map(function(it) {
var result = it.name.replace(removeArtistNdx, '');
if (it.catno) result += ' – ' + it.catno;
return result;
})
.join(', ');
});
}
if (json.extraartists && json.extraartists.length > 0) {
if (description) description += '\n\n';
description += '[b]Credits[/b]\n';
let roles = new Set(json.extraartists.map(it => it.role));
roles.forEach(function(role) {
description += '\n' + role + ' – ' + json.extraartists
.filter(it => it.role == role)
.map(function(it) {
var result = '[artist]' + (it.anv || it.name).replace(removeArtistNdx, '') + '[/artist]';
if (it.tracks) result += ' (tracks: ' + it.tracks + ')';
return result;
})
.join(', ');
});
}
if (json.notes) {
if (description) description += '\n\n';
description += '[b]Notes[/b]\n\n' + json.notes.trim();
}
if (json.identifiers && json.identifiers.length > 0) {
if (description) description += '\n\n';
description += '[b]Barcode and Other Identifiers[/b]\n';
json.identifiers.forEach(function(it) {
description += '\n' + it.type;
if (it.description) description += ' (' + it.description + ')';
description += ': ' + it.value;
});
}
country = json.country;
totalTracks = json.tracklist.length;
totalDiscs = json.format_quantity;
var identifiers = ['DISCOGS_ID=' + json.id];
[
['Single', 'Single'],
['EP', 'EP'],
['Compilation', 'Compilation'],
['Soundtrack', 'Soundtrack'],
].forEach(function(k) {
if (json.formats.every(it => it.descriptions && it.descriptions.includesCaseless(k[0]))) {
identifiers.push('RELEASETYPE=' + k[1]);
}
});
json.identifiers.forEach(function(it) {
identifiers.push(it.type.replace(/\W+/g, '_').toUpperCase() + '=' + it.value.replace(/\s/g, '\x1B'));
});
json.formats.forEach(function(it) {
if (it.descriptions) it.descriptions.forEach(function(it) {
if (/^(?:.+?\s+Edition|Remaster(?:ed)|Reissue|.+?\s+Release|Enhanced|Promo)$/.test(it)) {
editions.push(it);
}
});
});
json.tracklist.forEach(function(it) {
if (it.type_.toLowerCase() == 'heading') {
discSubtitle = it.title;
} else if (it.type_.toLowerCase() == 'track') {
if (/^([a-zA-Z]+)?(\d+)-(\w+)$/.test(it.position)) {
if (RegExp.$1) identifiers.push('VOL_MEDIA=' + RegExp.$1.replace(/\s/g, '\x1B'));
discNumber = RegExp.$2;
trackNumber = RegExp.$3;
} else {
discNumber = undefined;
trackNumber = it.position;
}
let trackArtists = getArtists(it);
if (trackArtists[0].length > 0 && !trackArtists[0].equalTo(albumArtists[0])
|| trackArtists[1].length > 0 && !trackArtists[1].equalTo(albumArtists[1])) {
trackArtist = (trackArtists[0].length > 0 ? trackArtists : albumArtists)[0].join('; ');
if (trackArtists[1].length > 0) trackArtist += ' feat. ' + trackArtists[1].join('; ');
} else {
trackArtist = null;
}
title = it.title;
duration = timeStringToTime(it.duration);
let performer = it.extraartists instanceof Array && it.extraartists
.map(it => (it.anv || it.name).replace(removeArtistNdx, ''))
.filter(function(artist) {
return !albumArtists.slice(2).some(it => it instanceof Array && it.includes(artist))
&& !trackArtists.slice(2).some(it => it instanceof Array && it.includes(artist))
});
track = [
artist,
album,
albumYear,
releaseDate,
label.join(' / '),
catalogue.join(' / '),
country,
undefined, // encoding
undefined, // format
undefined,
undefined,
undefined, // bit depth
undefined, // samplerate
undefined, // channels
json.formats.map(it => it.name).join(', '),
(json.genres ? json.genres.join(', ') : '') + (json.styles ? '|' + json.styles.join(', ') : ''),
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
performer instanceof Array && performer.join('; ') || undefined,
stringyfyRole(3), // composers
stringyfyRole(4), // conductors
stringyfyRole(2), // remixers
stringyfyRole(5), // DJs/compilers
stringyfyRole(6), // producers
duration,
undefined,
undefined,
undefined,
undefined,
undefined, // URL
undefined,
description.replace(/\n/g, '\x1C').replace(/\r/g, '\x1D'),
identifiers.join(' '),
];
tracks.push(track.join('\x1E'));
function stringyfyRole(ndx) {
return (trackArtists[ndx] instanceof Array && trackArtists[ndx].length > 0 ?
trackArtists : albumArtists)[ndx].join('; ');
}
}
});
clipBoard.value = tracks.join('\n');
fill_from_text_music();
} });
return true;
} else if (url.toLowerCase().includes('supraphonline.cz')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
dom = domparser.parseFromString(response.responseText, "text/html");
if (dom == null) return;
var genre;
artist = [];
dom.querySelectorAll('h2.album-artist > a').forEach(function(it) {
artist.pushUnique(it.title.trim());
});
if ((ref = dom.querySelector('h1[itemprop="name"]')) != null) album = ref.firstChild.data.trim();
if ((ref = dom.querySelector('meta[itemprop="numTracks"]')) != null) totalTracks = parseInt(ref.content);
if ((ref = dom.querySelector('meta[itemprop="genre"]')) != null) genre = ref.content;
if ((ref = dom.querySelector('li.album-version > div.selected > div')) != null) {
if (/\b(?:CD)\b/.test(ref.textContent)) { media = 'CD'; }
if (/\b(?:LP)\b/.test(ref.textContent)) { media = 'Vinyl'; }
if (/\b(?:MP3)\b/.test(ref.textContent)) { media = 'WEB'; encoding = 'lossy'; format = 'MP3'; }
if (/\b(?:FLAC)\b/.test(ref.textContent)) { media = 'WEB'; encoding = 'lossless'; format = 'FLAC'; bd = 16; }
if (/\b(?:Hi[\s\-]*Res)\b/.test(ref.textContent)) { media = 'WEB'; encoding = 'lossless'; format = 'FLAC'; bd = 24; }
}
dom.querySelectorAll('ul.summary > li').forEach(function(it) {
if (it.children.length < 1) return;
if (it.children[0].textContent.includes('Nosič')) media = it.lastChild.textContent.trim();
if (it.children[0].textContent.includes('Datum vydání')) releaseDate = normalizeDate(it.lastChild.textContent);
//if (it.children[0].textContent.includes('Žánr')) genre = it.lastChild.textContent.trim();
if (it.children[0].textContent.includes('Vydavatel')) label = it.lastChild.textContent.trim();
if (it.children[0].textContent.includes('Katalogové číslo')) catalogue = it.lastChild.textContent.trim();
if (it.children[0].textContent.includes('Formát')) {
if (/\b(?:FLAC|WAV|AIFF?)\b/.test(it.lastChild.textContent)) { encoding = 'lossless'; format = 'FLAC'; }
if (/\b(\d+)[\-\s]?bits?\b/i.test(it.lastChild.textContent)) bd = parseInt(RegExp.$1);
if (/\b([\d\.\,]+)[\-\s]?kHz\b/.test(it.lastChild.textContent)) sr = parseFloat(RegExp.$1.replace(',', '.'));
}
if (it.children[0].textContent.includes('Celková stopáž')) totalTime = timeStringToTime(it.lastChild.textContent.trim());
if (/^(?:\([PC]\)|℗|©)$/i.test(it.children[0].textContent)) albumYear = extract_year(it.lastChild.data);
});
[
[/^(?:Orchestrální\s+hudba)$/i, 'Orchestral Music'],
[/^(?:Komorní\s+hudba)$/i, 'Chamber Music'],
[/^(?:Vokální)$/i, 'Classical, Vocal'],
[/^(?:Klasická\s+hudba)$/i, 'Classical'],
[/^(?:Melodram)$/i, 'Classical, Melodram'],
[/^(?:Symfonie)$/i, 'Symphony'],
[/^(?:Vánoční\s+hudba)$/i, 'Christmas Music'],
[/^(?:Alternativní)$/i, 'Alternative'],
[/^(?:Dechová\s+hudba)$/i, 'Brass Music'],
[/^(?:Elektronika)$/i, 'Electronic'],
[/^(?:Folklor)$/i, 'Folclore, World Music'],
[/^(?:Instrumentální\s+hudba)$/i, 'Instrumental'],
[/^(?:Latinské\s+rytmy)$/i, 'Latin'],
[/^(?:Meditační\s+hudba)$/i, 'Meditative'],
[/^(?:Pro\s+děti)$/i, 'Children'],
].forEach(it => { if (it[0].test(genre)) genre = it[1] });
const creators = [
'autoři',
'interpreti',
'tělesa',
'digitalizace',
];
var cat, performer = [], engineers = {};
composer = [];
dom.querySelectorAll('ul.sidebar-artist > li').forEach(function(it) {
if ((ref = it.querySelector('h3')) != null) {
cat = undefined;
creators.forEach((it, ndx) => { if (ref.textContent.includes(it)) cat = ndx + 1 });
} else {
let key = it.children[0].textContent.replace(/\s*:.*$/, '').toLowerCase();
if (cat == 1) composer.pushUnique(it.lastChild.textContent.trim());
if (cat == 2 || cat == 3) performer.pushUnique(it.lastChild.textContent.trim());
if (cat == 4) {
if (!(engineers[key] instanceof Array)) engineers[key] = [];
engineers[key].pushUnique(it.lastChild.textContent.trim());
}
}
});
get_desc_from_node('div[itemprop="description"] p', response.finalUrl, true);
for (iter in engineers) {
if (description.length > 0) description += '\x1C' ;
description += iter + ' – ' + engineers[iter].join(', ');
}
dom.querySelectorAll('table.table-tracklist > tbody > tr').forEach(function(it) {
if (it.classList.contains('cd-header')) {
discNumber = /\b\d+\b/.test(it.querySelector('h3').firstChild.data.trim())
&& parseInt(RegExp.lastMatch) || undefined;
}
if (it.classList.contains('song-header')) {
discSubtitle = it.children[0].title.trim() || undefined;
}
if (it.classList.contains('track') && it.id) {
if (/^\s*(\d+)\.?\s*$/.test(it.children[0].firstChild.textContent)) {
trackNumber = parseInt(RegExp.$1);
}
ref = it.querySelectorAll('meta[itemprop="name"]');
if (ref.length > 0) title = ref[0].content;
if (/^PT(\d+)H(\d+)M(\d+)S$/i.test(it.querySelector('meta[itemprop="duration"]').content)) {
duration = parseInt(RegExp.$1 || 0) * 60**2 + parseInt(RegExp.$2 || 0) * 60 + parseInt(RegExp.$3 || 0);
}
track = [
artist.join('; '),
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
encoding,
format,
undefined,
undefined,
bd,
sr * 1000,
2,
media,
genre,
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
undefined, // trackArtist
performer.join('; '),
composer.join('; '),
undefined,
undefined,
undefined, // compiler
undefined, // producer
duration,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
/^track-(\d+)$/i.test(it.id) ? 'TRACK_ID=' + RegExp.$1 : undefined,
];
tracks.push(track.join('\x1E'));
}
});
clipBoard.value = tracks.join('\n');
fill_from_text_music();
} });
return true;
} else if (url.toLowerCase().includes('bontonland.cz')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
dom = domparser.parseFromString(response.responseText, "text/html");
if (dom == null) return;
ref = dom.querySelector('div#detailheader > h1');
if (ref != null && /^(.*?)\s*:\s*(.*)$/.test(ref.textContent.trim())) {
artist = RegExp.$1;
album = RegExp.$2;
}
var EAN;
dom.querySelectorAll('table > tbody > tr > td.nazevparametru').forEach(function(it) {
if (it.textContent.includes('Datum vydání')) {
releaseDate = normalizeDate(it.nextElementSibling.textContent);
albumYear = extract_year(it.nextElementSibling.textContent);
} else if (it.textContent.includes('Nosič / počet')) {
if (/^(.*?)\s*\/\s*(.*)$/.test(it.nextElementSibling.textContent)) {
media = RegExp.$1;
totalDiscs = RegExp.$2;
}
} else if (it.textContent.includes('Interpret')) {
artist = it.nextElementSibling.textContent.trim();
} else if (it.textContent.includes('EAN')) {
EAN = 'BARCODE=' + it.nextElementSibling.textContent.trim();
}
});
get_desc_from_node('div#detailtabpopis > div[class^="pravy"] > div > p:not(:last-of-type)', response.finalUrl, true);
const plParser = /^(\d+)(?:\s*[\/\.\-\:\)])?\s+(.*?)(?:\s+((?:(?:\d+:)?\d+:)?\d+))?$/;
ref = dom.querySelector('div#detailtabpopis > div[class^="pravy"] > div > p:last-of-type');
if (ref == null) throw new Error('Playlist not located');
var trackList = html2php(ref).split(/[\r\n]+/);
trackList = trackList.filter(it => plParser.test(it.trim())).map(it => plParser.exec(it.trim()));
totalTracks = trackList.length;
if (!totalTracks) throw new Error('Playlist empty');
trackList.forEach(function(it) {
trackNumber = it[1];
title = it[2];
duration = timeStringToTime(it[3]);
trackArtist = undefined;
track = [
artist,
album,
albumYear,
releaseDate,
label,
undefined, // catalogue
undefined, // country
undefined, // encoding
undefined, // format
undefined,
undefined,
undefined,
undefined,
undefined,
'CD', // media
undefined, // genre
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
undefined, // composer
undefined,
undefined,
undefined, // compiler
undefined, // producer
duration,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
EAN,
];
tracks.push(track.join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fill_from_text_music();
} });
return true;
}
addMessage('This domain not supported', 'ua-critical');
return false;
function get_desc_from_node(selector, url, quote = false) {
description = [];
dom.querySelectorAll(selector).forEach(k => { description.push(html2php(k, url).trim()) });
description = description.join('\n\n').trim().replace(/\n/g, '\x1C').replace(/\r/g, '\x1D');
if (quote && description.length > 0) description = '[quote]' + description + '[/quote]';
}
} // init_from_url_music
function normalizeDate(str) {
if (typeof str != 'string') return null;
return /\b(d{4}-\d+-\d+|\d{1,2}\/\d{1,2}\/\d{2})\b/.test(str) ? RegExp.$1 :
/\b(\d{1,2})\/(\d{1,2})\/(\d{4})\b/.test(str) ? RegExp.$2 + '/' + RegExp.$1 + '/' + RegExp.$3 :
/\b(\d{1,2})\.\s?(\d{1,2})\.\s?(\d{2}|\d{4})\b/.test(str) ? RegExp.$2 + '/' + RegExp.$1 + '/' + RegExp.$3 :
extract_year(str);
}
function fetch_image_from_store(response) {
if (response.readyState != 4 || !response.responseText) return;
dom = domparser.parseFromString(response.responseText, "text/html");
if (dom == null) return;
function testDomain(str) {
return typeof str == 'string' && response.finalUrl.toLowerCase().includes(str.toLowerCase());
}
if (testDomain('qobuz.com') && (ref = dom.querySelector('div.album-cover > img')) != null) {
setImage(ref.src);
} else if (testDomain('highresaudio.com') && (ref = dom.querySelector('div.albumbody > img.cover[data-pin-media]')) != null) {
setImage(ref.dataset.pinMedia);
} else if (testDomain('bandcamp.com') && (ref = dom.querySelector('div#tralbumArt > a.popupImage')) != null) {
setImage(ref.href);
} else if (testDomain('7digital.com') && (ref = dom.querySelector('span.release-packshot-image > img[itemprop="image"]')) != null) {
setImage(ref.src);
} else if (testDomain('hdtracks.com') && (ref = dom.querySelector('p.product-image > img')) != null) {
setImage(ref.src);
} else if (testDomain('discogs.com') && (ref = dom.querySelector('div#view_images > p:first-of-type > span > img')) != null) {
setImage(ref.src);
} else if (testDomain('junodownload.com') && (ref = dom.querySelector('a.productimage')) != null) {
setImage(ref.href);
} else if (testDomain('supraphonline.cz') && (ref = dom.querySelector('meta[itemprop="image"]')) != null) {
setImage(ref.content.replace(/\?.*$/, ''));
} else if (testDomain('prestomusic.com') && (ref = dom.querySelector('div.c-product-block__aside > a')) != null) {
setImage(ref.href.replace(/\?\d+$/, ''));
} else if (testDomain('bontonland.cz') && (ref = dom.querySelector('a.detailzoom')) != null) {
setImage(ref.href);
}
}
function reqSelectFormats(...vals) {
vals.forEach(function(val) {
[
['MP3', 0],
['FLAC', 1],
['AAC', 2],
['AC3', 3],
['DTS', 4],
].forEach(function(fmt) {
if (val == fmt[0] && (ref = document.getElementById('format_' + fmt[1])) != null) {
ref.checked = true;
ref.onchange();
}
});
});
}
function reqSelectBitrates(...vals) {
vals.forEach(function(val) {
var ndx = 10;
[
[192, 0],
['APS (VBR)', 1],
['V2 (VBR)', 2],
['V1 (VBR)', 3],
[256, 4],
['APX (VBR)', 5],
['V0 (VBR)', 6],
[320, 7],
['Lossless', 8],
['24bit Lossless', 9],
['Other', 10],
].forEach(k => { if (val == k[0]) ndx = k[1] });
if ((ref = document.getElementById('bitrate_' + ndx)) != null) {
ref.checked = true;
ref.onchange();
}
});
}
function reqSelectMedias(...vals) {
vals.forEach(function(val) {
[
['CD', 0],
['DVD', 1],
['Vinyl', 2],
['Soundboard', 3],
['SACD', 4],
['DAT', 5],
['Cassette', 6],
['WEB', 7],
['Blu-Ray', 8],
].forEach(function(med) {
if (val == med[0] && (ref = document.getElementById('media_' + med[1])) != null) {
ref.checked = true;
ref.onchange();
}
});
if (val == 'CD') {
if ((ref = document.getElementById('needlog')) != null) {
ref.checked = true;
ref.onchange();
if ((ref = document.getElementById('minlogscore')) != null) ref.value = 100;
}
if ((ref = document.getElementById('needcue')) != null) ref.checked = true;
//if ((ref = document.getElementById('needchecksum')) != null) ref.checked = true;
}
});
}
function getReleaseIndex(str) {
var ndx;
[
['Album', 1],
['Soundtrack', 3],
['EP', 5],
['Anthology', 6],
['Compilation', 7],
['Single', 9],
['Live album', 11],
['Remix', 13],
['Bootleg', 14],
['Interview', 15],
['Mixtape', 16],
['Demo', 17],
['Concert Recording', 18],
['DJ Mix', 19],
['Unknown', 21],
].forEach(k => { if (str.toLowerCase() == k[0].toLowerCase()) ndx = k[1] });
return ndx || 21;
}
function joinArtists(arr, prefix, postfix) {
if (!(arr instanceof Array)) return null;
if (arr.find(it => it.includes('&')) != undefined) return arr.map(decorator).join(', ');
if (arr.length < 3) return arr.map(decorator).join(' & ');
var foo = arr.slice(-1).map(decorator);
foo.unshift(arr.slice(0, -1).map(decorator).join(', '));
return foo.join(' & ');
function decorator(it) {
var result = it;
if (prefix) result = prefix.concat(result);
if (postfix) result += postfix;
return result;
}
}
} // fill_from_text_music
function fill_from_text_apps() {
if (messages != null) messages.parentNode.removeChild(messages);
if (!urlParser.test(clipBoard.value)) {
addMessage('Only URL accepted for this category', 'ua-critical');
return false;
}
url = RegExp.$1;
var description, tags = new TagManager();
if (url.toLowerCase().includes('//sanet')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
dom = domparser.parseFromString(response.responseText, "text/html");
i = dom.querySelector('h1.item_title > span');
if (element_writable(ref = document.getElementById('title'))) {
ref.value = i != null ? i.textContent.
replace(/\(x64\)$/i, '(64-bit)').
replace(/\b(?:Build)\s+(\d+)/, 'build $1').
replace(/\b(?:Multilingual|Multilanguage)\b/, 'multilingual') : null;
}
description = html2php(dom.querySelector('section.descr'), response.finalUrl);
if (/\s*^(?:\[i\]\[\/i\])?Homepage$.*/m.test(description)) description = RegExp.leftContext;
description = description.trim().split(/\n/).slice(5).map(k => k.trimRight()).join('\n').trim();
ref = dom.querySelector('section.descr > div.release-info');
var releaseInfo = ref != null && ref.textContent.trim();
if (/\b(?:Languages?)\s*:\s*(.*?)\s*(?:$|\|)/i.exec(releaseInfo) != null) {
description += '\n\n[b]Languages:[/b]\n' + RegExp.$1;
}
ref = dom.querySelector('div.txtleft > a');
if (ref != null) description += '\n\n[b]Product page:[/b]\n[url]' + de_anonymize(ref.href) + '[/url]';
write_description(description);
if ((ref = dom.querySelector('section.descr > div.center > a.mfp-image')) != null) {
setImage(ref.href);
} else {
ref = dom.querySelector('section.descr > div.center > img[data-src]');
if (ref != null) setImage(ref.dataset.src);
}
var cat = dom.querySelector('a.cat:last-of-type > span');
if (cat != null) {
if (cat.textContent.toLowerCase() == 'windows') {
tags.add('apps.windows');
if (/\b(?:x64)\b/i.test(releaseInfo)) tags.add('win64');
if (/\b(?:x86)\b/i.test(releaseInfo)) tags.add('win32');
}
if (cat.textContent.toLowerCase() == 'macos') tags.add('apps.mac');
if (cat.textContent.toLowerCase() == 'linux' || cat.textContent.toLowerCase() == 'unix') tags.add('apps.linux');
if (cat.textContent.toLowerCase() == 'android') tags.add('apps.android');
if (cat.textContent.toLowerCase() == 'ios') tags.add('apps.ios');
}
if (tags.length > 0 && element_writable(ref = document.getElementById('tags'))) {
ref.value = tags.toString();
}
}, });
return true;
}
addMessage('This domain not supported', 'ua-critical');
return false;
}
function fill_from_text_books() {
if (messages != null) messages.parentNode.removeChild(messages);
if (!urlParser.test(clipBoard.value)) {
addMessage('Only URL accepted for this category', 'ua-critical');
return false;
}
url = RegExp.$1;
var description, tags = new TagManager();
if (url.toLowerCase().includes('martinus.cz') || url.toLowerCase().includes('martinus.sk')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
dom = domparser.parseFromString(response.responseText, "text/html");
function get_detail(x, y) {
var ref = dom.querySelector('section#details > div > div > div:first-of-type > div:nth-child(' +
x + ') > dl:nth-child(' + y + ') > dd');
return ref != null ? ref.textContent.trim() : null;
}
i = dom.querySelectorAll('article > ul > li > a');
if (i.length > 0 && element_writable(ref = document.getElementById('title'))) {
description = joinAuthors(i);
if ((i = dom.querySelector('article > h1')) != null) description += ' - ' + i.textContent.trim();
i = dom.querySelector('div.bar.mb-medium > div:nth-child(1) > dl > dd > span');
if (i != null && (i = extract_year(i.textContent))) description += ' (' + i + ')';
ref.value = description;
}
description = '[quote]' + html2php(dom.querySelector('section#description > div')).
replace(/^\s*\[img\].*?\[\/img\]\s*/i, '') + '[/quote]';
const translation_map = [
[/\b(?:originál)/i, 'Original title'],
[/\b(?:datum|dátum|rok)\b/i, 'Release date'],
[/\b(?:katalog|katalóg)/i, 'Catalogue #'],
[/\b(?:stran|strán)\b/i, 'Page count'],
[/\bjazyk/i, 'Language'],
[/\b(?:nakladatel|vydavatel)/i, 'Publisher'],
[/\b(?:doporuč|ODPORÚČ)/i, 'Age rating'],
];
dom.querySelectorAll('section#details > div > div > div:first-of-type > div > dl').forEach(function(detail) {
var lbl = detail.children[0].textContent.trim();
var val = detail.children[1].textContent.trim();
if (/\b(?:rozm)/i.test(lbl) || /\b(?:vazba|vázba)\b/i.test(lbl)) return;
translation_map.forEach(k => { if (k[0].test(lbl)) lbl = k[1] });
if (/\b(?:ISBN)\b/i.test(lbl)) {
val = '[url=https://www.worldcat.org/isbn/' + detail.children[1].textContent.trim() +
']' + detail.children[1].textContent.trim() + '[/url]';
// } else if (/\b(?:ISBN)\b/i.test(lbl)) {
// val = '[url=https://www.goodreads.com/search/search?q=' + detail.children[1].textContent.trim() +
// '&search_type=books]' + detail.children[1].textContent.trim() + '[/url]';
}
description += '\n[b]' + lbl + ':[/b] ' + val;
});
description += '\n[b]More info:[/b] ' + response.finalUrl;
write_description(description);
if ((i = dom.querySelector('a.mj-product-preview > img')) != null) {
setImage(i.src.replace(/\?.*/, ''));
} else if ((i = dom.querySelector('head > meta[property="og:image"]')) != null) {
setImage(i.content.replace(/\?.*/, ''));
}
dom.querySelectorAll('dd > ul > li > a').forEach(x => { tags.add(x.textContent) });
if (tags.length > 0 && element_writable(ref = document.getElementById('tags'))) {
ref.value = tags.toString();
}
}, });
return true;
} else if (url.toLowerCase().includes('goodreads.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
dom = domparser.parseFromString(response.responseText, "text/html");
i = dom.querySelectorAll('a.authorName > span');
if (i.length > 0 && element_writable(ref = document.getElementById('title'))) {
description = joinAuthors(i);
if ((i = dom.querySelector('h1#bookTitle')) != null) description += ' - ' + i.textContent.trim();
if ((i = dom.querySelector('div#details > div.row:nth-of-type(2)')) != null
&& (i = extract_year(i.textContent))) description += ' (' + i + ')';
ref.value = description;
}
description = '[quote]' + html2php(dom.querySelector('div#description > span:last-of-type'), response.finalUrl) + '[/quote]';
function strip(str) {
return typeof str == 'string' ?
str.replace(/\s{2,}/g, ' ').replace(/[\n\r]+/, '').replace(/\s*\.{3}(?:less|more)\b/g, '').trim() : null;
}
dom.querySelectorAll('div#details > div.row').forEach(k => { description += '\n' + strip(k.innerText) });
description += '\n';
dom.querySelectorAll('div#bookDataBox > div.clearFloats').forEach(function(detail) {
var lbl = detail.children[0].textContent.trim();
var val = strip(detail.children[1].textContent);
if (/\b(?:ISBN)\b/i.test(lbl) && ((matches = val.match(/\b(\d{13})\b/)) != null
|| (matches = val.match(/\b(\d{10})\b/)) != null)) {
val = '[url=https://www.worldcat.org/isbn/' + matches[1] + ']' + strip(detail.children[1].textContent) + '[/url]';
}
description += '\n[b]' + lbl + ':[/b] ' + val;
});
description += '\n[b]More info:[/b] ' + response.finalUrl;
write_description(description);
if ((i = dom.querySelector('div.editionCover > img')) != null) {
setImage(i.src.replace(/\?.*/, ''));
}
dom.querySelectorAll('div.elementList > div.left').forEach(x => { tags.add(x.textContent.trim()) });
if (tags.length > 0 && element_writable(ref = document.getElementById('tags'))) {
ref.value = tags.toString();
}
}, });
return true;
} else if (url.toLowerCase().includes('databazeknih.cz')) {
if (!url.toLowerCase().includes('show=alldesc')) {
if (!url.includes('?')) { url += '?show=alldesc' } else { url += '&show=alldesc' }
}
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
dom = domparser.parseFromString(response.responseText, "text/html");
i = dom.querySelectorAll('span[itemprop="author"] > a');
if (i != null && element_writable(ref = document.getElementById('title'))) {
description = joinAuthors(i);
if ((i = dom.querySelector('h1[itemprop="name"]')) != null) description += ' - ' + i.textContent.trim();
i = dom.querySelector('span[itemprop="datePublished"]');
if (i != null && (i = extract_year(i.textContent))) description += ' (' + i + ')';
ref.value = description;
}
description = '[quote]' + html2php(dom.querySelector('p[itemprop="description"]'), response.finalUrl) + '[/quote]';
const translation_map = [
[/\b(?:orig)/i, 'Original title'],
[/\b(?:série)\b/i, 'Series'],
[/\b(?:vydáno)\b/i, 'Released'],
[/\b(?:stran)\b/i, 'Page count'],
[/\b(?:jazyk)\b/i, 'Language'],
[/\b(?:překlad)/i, 'Translation'],
[/\b(?:autor obálky)\b/i, 'Cover author'],
];
dom.querySelectorAll('table.bdetail tr').forEach(function(detail) {
var lbl = detail.children[0].textContent.trim();
var val = detail.children[1].textContent.trim();
if (/(?:žánr|\bvazba)\b/i.test(lbl)) return;
translation_map.forEach(k => { if (k[0].test(lbl)) lbl = k[1] });
if (/\b(?:ISBN)\b/i.test(lbl) && /\b(\d+(?:-\d+)*)\b/.exec(val) != null) {
val = '[url=https://www.worldcat.org/isbn/' + RegExp.$1.replace(/-/g, '') +
']' + detail.children[1].textContent.trim() + '[/url]';
}
description += '\n[b]' + lbl + '[/b] ' + val;
});
description += '\n[b]More info:[/b] ' + response.finalUrl.replace(/\?.*/, '');
write_description(description);
if ((i = dom.querySelector('div#icover_mid > a')) != null) setImage(i.href.replace(/\?.*/, ''));
if ((i = dom.querySelector('div#lbImage')) != null
&& (matches = i.style.backgroundImage.match(/\burl\("(.*)"\)/i)) != null) {
setImage(matches[1].replace(/\?.*/, ''));
}
dom.querySelectorAll('h5[itemprop="genre"] > a').forEach(x => { tags.add(x.textContent.trim()) });
dom.querySelectorAll('a.tag').forEach(x => { tags.add(x.textContent.trim()) });
if (tags.length > 0 && element_writable(ref = document.getElementById('tags'))) {
ref.value = tags.toString();
}
}, });
return true;
}
addMessage('This domain not supported', 'ua-critical');
return false;
function joinAuthors(nodeList) {
if (typeof nodeList != 'object') return null;
var authors = [];
nodeList.forEach(k => { authors.push(k.textContent.trim()) });
return authors.join(' & ');
}
}
function preview(n) {
if (!prefs.auto_preview) return;
var btn = document.querySelector('input.button_preview_' + n + '[type="button"][value="Preview"]');
if (btn != null) btn.click();
}
function html2php(node, url) {
var php = '';
if (node instanceof HTMLElement) node.childNodes.forEach(function(ch) {
if (ch.nodeType == 3) {
php += ch.data.replace(/\s+/g, ' ');
} else if (ch.nodeName == 'P') {
php += '\n' + html2php(ch, url);
} else if (ch.nodeName == 'DIV') {
php += '\n\n' + html2php(ch, url) + '\n\n';
} else if (ch.nodeName == 'LABEL') {
php += '\n\n[b]' + html2php(ch, url) + '[/b]';
} else if (ch.nodeName == 'SPAN') {
php += html2php(ch, url);
} else if (ch.nodeName == 'BR' || ch.nodeName == 'HR') {
php += '\n';
} else if (ch.nodeName == 'B' || ch.nodeName == 'STRONG') {
php += '[b]' + html2php(ch, url) + '[/b]';
} else if (ch.nodeName == 'I' || ch.nodeName == 'EM') {
php += '[i]' + html2php(ch, url) + '[/i]';
} else if (ch.nodeName == 'U') {
php += '[u]' + html2php(ch, url) + '[/u]';
} else if (ch.nodeName == 'CODE') {
php += '[pre]' + ch.textContent + '[/pre]';
} else if (ch.nodeName == 'A') {
php += ch.childNodes.length > 0 ?
'[url=' + de_anonymize(ch.href) + ']' + html2php(ch, url) + '[/url]' :
'[url]' + de_anonymize(ch.href) + '[/url]';
} else if (ch.nodeName == 'IMG') {
php += '[img]' + (ch.dataset.src || ch.src) + '[/img]';
}
});
return php;
}
function de_anonymize(uri) {
return typeof uri == 'string' ? uri.replace(/^https?:\/\/(?:www\.)?anonymz\.com\/\?/i, '') : null;
}
function write_description(desc) {
if (typeof desc != 'string') return;
if (element_writable(ref = document.getElementById('desc'))) ref.value = desc;
if ((ref = document.getElementById('body')) != null && !ref.disabled) {
if (ref.textLength > 0) ref.value += '\n\n';
ref.value += desc;
}
}
function setImage(url) {
var image = document.getElementById('image') || document.querySelector('input[name="image"]');
if (!element_writable(image)) return false;
image.value = url;
if (prefs.auto_preview_cover) {
if ((child = document.getElementById('cover preview')) == null) {
elem = document.createElement('div');
elem.style.paddingTop = '10px';
child = document.createElement('img');
child.id = 'cover preview';
child.style.width = '90%';
elem.append(child);
image.parentNode.previousElementSibling.append(elem);
}
child.src = url;
}
// Re-Host to PTPIMG
if (prefs.auto_rehost_cover) {
var rehost_btn = document.querySelector('input.rehost_it_cover[type="button"]');
if (rehost_btn != null) {
rehost_btn.click();
} else {
var pr = rehostImgs([url]);
if (pr != null) pr.then(new_urls => { image.value = new_urls[0] });
}
}
}
// PTPIMG rehoster taken from `PTH PTPImg It`
function rehostImgs(urls) {
if (!Array.isArray(urls)) return null;
var config = JSON.parse(window.localStorage.ptpimg_it);
return config.api_key ? new Promise(ptpimg_upload_urls).catch(m => { alert(m) }) : null;
function ptpimg_upload_urls(resolve, reject) {
const boundary = 'NN-GGn-PTPIMG';
var data = '--' + boundary + "\n";
data += 'Content-Disposition: form-data; name="link-upload"\n\n';
data += urls.map(function(url) {
return !url.toLowerCase().includes('://reho.st/') && url.toLowerCase().includes('discogs.com') ?
'https://reho.st/' + url : url;
}).join('\n') + '\n';
data += '--' + boundary + '\n';
data += 'Content-Disposition: form-data; name="api_key"\n\n';
data += config.api_key + '\n';
data += '--' + boundary + '--';
GM_xmlhttpRequest({
method: 'POST',
url: 'https://ptpimg.me/upload.php',
responseType: 'json',
headers: {
'Content-type': 'multipart/form-data; boundary=' + boundary,
},
data: data,
onload: response => {
if (response.status != 200) reject('Response error ' + response.status);
resolve(response.response.map(item => 'https://ptpimg.me/' + item.code + '.' + item.ext));
},
});
}
}
function element_writable(elem) { return elem != null && !elem.disabled && (overwrite || !elem.value) }
}
function add_artist() { exec(function() { AddArtistField() }) }
function array_homogenous(arr) { return arr.every(k => k === arr[0]) }
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 makeTimeString(duration) {
let t = Math.abs(Math.round(duration));
let H = Math.floor(t / 60 ** 2);
let M = Math.floor(t / 60 % 60);
let S = t % 60;
return (duration < 0 ? '-' : '') + (H > 0 ? H + ':' + M.toString().padStart(2, '0') : M.toString()) +
':' + S.toString().padStart(2, '0');
}
function timeStringToTime(str) {
if (!/(-\s*)?\b(\d+(?::\d{2})*(?:\.\d+)?)\b/.test(str)) return null;
var t = 0, a = RegExp.$2.split(':');
while (a.length > 0) t = t * 60 + parseFloat(a.shift());
return RegExp.$1 ? -t : t;
}
function extract_year(expr) {
if (typeof expr != 'string') return null;
if (/\b(\d{4})\b/.test(expr)) return parseInt(RegExp.$1);
var d = new Date(expr);
return parseInt(isNaN(d) ? expr : d.getFullYear());
}
function isRED() { return document.domain.toLowerCase().endsWith('redacted.ch') }
function isNWCD() { return document.domain.toLowerCase().endsWith('notwhat.cd') }
function isOrpheus() { return document.domain.toLowerCase().endsWith('orpheus.network') }
function reInParenthesis(expr) { return new RegExp('\\s+\\([^\\(\\)]*'.concat(expr, '[^\\(\\)]*\\)$'), 'i') }
function reInBrackets(expr) { return new RegExp('\\s+\\[[^\\[\\]]*'.concat(expr, '[^\\[\\]]*\\]$'), 'i') }
function addMessage(text, cls, html = false) {
messages = document.getElementById('UA messages');
if (messages == null) {
var ua = document.getElementById('upload assistant');
if (ua == null) return null;
messages = document.createElement('TR');
if (messages == null) return null;
messages.id = 'UA messages';
ua.children[0].append(messages);
elem = document.createElement('TD');
if (elem == null) return null;
elem.colSpan = 2;
elem.className = 'ua-messages-bg';
messages.append(elem);
} else {
elem = messages.children[0]; // tbody
if (elem == null) return null;
}
var div = document.createElement('DIV');
div.classList.add('ua-messages', cls);
div[html ? 'innerHTML' : 'textContent'] = text;
return elem.appendChild(div);
}