// ==UserScript==
// @name [RED/NWCD] Upload Assistant
// @namespace https://gf.qytechs.cn/users/321857-anakunda
// @version 1.193
// @description Accurate filling of new upload/request and group/request edit forms based on foobar2000's playlist selection (via pasted output of copy command), release integrity check, two tracklist layouts, colours customization, featured artists extraction, classical works formatting, coverart fetching from store, checking for previous upload and more. As alternative to pasted playlist, e.g. for requests creation, valid URL to product page on supported web can be used -- see below.
// @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/torrents.php?action=edit&*
// @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/torrents.php?action=edit&*
// @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/torrents.php?action=edit&*
// @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
// //@require https://connect.soundcloud.com/sdk/sdk-3.3.2.js
// @require https://gf.qytechs.cn/scripts/393837-qobuzlib/code/QobuzLib.js
// @require https://gf.qytechs.cn/scripts/394414-ua-resource/code/UA-resource.js
// ==/UserScript==
// Additional setup: to work, set the pattern below as built-in foobar2000 copy command or custom Text Tools plugin quick copy command
// [$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 #%,%SKU%,%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)[%filesize%]$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% ][ORIGINALFORMAT=%ORIGINALFORMAT% ][ASIN=%ASIN% ][DISCOGS_ID=%discogs_release_id% ][SOURCEID=%SOURCEID% ][BPM=%BPM% ])
//
// List of supported domains for online capturing of release details:
//
// Music releases:
// - qobuz.com
// - highresaudio.com
// - bandcamp.com
// - prestomusic.com
// - discogs.com
// - supraphonline.cz
// - bontonland.cz (closing soon)
// - nativedsd.com
// - junodownload.com
// - hdtracks.com
// - deezer.com
// - spotify.com
// - prostudiomasters.com
// - play.google.com
// - 7digital.com
// - e-onkyo.com
// - acousticsounds.com
//
// Ebooks releases:
// - martinus.cz, martinus.sk
// - goodreads.com
// - databazeknih.cz
//
// Application releases:
// - sanet.st
'use strict';
function testDomain(domain) {
return document.location.hostname.toLowerCase() == domain.toLowerCase();
}
function testPath(path, query) {
return document.location.pathname.toLowerCase() == '/'.concat(path.toLowerCase(), '.php')
&& (!query || document.location.search.toLowerCase().startsWith('?'.concat(query.toLowerCase())));
}
const isRED = testDomain('redacted.ch');
const isNWCD = testDomain('notwhat.cd');
const isOrpheus = testDomain('orpheus.network');
const isUpload = testPath('upload');
const isEdit = testPath('torrents', 'action=editgroup&');
const isRequestNew = testPath('requests', 'action=new');
const isRequestEdit = testPath('requests', 'action=edit&');
const urlParser = /^\s*(https?:\/\/\S+)\s*$/i;
var prefs = {
set: function(prop, def) { this[prop] = GM_getValue(prop, def) },
save: function() {
for (var iter in this) {
if (typeof this[iter] != 'function' && this[iter] != undefined) GM_setValue(iter, this[iter]);
}
},
};
prefs.set('autfill_delay', 1000); // delay in ms to autofill form after pasting text into box, 0 to disable
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 emphasing genres
prefs.set('single_threshold', 10 * 60); // For autodetection of release type: max length of single in s
prefs.set('EP_threshold', 30 * 60); // For autodetection of release type: max time of EP in s
prefs.set('auto_preview_cover', 1);
prefs.set('auto_rehost_cover', 1); // PTPIMG / using 3rd party script
prefs.set('fetch_tags_from_artist', 0); // add N most used tags from release artist (if one) - experimental/may inject nonsense tags for coinciding artists; 0 for disable
prefs.set('estimate_decade_tag', 1); // deduce decade tag (1980s, etc.) from album year for regular albums
prefs.set('dragdrop_patch_to_ptpimgit', 1);
prefs.set('sacd_decoder', 'foobar2000\'s SACD decoder (direct-fp64)');
prefs.set('ptpimg_api_key');
prefs.set('spotify_clientid');
prefs.set('spotify_clientsecret');
prefs.set('soundcloud_clientid');
prefs.set('catbox_userhash');
prefs.set('upcoming_tags', ''); // add this tag(s) to upcoming releases (requests); empty to disable
prefs.set('remap_texttools_newlines', 0); // convert underscores to linebreaks (ambiguous)
// request specific
prefs.set('request_default_bounty', 0); // set this bounty in MB after successfull fill of request form / 0 for disable
prefs.set('always_request_perfect_flac', 0);
prefs.set('include_tracklist_in_request', 0); // 0: include one line summary only; 1: include full tracklisting
// tracklist specific
prefs.set('sort_tracklist', 1);
prefs.set('tracklist_style', 1); // 1: classical, 2: propertional font right-justified
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('tracklist_size', 2);
prefs.set('title_separator', '. '); // divisor of track# and title
prefs.set('pad_leader', ' ');
prefs.set('tracklist_head_color', '#46a7b4'); // #4682B4 / #a7bdd0
// classical tracklist only components colouring
prefs.set('tracklist_disctitle_color', '#2bb7b7'); // #bb831c
prefs.set('tracklist_work_color', 'Olive'); //#b16890
prefs.set('tracklist_tracknumber_color', '#8899AA');
prefs.set('tracklist_composer_color', '#8ca014');
prefs.set('tracklist_artist_color', '#bd8218');
prefs.set('tracklist_duration_color', '#33a6cc'); // #2196f3
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;
}
#cover-preview {
width: 100%;
/*box-shadow: 3px 3px 3px;*/
}
#cover-size {
width: 100%;
color: white; background-color: teal;
text-align: center;
/*padding-top: 5px;*/
}
`;
var ref, tbl, elem, child, rehostItBtn;
var spotifyCredentials = {}, siteArtistsCache = {}, notSiteArtistsCache = [];
var messages = null, autofill = false, domParser = new DOMParser(), dom;
const ctxt = document.createElement('canvas').getContext('2d');
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 = fillFromText;
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 = fillFromText;
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 = fillFromText;
elem.append(child);
common2();
tbl.style.marginBottom = '10px';
ref.parentNode.insertBefore(tbl, ref);
} else if (isRequestNew) {
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 = fillFromText;
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 = fillFromText;
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);
}
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;
var desc = document.getElementById('body');
if (desc != null && urlParser.test(desc.value)) {
child.value = RegExp.$1;
desc.value = '';
if (prefs.autfill_delay > 0) autoFill();
}
//child.ondblclick = clear0;
child.ondrop = clear0;
child.onmousedown = clear1;
if (prefs.autfill_delay > 0) {
child.onpaste = autoFill;
child.ondrop = autoFill;
}
elem.append(child);
tbl.append(elem);
elem = document.createElement('td');
elem.style.textAlign = 'center';
}
function common2() {
tbl.append(elem);
var tb = document.createElement('tbody');
tb.append(tbl);
tbl = document.createElement('table');
tbl.id = 'upload assistant';
tbl.append(tb);
}
if ((ref = document.getElementById('yadg_input')) != null) ref.ondrop = clear0;
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';
});
}
if (!isNWCD) {
if ((ref = document.getElementById('image') || document.querySelector('input[name="image"]')) != null) {
ref.ondragover = voidDragHandler;
ref.ondblclick = imageClear;
ref.onmousedown = imageClear1;
ref.ondrop = imageDropHandler;
ref.onpaste = imagePasteHandler;
}
rehostItBtn = document.querySelector('input.rehost_it_cover[type="button"]');
if (prefs.dragdrop_patch_to_ptpimgit && rehostItBtn != null) {
rehostItBtn.dataset.caption = rehostItBtn.value;
rehostItBtn.ondragover = voidDragHandler;
rehostItBtn.ondrop = rehostDropHandler;
}
}
['album_desc', 'body', 'description', 'release_desc', 'release_lineage'].forEach(function(ID) {
if ((ref = document.getElementById(ID)) == null) return;
ref.ondragover = voidDragHandler;
ref.ondrop = descDropHandler;
ref.onpaste = descPasteHandler;
});
if (isRequestNew) {
let title = document.querySelector('input[name="title"]');
if (title != null) setTimeout(function(e) { title.readOnly = false }, 1000);
}
Array.prototype.includesCaseless = function(str) {
return typeof str == 'string' && this.find(it => typeof it == 'string' && it.toLowerCase() == str.toLowerCase()) != undefined;
};
Array.prototype.pushUnique = function(...items) {
items.forEach(it => { if (!this.includes(it)) this.push(it) });
return this.length;
};
Array.prototype.pushUniqueCaseless = function(...items) {
items.forEach(it => { if (!this.includesCaseless(it)) this.push(it) });
return this.length;
};
// 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 Array.isArray(arr) && arr.length == this.length && arr.sort().toString() == this.sort().toString();
};
Array.prototype.homogenous = function() {
return this.every(elem => elem === this[0]);
}
String.prototype.toASCII = function() {
return this.normalize("NFKD").replace(/[\x00-\x1F\u0080-\uFFFF]/g, '');
};
String.prototype.trueLength = function() {
return this.normalize('NFKD').replace(/[\u0300-\u036f]/g, '').length;
// var index = 0, width = 0, len = 0;
// while (index < this.length) {
// var point = this.codePointAt(index);
// width = 0;
// while (point) {
// ++width;
// point = point >> 8;
// }
// index += Math.round(width / 2);
// ++len;
// }
// return len;
};
String.prototype.flatten = function() {
return this.replace(/\n/g, '\x1C').replace(/\r/g, '\x1D');
};
String.prototype.expand = function() {
return this.replace(/\x1D/g, '\r').replace(/\x1C/g, '\n');
}
Date.prototype.getDateValue = function() {
return Math.floor((this.getTime() / 1000 / 60 - this.getTimezoneOffset()) / 60 / 24);
};
File.prototype.getText = function(encoding) {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() { resolve(reader.result) };
reader.onerror = reader.onabort = function() { reject('FileReader error (' + this.name + ')') };
reader.readAsText(this, encoding);
}.bind(this));
};
const excludedCountries = [
/\b(?:United\s+States|USA?)\b/,
/\b(?:United\s+Kingdom|(?:Great\s+)?Britain|England|GB|UK)\b/,
/\b(?:Europe|European\s+Union|EU)\b/,
/\b(?:Unknown)\b/,
];
class TagManager extends Array {
constructor(...tags) {
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 = [
[/^Pop\s*(?:[\-\−\—\–]\s*)?Rock$/i, 'pop.rock'],
[/^Rock\s*(?:[\-\−\—\–]\s*)?Pop$/i, 'pop.rock'],
[/^Rock\s+n\s+Roll$/i, 'rock.and.roll'],
['AOR', 'album.oriented.rock'],
[/^(?:Prog)\.?\s*(?:Rock)$/i, 'progressive.rock'],
[/^Synth[\s\-\−\—\–]+Pop$/i, 'synthpop'],
[/^World(?:\s+and\s+|\s*[&+]\s*)Country$/i, 'world.music', 'country'],
['World', 'world.music'],
[/^(?: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', 'electronic'],
['Metal', 'heavy.metal'],
['NonFiction', 'non.fiction'],
['Rap', 'hip.hop'],
['NeoSoul', 'neo.soul'],
['NuJazz', 'nu.jazz'],
[/^J[\s\-]Pop$/i, 'jpop'],
[/^K[\s\-]Pop$/i, 'jpop'],
[/^J[\s\-]Rock$/i, 'jrock'],
['Hardcore', 'hardcore.punk'],
['Garage', 'garage.rock'],
[/^(?:Neo[\s\-\−\—\–]+Classical)$/i, 'neoclassical'],
[/^(?:Bluesy[\s\-\−\—\–]+Rock)$/i, 'blues.rock'],
[/^(?:Be[\s\-\−\—\–]+Bop)$/i, 'bebop'],
[/^(?:Chill)[\s\-\−\—\–]+(?:Out)$/i, 'chillout'],
[/^(?:Atmospheric)[\s\-\−\—\–]+(?:Black)$/i, 'atmospheric.black.metal'],
['GoaTrance', 'goa.trance'],
[/^Female\s+Vocal\w*$/i, 'female.vocalist'],
['Contemporary R&B', 'contemporary.rhytm.and.blues'],
// Country aliases
['Canada', 'canadian'],
['Australia', 'australian'],
['New Zealand', 'new.zealander'],
['Japan', 'japanese'],
['Taiwan', 'thai'],
['China', 'chinese'],
['Singapore', 'singaporean'],
[/^(?:Russia|Russian\s+Federation|Россия|USSR|СССР)$/i, 'russian'],
['Turkey', 'turkish'],
['Israel', 'israeli'],
['France', 'french'],
['Germany', 'german'],
['Spain', 'spanish'],
['Italy', 'italian'],
['Sweden', 'swedish'],
['Norway', 'norwegian'],
['Finland', 'finnish'],
['Greece', 'greek'],
[/^(?:Netherlands|Holland)$/i, 'dutch'],
['Belgium', 'belgian'],
['Luxembourg', 'luxembourgish'],
['Denmark', 'danish'],
['Switzerland', 'swiss'],
['Austria', 'austrian'],
['Portugal', 'portugese'],
['Ireland', 'irish'],
['Scotland', 'scotish'],
['Iceland', 'icelandic'],
[/^(?:Czech\s+Republic|Czechia)$/i, 'czech'],
[/^(?:Slovak\s+Republic|Slovakia)$/i, 'slovak'],
['Hungary', 'hungarian'],
['Poland', 'polish'],
['Estonia', 'estonian'],
['Latvia', 'latvian'],
['Lithuania', 'lithuanian'],
['Moldova', 'moldovan'],
['Armenia', 'armenian'],
['Ukraine', 'ukrainian'],
['Yugoslavia', 'yugoslav'],
['Serbia', 'serbian'],
['Slovenia', 'slovenian'],
['Croatia', 'croatian'],
['Macedonia', 'macedonian'],
['Montenegro', 'montenegrin'],
['Romania', 'romanian'],
['Malta', 'maltese'],
['Brazil', 'brazilian'],
['Mexico', 'mexican'],
['Argentina', 'argentinean'],
['Jamaica', 'jamaican'],
// Books
['Beletrie', 'fiction'],
['Satira', 'satire'],
['Komiks', 'comics'],
['Komix', 'comics'],
// Removals
['Unknown'],
['Other'],
['New'],
['Ostatni'],
['Knihy'],
['Audioknihy'],
['dsbm'],
[/^(?:Audio\s*kniha|Audio\s*Book)$/i],
].concat(excludedCountries.map(it => [it]));
this.splits = [
['Alternative', 'Indie'],
['Rock', 'Pop'],
['Soul', 'Funk'],
['Ska', 'Rocksteady'],
['Jazz Fusion', 'Jazz Rock'],
['Rock', 'Pop'],
['Jazz', 'Funk'],
];
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'],
[/^(?:Sacred\s+Vocal)\b/i, 'classical'],
[/\b(?:Soundtracks?|Films?|Games?|Video|Series?|Theatre|Musical)\b/i, 'score'],
];
if (tags.length > 0) this.add(...tags);
}
add(...tags) {
var added = 0;
for (var tag of tags) {
if (typeof tag != 'string') continue;
qobuzTranslations.forEach(function(it) { if (tag == it[0]) tag = it[1] });
this.presubstitutions.forEach(k => { if (k[0].test(tag)) tag = tag.replace(k[0], k[1]) });
tag.split(/\s*[\,\/\;\>\|]+\s*/).forEach(function(tag) {
//qobuzTranslations.forEach(function(it) { if (tag == it[0]) tag = it[1] });
tag = tag.toASCII().replace(/\(.*?\)|\[.*?\]|\{.*?\}/g, '').trim();
if (tag.length <= 0 || tag == '?') return null;
function test(obj) {
return typeof obj == 'string' && tag.toLowerCase() == obj.toLowerCase()
|| obj instanceof RegExp && obj.test(tag);
}
for (var k of this.substitutions) {
if (test(k[0])) {
if (k.length >= 1) added += this.add(...k.slice(1));
else addMessage('Warning: invalid tag \'' + tag + '\' found', 'ua-warning');
return;
}
}
for (k of this.additions) {
if (test(k[0])) added += this.add(...k.slice(1));
}
for (k of this.splits) {
if (new RegExp('^' + k[0] + '(?:\\s+and\\s+|\\s*[&+]\\s*)' + k[1] + '$', 'i').test(tag)) {
added += this.add(k[0], k[1]); return;
}
if (new RegExp('^' + k[1] + '(?:\\s+and\\s+|\\s*[&+]\\s*)' + k[0] + '$', 'i').test(tag)) {
added += this.add(k[0], k[1]); return;
}
}
tag = tag.
replace(/^(?:Alt\.)\s*(\w+)$/i, 'Alternative $1').
replace(/\b(?:Alt\.)(?=\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.sort().join(', ') }
};
return;
function fillFromText(e) {
if (e == undefined && !autofill) return;
autofill = false;
var overwrite = this.id == 'fill-from-text';
var clipBoard = document.getElementById('UA data');
if (clipBoard == null) return false;
const VA = 'Various Artists';
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'), xhr = new XMLHttpRequest();
if (category == null && document.getElementById('releasetype') != null
|| category != null && (category.value == 0 || category.value == 'Music')) return fillFromText_Music();
if (category != null && (category.value == 1 || category.value == 'Applications')) return fillFromText_Apps();
if (category != null && (category.value == 2 || category.value == 3
|| category.value == 'E-Books' || category.value == 'Audiobooks')) return fillFromText_Ebooks();
return category == null ? fillFromText_Apps(true) || fillFromText_Ebooks() : false;
function fillFromText_Music() {
if (messages != null) messages.parentNode.removeChild(messages);
const divs = ['—', '⸺', '⸻'];
const vaParser = /^(?:Various(?:\s+Artists)?|VA|\<various\s+artists\>|Různí(?:\s+interpreti)?)$/i;
const multiArtistParsers = [
/(?:\s+[\/\|\×]|\s*(?:;|,(?!\s*(?:[JjSs]r)\b)(?:\s*[Aa]nd\s+)?))\s+/,
];
const ampersandParsers = [
/\s+(?:meets|vs\.?|X)\s+/i,
/\s*[;\/\|\×]\s*/,
/\s+(?:[\&\+]|and)\s+(?!:his\b|her\b|Friends$|Strings$)/i, // /\s+(?:[\&\+]|and)\s+(?!(?:The|his|her|Friends)\b)/i,
/\s*\+\s*(?!(?:his\b|her\b|Friends$|Strings$))/i,
];
const featParsers = [
/\s+(?:meets)\s+(.*?)\s*$/i,
/\s+(?:[Ww]ith)\s+(?!:his\b|her\b|Friends$|Strings$)(.*?)\s*$/,
/(?:\s+[\-\−\—\–\_])?\s+(?:[Ff](?:eaturing|t\.))\s+(.*?)\s*$/,
/(?:\s+[\-\−\—\–\_])?\s+(?:[Ff]eat\.)\s+(.*?)\s*$/,
/\s+\[\s*f(?:eat(?:\.|uring)|t\.)\s+([^\[\]]+?)\s*\]/i,
/\s+\(\s*f(?:eat(?:\.|uring)|t\.)\s+([^\(\)]+?)\s*\)/i,
/\s+\[\s*with\s+(?!:his\b|her\b|Friends$|Strings$)([^\[\]]+?)\s*\]/i,
/\s+\(\s*with\s+(?!:his\b|her\b|Friends$|Strings$)([^\(\)]+?)\s*\)/i,
/\s+\[\s*(?:(?:en\s+)?duo\s+)?avec\s+([^\[\]]+?)\s*\]/i,
/\s+\(\s*(?:(?:en\s+)?duo\s+)?avec\s+([^\(\)]+?)\s*\)/i,
];
const remixParsers = [
/\s+\((?:The\s+)Remix(?:e[sd])?\)/i,
/\s+\[(?:The\s+)Remix(?:e[sd])?\]/i,
/\s+(?:The\s+)Remix(?:e[sd])?\s*$/i,
/\s+\(([^\(\)]+?)(?:[\'\’\`]s)?\s+(?:(?:Extended|Enhanced)\s+)?Remix\)/i,
/\s+\[([^\[\]]+?)(?:[\'\’\`]s)?\s+(?:(?:Extended|Enhanced)\s+)?Remix\]/i,
/\s+\(\s*(?:(Extended|Enhanced)\s+)?Remix(?:ed)?\s+by\s+([^\(\)]+)\)/i,
/\s+\[\s*(?:(Extended|Enhanced)\s+)?Remix(?:ed)?\s+by\s+([^\[\]]+)\]/i,
];
const otherArtistsParsers = [
[/^(.*?)\s+(?:under|(?:conducted)\s+by)\s+(.*)$/, 4],
[/^()(.*?)\s+\(conductor\)$/i, 4],
//[/^()(.*?)\s+\(.*\)$/i, 1],
];
const pseudoArtistParsers = [
/^(?:#?N\/?A|[JS]r\.?)$/i,
/^(?:traditional|lidová)$/i,
/\b(?:traditional|lidová)$/,
/^(?:tradiční|lidová)\s+/,
/^(?:[Aa]nonym)/,
/^(?:[Ll]iturgical\b|[Ll]iturgick[áý])/,
/^(?:auditorium|[Oo]becenstvo|[Pp]ublikum)$/,
/^(?:Various\s+Composers)$/i,
];
const artistStrips = [
/\s+(?:aka|AKA)\.?\s+(.*)/,
/\s+\([^\(\)]+\)$/,
/\s+\[[^\[\]]+\]$/,
/\s+\{[^\{\}]+\}$/,
];
var track, tracks = [], media;
if (urlParser.test(clipBoard.value)) return fetchOnline_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, albumSize = 0;
var release = {
totaldiscs: 1,
srs: [],
};
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: metaData.shift().trim() || undefined,
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()),
filesize: 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 (/^(\d+)\s*[\/]\s*(\d+)$/.test(track.tracknumber)) { // track/totaltracks
addMessage('Warning: nonstandard track number formatting for track ' + RegExp.$1 + ': ' + track.tracknumber, 'ua-warning');
track.tracknumber = RegExp.$1;
if (!track.totaltracks) track.totaltracks = parseInt(RegExp.$2);
} else if (/^(\d+)[\.\-](\d+)$/.test(track.tracknumber)) { // discnumber.tracknumber
addMessage('Warning: nonstandard track number formatting for track ' + RegExp.$2 + ': ' + track.tracknumber, 'ua-warning');
if (!track.discnumber) track.discnumber = parseInt(RegExp.$1);
track.tracknumber = RegExp.$2;
}
if (track.discnumber) {
if (/^(\d+)\s*\/\s*(\d+)/.test(track.discnumber)) {
addMessage('Warning: nonstandard disc number formatting for track ' + track.tracknumber + ': ' + track.discnumber, 'ua-warning');
track.discnumber = RegExp.$1;
if (!track.totaldiscs) track.totaldiscs = RegExp.$2;
} else track.discnumber = parseInt(track.discnumber);
if (isNaN(track.discnumber)) {
addMessage('Warning: invalid disc numbering for track ' + track.tracknumber, 'ua-warning');
track.discnumber = undefined;
}
if (track.discnumber > release.totaldiscs) release.totaldiscs = track.discnumber;
}
if (track.comment == '.') track.comment = undefined;
if (track.comment) {
track.comment = track.comment.expand();
if (prefs.remap_texttools_newlines) track.comment = track.comment.replace(/__/g, '\r\n').replace(/_/g, '\n') // ambiguous
track.comment = collapseGaps(track.comment);
}
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);
albumSize += track.filesize;
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 : extractYear(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');
}
if (release.totaldiscs > 1 && tracks.some(it => it.totaldiscs != release.totaldiscs))
addMessage('Info: at least one track not having properly set TOTALDISCS (' + release.totaldiscs + ')', 'ua-info');
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');
setUniqueProperty('album', 'album title');
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) {
setProperty('trackArtists', 'track_artist');
setProperty('totalTracks', 'totaltracks');
setProperty('discSubtitles', 'discsubtitle');
setProperty('composers', 'composer');
setProperty('catalogs', 'catalog');
setProperty('bitrates', 'bitrate');
setProperty('bds', 'bd');
setProperty('rgs', 'rg');
setProperty('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;
setProperty('dirpaths', 'dirpath');
setProperty('comments', 'comment');
setProperty('genres', 'genre');
setProperty('urls', 'url');
function setProperty(propName, trackProp) {
if (!Array.isArray(release[propName])) release[propName] = [];
if (iter[trackProp] !== undefined && iter[trackProp] !== null && (typeof iter[trackProp] != 'string'
|| iter[trackProp].length > 0) && !release[propName].includes(iter[trackProp])) {
release[propName].push(iter[trackProp]);
}
}
});
if (!release.totalTracks) addMessage('Warning: total tracks not set', 'ua-warning');
if (release.totalTracks.length > 0) {
if (release.totalTracks.length > 1) {
addMessage('Warning: total tracks not consistent across release: ' + release.totalTracks, 'ua-warning');
} else if (release.totalTracks[0] != tracks.length) {
addMessage('Warning: total tracks not matching tracklist length: ' + release.totalTracks[0] + ' != ' +
tracks.length, 'ua-warning');
}
}
tracks.forEach(function(track1, ndx1) {
if (tracks.some((track2, ndx2) => ndx2 < ndx1 && track1.tracknumber == track2.tracknumber
&& track1.discnumber == track2.discnumber && track1.discsubtitle == track2.discsubtitle)) {
addMessage('Warning: duplicate track ' + (track1.discnumber ? track1.discnumber + '-' : '') +
(track1.discsubtitle ? track1.discsubtitle + '-' : '') +
track1.tracknumber, 'ua-warning');
}
});
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, isFromDSD = false, isClassical = false;
var canSort = tracks.every((tr1, ndx1) => tracks.every((tr2, ndx2) => ndx1 == ndx2
|| tr1.tracknumber != tr2.tracknumber || tr1.discnumber != tr2.discnumber));
var yadg_prefil = '', releaseType, editionTitle, isVA, iter, rx;
var tags = new TagManager();
albumBitrate /= totalTime;
if (tracks.every(it => /^(?:single)$/i.test(it.identifiers.RELEASETYPE))
|| tracks.length == 1 && totalTime > 0 && totalTime < prefs.single_threshold) {
releaseType = getReleaseIndex('Single');
} else if (tracks.every(it => it.identifiers.RELEASETYPE == 'EP')) {
releaseType = getReleaseIndex('EP');
} else if (tracks.every(it => /^soundtrack$/i.test(it.identifiers.RELEASETYPE))) {
releaseType = getReleaseIndex('Soundtrack');
tags.add('score');
composerEmphasis = true;
}
if (release.genres.length > 0) {
const classicalGenreParsers = [
/\b(?:Classical|Classique|Klassik|Symphony|Symphonic(?:al)?|Operas?|Operettas?|Ballets?|(?:Violin|Cello|Piano)\s+Solos?|Chamber|Choral|Choirs?|Orchestral|Etudes?|Duets|Concertos?|Cantatas?|Requiems?|Passions?|Mass(?:es)?|Oratorios?|Poems?|Sacred|Secular|Vocal\s+Music)\b/i,
];
release.genres.forEach(function(genre) {
classicalGenreParsers.forEach(function(classicalGenreParser) {
if (classicalGenreParser.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)) {
if (!releaseType) releaseType = getReleaseIndex('Soundtrack');
composerEmphasis = true;
}
if (/\b(?:Christmas\s+Music)\b/i.test(genre)) {
composerEmphasis = true;
}
tags.add(...genre.split(/\s*\|\s*/));
});
if (release.genres.length > 1) {
addMessage('Warning: inconsistent genre accross album: ' + release.genres, 'ua-warning');
}
}
if (isClassical && !tracks.every(track => track.composer)) {
addMessage('Warning: all tracks composers must be set for clasical music' + ruleLink('2.3.17'), 'ua-warning', true);
//return false;
}
// Processing artists: recognition, splitting and dividing to categores
var roleCollisions = [
[4, 5], // main
[0, 4], // guest
[], // remixer
[], // composer
[], // conductor
[], // DJ/compiler
[], // producer
];
isVA = vaParser.test(release.artist);
var artists = [];
for (i = 0; i < 7; ++i) artists[i] = [];
if (!isVA) addArtists(0, yadg_prefil = spliceGuests(release.artist));
var albumGuests = artists[1].slice();
featParsers.slice(3).forEach(function(rx, __a, ndx) {
matches = rx.exec(release.album);
if (matches != null && (ndx < 2 || splitArtists(matches[1]).every((artist) => looksLikeTrueName(artist, 1)))) {
addArtists(1, matches[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.replace(/\b\d{4}\b/g, '').replace(/\s{2,}/g, ' ').trim());
})
if (((matches = /^(.*?)\s+Presents\s+(.*)$/.exec(release.album)) != null
|| isVA && (matches = (/\s+\(compiled\s+by\s+(.*?)\)\s*$/i.exec(release.album)
|| /\s+compiled\s+by\s+(.*?)\s*$/i.exec(release.album))) != null) && looksLikeTrueName(matches[1])) {
addArtists(5, matches[1]);
if (!releaseType) releaseType = getReleaseIndex('Compilation');
}
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, ndx) {
matches = rx.exec(iter.title);
if (matches != null && (ndx < 2 || splitArtists(matches[1]).every((artist) => looksLikeTrueName(artist, 1)))) {
iter.track_artist = (!isVA && (!iter.track_artist || iter.track_artist.includes(matches[1])) ?
iter.artist : iter.track_artist) + ' feat. ' + matches[1];
addArtists(1, matches[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.replace(/\b\d{4}\b/g, '').replace(/\s{2,}/g, ' ').trim());
});
}
if (isClassical && !iter.composer && /^([^\(\)\[\]\{\},:]+?)(?:\s*\(\d{4}\s*-\s*\d{4}\))/.test(iter.discsubtitle)) {
//track.composer = RegExp.$1;
addArtists(3, RegExp.$1);
}
}
for (i = 0; i < Math.round(tracks.length / 2); ++i) splitAmpersands();
function addArtists(ndx, str) {
if (str) splitArtists(str).forEach(function(artist) {
artist = ndx != 0 ? strip(artist) : guessOtherArtists(artist);
if (artist.length > 0 && !pseudoArtistParsers.some(rx => rx.test(artist))
&& !artists[ndx].includesCaseless(artist)
&& !roleCollisions[ndx].some(n => artists[n].includesCaseless(artist))) artists[ndx].push(artist);
});
}
function addTrackPerformers(str) {
if (str) splitArtists(spliceGuests(str, 1)).forEach(function(artist) {
artist = guessOtherArtists(artist);
if (artist.length > 0 && !pseudoArtistParsers.some(rx => rx.test(artist))
&& !artists[0].includesCaseless(artist)
&& (isVA || !artists[1].includesCaseless(artist))) artists[isVA ? 0 : 1].push(artist);
});
}
function splitArtists(str) {
var result = [str];
multiArtistParsers.forEach(function(multiArtistParser) {
for (i = result.length; i > 0; --i) {
var j = result[i - 1].split(multiArtistParser);
if (j.length >= 2 && j.every(twoOrMore)
&& !j.some(artist => pseudoArtistParsers.some(rx => rx.test(artist)))
&& !getSiteArtist(result[i - 1])) result.splice(i - 1, 1, ...j);
}
});
return result;
}
function splitAmpersands() {
for (var ndx = 0; ndx < artists.length; ++ndx) {
ampersandParsers.forEach(function(ampersandParser) {
for (var i = artists[ndx].length; i > 0; --i) {
var 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(artist) {
return !artists[ndx].includesCaseless(artist)
&& !pseudoArtistParsers.some(rx => rx.test(artist))
&& !roleCollisions[ndx].some(n => artists[n].includesCaseless(artist));
}));
}
}
});
}
}
function spliceGuests(str, level = 1) {
(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 strip(name);
}
function getSiteArtist(artist) {
if (!artist || notSiteArtistsCache.includesCaseless(artist)) return null;
var key = Object.keys(siteArtistsCache).find(it => it.toLowerCase() == artist.toLowerCase());
if (key) return siteArtistsCache[key];
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);
if (response.status != 'success') {
notSiteArtistsCache.pushUniqueCaseless(artist);
return null;
}
return (siteArtistsCache[artist] = response.response);
}
function twoOrMore(artist) { return artist.length >= 2 && !pseudoArtistParsers.some(rx => rx.test(artist)) };
function looksLikeTrueName(artist, index = 0) {
return twoOrMore(artist)
&& (index == 0 || !/^(?:his\b|her\b|Friends$|Strings$)/i.test(artist))
&& artist.split(/\s+/).length >= 2
&& !pseudoArtistParsers.some(rx => rx.test(artist)) || getSiteArtist(artist);
}
function strip(art) { return artistStrips.reduce((acc, it) => acc.replace(it, ''), art) }
function getRealTrackArtist(track) {
if (typeof track != 'object') return null;
if (track.track_artist == release.artist) return undefined;
var trackArtist = track.track_artist;
if (trackArtist && !isVA) {
let trackArtists = [], trackGuests = [], ta = trackArtist;
featParsers.slice(1).forEach(function(it) {
if (!it.test(ta)) return;
trackGuests.pushUniqueCaseless(RegExp.$1);
ta = ta.replace(it, '');
});
splitArtists(ta).forEach(function(artist) {
otherArtistsParsers.forEach(it => { if (it[0].test(artist)) artist = RegExp.$1 });
artist = strip(artist);
if (artist.length > 0 && !pseudoArtistParsers.some(rx => rx.test(artist))) trackArtists.pushUniqueCaseless(artist);
});
splitAmpersands(trackArtists);
splitAmpersands(trackGuests);
if (trackArtists.equalTo(artists[0]) && trackGuests.equalTo(albumGuests)) trackArtist = undefined;
function splitAmpersands(array) {
ampersandParsers.forEach(function(ampersandParser) {
for (var i = array.length; i > 0; --i) {
var j = array[i - 1].split(ampersandParser);
if (j.length >= 2 && j.every(twoOrMore) && !getSiteArtist(array[i - 1])
&& (j.some(it1 => artists.some(it2 => it2.includesCaseless(it1))) || j.every(looksLikeTrueName))) {
array.splice(i - 1, 1, ...j.filter(function(artist) {
return !array.includesCaseless(artist) && !pseudoArtistParsers.some(rx => rx.test(artist));
}));
}
}
});
}
}
return trackArtist;
}
if (elementWritable(document.getElementById('artist'))) {
let artistIndex = 0;
catLoop: for (i = 0; i < 7; ++i) for (iter of artists[i]
.filter(artist => !roleCollisions[i].some(n => artists[n].includesCaseless(artist)))
.sort((a, b) => a.localeCompare(b))) {
if (isUpload) {
var id = 'artist';
if (artistIndex > 0) id += '_' + artistIndex;
while ((ref = document.getElementById(id)) == null) addArtistField();
} else {
while ((ref = document.querySelectorAll('input[name="artists[]"]')).length <= artistIndex) addArtistField();
ref = ref[artistIndex];
}
if (ref == null) throw new Error('Failed to allocate artist fields');
ref.value = iter;
ref.nextElementSibling.value = i + 1;
if (++artistIndex >= 200) break catLoop;
}
if (overwrite && artistIndex > 0) while (document.getElementById('artist_' + artistIndex) != null) {
removeArtistField();
}
}
// Processing album title
const editionParsers = [
/\s+\(((?:Remaster(?:ed)?|Remasterizado|Remasterisée|Reissu(?:ed)?|Deluxe|Enhanced|Expanded|Limited|Version)\b[^\(\)]*|[^\(\)]*\b(?:Edition|Version|Promo|Release))\)$/i,
/\s+\[((?:Remaster(?:ed)?|Remasterizado|Remasterisée|Reissu(?:ed)?|Deluxe|Enhanced|Expanded|Limited|Version)\b[^\[\]]*|[^\[\]]*\b(?:Edition|Version|Promo|Release))\]$/i,
/\s+-\s+([^\[\]\(\)\-\−\—\–]*\b(?:(?:Remaster(?:ed)?|Remasterizado|Remasterisée|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|BD|BRD?)\]|\((?:Blu[\s\-\−\—\–]?Ray|BD|BRD?)\))$/, '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\s+of|Greatest\s+Hits|Complete\s+(.+?\s+)(?:Albums|Recordings))\b|Collection$)|^The(\s+\w+)+Years$/i, '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');
});
editionParsers.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 (elementWritable(ref = document.getElementById('title') || document.querySelector('input[name="title"]'))) {
ref.value = album;
}
if (yadg_prefil) yadg_prefil += ' ';
yadg_prefil += album;
if (elementWritable(ref = document.getElementById('yadg_input'))) {
ref.value = yadg_prefil || '';
if (yadg_prefil && (ref = document.getElementById('yadg_submit')) != null && !ref.disabled) ref.click();
}
if (!release.album_year) release.album_year = parseInt(getHomoIdentifier('PUBYEAR')) || undefined;
if (elementWritable(ref = document.getElementById('year'))) {
ref.value = release.album_year || '';
}
i = release.release_date && extractYear(release.release_date);
if (elementWritable(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 (!editionTitle && (tracks.every(function(it) {
return ['remastered', 'Remasterisée', 'remasterizado'].some(function(k) {
return it.title.toLowerCase().endsWith(' (' + k + ')')
|| it.title.toLowerCase().endsWith(' [' + k + ']')
|| it.title.toLowerCase().endsWith(' {' + k + '}');
});
}))) {
editionTitle = 'Remastered';
}
if (elementWritable(ref = document.getElementById('remaster_title'))) {
ref.value = editionTitle || '';
}
if (elementWritable(ref = document.getElementById('remaster_record_label') || document.querySelector('input[name="recordlabel"]'))) {
ref.value = release.label && release.label.split(/\s*;\s*/g)
.map(it => isVA || it != release.artist ? it : 'self-released').join(' / ') || '';
}
if (elementWritable(ref = document.getElementById('remaster_catalogue_number') || document.querySelector('input[name="cataloguenumber"]'))) {
ref.value = release.catalogs.length >= 1 && release.catalogs.map(it => it.replace(/\s*;\s*/g, ' / ')).join(' / ')
|| getHomoIdentifier('BARCODE') || '';
}
var br_isSet = (ref = document.getElementById('bitrate')) != null && ref.value;
if (elementWritable(ref = document.getElementById('format'))) {
ref.value = release.codec || '';
ref.onchange(); //exec(function() { Format() });
}
if (isRequestNew) {
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 (isRequestNew) {
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 (!media) {
if (tracks.every(isRedBook)) {
addMessage('Info: media not determined - CD estimated', 'ua-info');
media = 'CD';
} 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 (elementWritable(ref = document.getElementById('media'))) ref.value = media || '';
if (isRequestNew) {
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 == 'WEB') for (iter of tracks) {
if (iter.duration > 29.5 && iter.duration < 30.5) {
addMessage('Warning: track ' + iter.tracknumber + ' possible preview', 'ua-warning');
}
}
if (tracks.every(it => it.identifiers.ORIGINALFORMAT && it.identifiers.ORIGINALFORMAT.includes('DSD'))) {
isFromDSD = true;
}
// Release type
if (!releaseType) {
if (tracks.every(it => it.title.endsWith(' (Live)') || it.title.endsWith(' [Live]'))) {
releaseType = getReleaseIndex('Live album');
} else if (/\b(?:Mixtape)\b/i.test(release.album)) {
releaseType = getReleaseIndex('Mixtape');
} else if (isVA) {
releaseType = getReleaseIndex('Compilation');
} else if (tracks.every(it => it.identifiers.COMPILATION == 1)) {
releaseType = getReleaseIndex('Anthology');
}
}
if ((!releaseType || releaseType == 5) && totalTime <= prefs.EP_threshold && tracks.every(function(track) {
const rxs = [/\s+\([^\(\)]+\)\s*$/, /\s+\[[^\[\]]+\]\s*$/];
return rxs.reduce((acc, rx) => acc.replace(rx, ''), track.title)
== rxs.reduce((acc, rx) => acc.replace(rx, ''), tracks[0].title);
})) {
releaseType = getReleaseIndex('Single');
}
if (!releaseType) if (totalTime > 0 && totalTime < prefs.single_threshold) {
releaseType = getReleaseIndex('Single');
} else if (totalTime > 0 && totalTime < prefs.EP_threshold) {
releaseType = getReleaseIndex('EP');
}
if ((ref = document.getElementById('releasetype')) != null && !ref.disabled
&& (overwrite || ref.value == 0 || ref.value == '---')) ref.value = releaseType || getReleaseIndex('Album');
// Tags
if (prefs.estimate_decade_tag && (isNaN(totalTime) || totalTime < 2 * 60 * 60)
&& release.album_year > 1900 && [1, 3, 5, 9, 13, undefined].includes(releaseType)
/*&& !/\b(?:Remaster(?:ed)?|Remasterizado|Remasterisée|Reissue|Anniversary|Collector(?:'?s)?)\b/i.test(editionTitle)*/)
tags.add(Math.floor(release.album_year/10) * 10 + 's'); // experimental
if (release.country) {
if (!excludedCountries.some(it => it.test(release.country))) tags.add(release.country);
}
if (elementWritable(ref = document.getElementById('tags'))) {
ref.value = 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 (!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 && tracks.map(track => track.title).homogenous()) {
addMessage('Warning: all tracks having same title: ' + tracks[0].title, 'ua-warning');
}
if (isUpload) findPreviousUploads();
// Album description
url = release.urls.length == 1 && release.urls[0] || getStoreUrl();
const vinylTest = /^((?:Vinyl|LP) rip by\s+)(.*)$/im;
const vinyltrackParser = /^([A-Z])[\-\.\s]?((\d+)(?:\.\d+)?)$/;
const classicalWorkParsers = [
/^(.*\S):\s+(.*)$/,
/^(.+?):\s+([IVXC]+\.\s+.*)$/,
];
var description;
if (isRequestNew || isRequestEdit) { // request
description = []
if (release.release_date) {
i = new Date(release.release_date);
let today = new Date(new Date().toDateString());
description.push((isNaN(i) || i < today ? 'Released' : 'Releasing') + ' ' +
(isNaN(i) ? release.release_date : i.toDateString()));
if ((ref = document.getElementById('tags')) != null && !ref.disabled) {
let tags = new TagManager(ref.value);
if (prefs.upcoming_tags && i >= today) tags.add(prefs.upcoming_tags);
ref.value = tags.toString();
}
}
if (!prefs.include_tracklist_in_request) {
let summary = '';
if (release.totaldiscs > 1) summary += release.totaldiscs + ' discs, ';
summary += tracks.length + ' track(s)';
if (totalTime > 0) summary += ', ' + makeTimeString(totalTime);
description.push(summary);
}
if (url) description.push('[url]' + url + '[/url]');
if (release.catalogs.length == 1 && /^\d{10,}$/.test(release.catalogs[0]) || /^\d{10,}$/.test(getHomoIdentifier('BARCODE'))) {
description.push('[url=https://www.google.com/search?q=' + RegExp.lastMatch + ']Find more stores...[/url]');
}
if (prefs.include_tracklist_in_request) description.push(genPlaylist());
if (release.comments.length == 1) description.push(release.comments[0]);
description = genAlbumHeader().concat(description.join('\n\n'));
if (description.length > 0) {
ref = document.getElementById('description');
if (elementWritable(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 { // upload
description = '';
let vinylRipInfo;
if (release.comments.length == 1 && release.comments[0]) {
description = '\n\n';
if ((matches = vinylTest.exec(release.comments[0])) != null) {
vinylRipInfo = release.comments[0].slice(matches.index).trim().split(/(?:[ \t]*\r?\n)+/);
description += release.comments[0].slice(0, matches.index).trim();
} else description += release.comments[0];
}
if (elementWritable(ref = document.getElementById('album_desc'))) {
ref.value = genPlaylist().concat(description);
preview(0);
}
if ((ref = document.getElementById('body')) != null && !ref.disabled) {
if (ref.textLength == 0) ref.value = genPlaylist().concat(description); else {
let editioninfo = '';
if (editionTitle) {
editioninfo = '[size=5][b]' + editionTitle;
if (release.release_date && (i = extractYear(release.release_date)) > 0) editioninfo += ' (' + i + ')';
editioninfo += '[/b][/size]\n\n';
}
ref.value = ref.value.concat('\n\n', editioninfo, genPlaylist(false, false), description);
}
preview(0);
}
// Release description
var lineage = '', comment = '', drinfo, srcinfo;
if (elementWritable(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 (vinylRipInfo) {
vinylRipInfo[0] = vinylRipInfo[0].replace(vinylTest, '$1[color=blue]$2[/color]');
if (hassr) { vinylRipInfo[0] = vinylRipInfo[0].replace(/^Vinyl\b/, 'vinyl') }
lineage += vinylRipInfo[0] + '\n\n[u]Lineage:[/u]' +
vinylRipInfo.slice(1).map(function(l) {
return '\n' + l
// RuTracker translation
.replace('Код класса состояния винила', 'Vinyl condition class')
.replace('Устройство воспроизведения', 'Turntable')
.replace('Головка звукоснимателя', 'Cartridge')
.replace('Предварительный усилитель', 'Preamplifier')
.replace('АЦП', 'ADC:')
.replace('Программа-оцифровщик', 'Software')
.replace('Обработка', 'Post-processing');
}).join('');
} else {
lineage += (hassr ? 'Vinyl' : ' vinyl') + ' rip by [color=blue][/color]\n\n[u]Lineage:[/u]\n';
}
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) addChannelInfo();
if (media == 'SACD' || isFromDSD) addDSDInfo();
drinfo += '[/hide]';
//addRGInfo();
} else { // WEB Hi-Res
if (ref == null || Object.keys(release.srs).length > 1) lineage = kHz;
if (release.channels && release.channels != 2) addChannelInfo();
if (isFromDSD) addDSDInfo(); else addDRInfo();
//if (lineage.length > 0) addRGInfo();
if (release.bds.length > 1) release.bds.filter(bd => bd != 24).forEach(function(bd) {
let hybrid_tracks = tracks.filter(it => it.bd == bd).sort(trackComparer).map(function(it) {
return (release.totaldiscs > 1 && it.discnumber ? it.discnumber + '-' : '').concat(it.tracknumber);
});
if (hybrid_tracks.length < 1) return;
if (lineage) lineage += '\n';
lineage += 'Note: track';
if (hybrid_tracks.length > 1) lineage += 's';
lineage += ' #' + hybrid_tracks.join(', ') +
(hybrid_tracks.length > 1 ? ' are' : ' is') + ' ' + bd + 'bit lossless';
});
if (Object.keys(release.srs).length == 1 && Object.keys(release.srs)[0] == 88200 || isFromDSD) {
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) addChannelInfo();
//addDRInfo();
//if (lineage.length > 0) addRGInfo();
if (release.codec == 'MP3' && release.vendor) {
// TODO: parse mp3 vendor string
} else 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 addDSDInfo() {
lineage += ' from DSD64';
if (prefs.sacd_decoder) lineage += ' using ' + prefs.sacd_decoder;
lineage += '\nOutput gain +0dB';
}
function addDRInfo() {
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 addRGInfo() {
if (release.rgs.length != 1) return false;
if (lineage.length > 0) lineage += ' | ';
lineage += 'RG'; //lineage += 'RG ' + rgs[0];
return true;
}
function addChannelInfo() {
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 (/*urlParser.test(url)*/ url) srcinfo = '[url]' + url + '[/url]';
if ((ref = document.getElementById('release_lineage')) != null) {
if (elementWritable(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 (elementWritable(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 (!isNWCD && elementWritable(document.getElementById('image') || document.querySelector('input[name="image"]'))) {
if (sel = getHomoIdentifier('IMGURL')) setImage(sel); else {
getCoverOnline(/^https?:\/\/(\w+\.)?discogs\.com\/release\/[\w\-]+\/?$/i.test(url) ? url.concat('/images') : url);
}
}
if (elementWritable(ref = document.getElementById('release_dynamicrange'))) {
ref.value = release.drs.length == 1 ? release.drs[0] : '';
}
if (isRequestNew && 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 genPlaylist(pad = true, header = true) {
if (prefs.tracklist_style <= 0) return null;
var playlist = '';
if (tracks.length > 1 || isRequestNew || isRequestEdit) {
if (pad && isRED) playlist += '[pad=5|0|0|0]';
if (header) playlist += genAlbumHeader();
playlist += '[size=4][color=' + prefs.tracklist_head_color + '][b]Tracklisting[/b][/color][/size]';
if (pad && isRED) playlist += '[/pad]';
playlist += '\n'; //'[hr]';
let lastDisc, lastSubtitle, lastWork, lastSide, vinylTrackWidth;
let block = 0, classicalWorks = new Map();
if (composerEmphasis /*isClassical*/ && !tracks.some(it => it.discsubtitle)) {
tracks.forEach(function(track) {
if (!track.composer) return;
(/*isClassical ? classicalWorkParsers : */classicalWorkParsers.slice(1)).forEach(function(classicalWorkParser) {
if (track.classical_work || !classicalWorkParser.test(track.title)) return;
classicalWorks.set(track.classical_work = RegExp.$1, {});
track.classical_title = RegExp.$2;
});
});
for (iter of classicalWorks.keys()) {
let work = tracks.filter(track => track.classical_work == iter);
if (work.length > 1 || tracks.every(track => track.classical_work)) {
if (work.map(it => it.track_artist).homogenous()) classicalWorks.get(iter).performer = work[0].track_artist;
if (work.map(it => it.composer).homogenous()) classicalWorks.get(iter).composer = work[0].composer;
} else {
work.forEach(function(track) {
delete track.classical_work;
delete track.classical_title;
});
classicalWorks.delete(iter);
}
}
}
let duration, volumes = new Map(tracks.map(it => [it.discnumber, undefined])), tnOffset = 0;
volumes.forEach(function(val, key) {
volumes.set(key, new Set(tracks.filter(it => it.discnumber == key).map(it => it.discsubtitle)).size)
});
if (!tracks.every(it => !isNaN(parseInt(it.tracknumber)))
&& !tracks.every(it => vinyltrackParser.test(it.tracknumber.toUpperCase()))) {
addMessage('Warning: inconsistent tracks numbering (' + tracks.map(it => it.tracknumber) + ')', 'ua-warning');
}
vinylTrackWidth = tracks.reduce(function(acc, it) {
return Math.max(vinyltrackParser.test(it.tracknumber.toUpperCase()) && parseInt(RegExp.$3), acc);
}, 0);
if (vinylTrackWidth) {
vinylTrackWidth = vinylTrackWidth.toString().length;
tracks.forEach(function(it) {
if (vinyltrackParser.test(it.tracknumber.toUpperCase()) != null)
it.tracknumber = RegExp.$1 + RegExp.$3.padStart(vinylTrackWidth, '0');
});
++vinylTrackWidth;
}
if (release.totaldiscs < 2 && tracks.reduce(computeLowestTrack, undefined) - 1)
addMessage('Info: volume ' + iter.discnumber + ' track numbering not starting from 1', 'ua-info');
const padStart = '[pad=0|0|5|0]';
for (iter of canSort && prefs.sort_tracklist ? tracks.sort(trackComparer) : tracks) {
let title = '', trackArtist = getRealTrackArtist(iter);
let ttwidth = vinylTrackWidth || (release.totaldiscs > 1 && iter.discnumber ?
tracks.filter(it => it.discnumber == iter.discnumber) : tracks).reduce(function (accumulator, it) {
return Math.max(accumulator, (parseInt(it.tracknumber) || it.tracknumber).toString().length);
}, 2);
function realTrackNumber() {
var tn = parseInt(iter.tracknumber);
return isNaN(tn) ? iter.tracknumber : (tn - tnOffset).toString().padStart(ttwidth, '0');
}
switch (prefs.tracklist_style) {
case 1: {
prologue('[size=' + prefs.tracklist_size + ']', '[/size]\n');
track = '[b][color=' + prefs.tracklist_tracknumber_color + ']';
track += realTrackNumber();
track += '[/color][/b]' + prefs.title_separator;
if (trackArtist && (!iter.classical_work || !classicalWorks.get(iter.classical_work).performer)) {
title = '[color=' + prefs.tracklist_artist_color + ']' + trackArtist + '[/color] - ';
}
title += iter.classical_title || iter.title;
if (iter.composer && composerEmphasis && release.composers.length != 1
&& (!iter.classical_work || !classicalWorks.get(iter.classical_work).composer)) {
title = title.concat(' [color=', prefs.tracklist_composer_color, '](', iter.composer, ')[/color]');
}
playlist += track + title;
if (iter.duration) playlist += ' [i][color=' + prefs.tracklist_duration_color +'][' +
makeTimeString(iter.duration) + '][/color][/i]';
break;
}
case 2: {
prologue('[size=' + prefs.tracklist_size + '][pre]', '[/pre][/size]');
track = realTrackNumber();
track += prefs.title_separator;
if (trackArtist && (!iter.classical_work || !classicalWorks.get(iter.classical_work).performer)) {
title = trackArtist + ' - ';
}
title += iter.classical_title || iter.title;
if (composerEmphasis && iter.composer && release.composers.length != 1
&& (!iter.classical_work || !classicalWorks.get(iter.classical_work).composer)) {
title = title.concat(' (', iter.composer, ')');
}
let l = 0, j, left, padding, spc;
duration = iter.duration ? '[' + makeTimeString(iter.duration) + ']' : null;
let width = prefs.max_tracklist_width - track.length;
if (duration) width -= duration.length + 1;
while (title.trueLength() > 0) {
j = width;
if (title.trueLength() > width) {
while (j > 0 && title[j] != ' ') { --j }
if (j <= 0) j = width;
}
left = title.slice(0, j).trim();
if (++l <= 1) {
playlist += track + left;
if (duration) {
spc = width - left.trueLength();
padding = (spc < 2 ? ' '.repeat(spc) : ' ' + prefs.pad_leader.repeat(spc - 1)) + ' ';
playlist += padding + duration;
}
width = prefs.max_tracklist_width - track.length - 2;
} else {
playlist += '\n' + ' '.repeat(track.length) + left;
}
title = title.slice(j).trim();
}
break;
}
}
}
switch (prefs.tracklist_style) {
case 1:
if (totalTime > 0) playlist += '\n\n' + divs[0].repeat(10) + '\n[color=' + prefs.tracklist_duration_color +
']Total time: [i]' + makeTimeString(totalTime) + '[/i][/color][/size]';
break;
case 2:
if (totalTime > 0) {
duration = '[' + makeTimeString(totalTime) + ']';
playlist += '\n\n' + divs[0].repeat(32).padStart(prefs.max_tracklist_width);
playlist += '\n' + 'Total time:'.padEnd(prefs.max_tracklist_width - duration.length) + duration;
}
playlist += '[/pre][/size]';
break;
}
function computeLowestTrack(acc, track) {
if (Number.isNaN(acc)) return NaN;
var tn = parseInt(track.tracknumber);
if (isNaN(tn)) return NaN;
return isNaN(acc) || tn < acc ? tn : acc;
}
function prologue(prefix, postfix) {
function block1() {
if (block == 3) playlist += postfix;
playlist += '\n';
if (isRED && ![1, 2].includes(block)) playlist += padStart;
block = 1;
}
function block2() {
if (block == 3) playlist += postfix;
playlist += '\n';
if (isRED && ![1, 2].includes(block)) playlist += padStart;
block = 2;
}
function block3() {
//if (block == 2 && isRED) playlist += '[hr]';
if (isRED && [1, 2].includes(block)) playlist += '[/pad]';
playlist += '\n';
if (block != 3) playlist += prefix;
block = 3;
}
if (release.totaldiscs > 1 && iter.discnumber != lastDisc) {
block1();
lastDisc = iter.discnumber;
lastSubtitle = lastWork = undefined;
playlist += '[color=' + prefs.tracklist_disctitle_color + '][size=3][b]';
if (iter.identifiers.VOL_MEDIA && tracks.filter(it => it.discnumber == iter.discnumber)
.every(it => it.identifiers.VOL_MEDIA == iter.identifiers.VOL_MEDIA)) {
playlist += iter.identifiers.VOL_MEDIA.toUpperCase() + ' ';
}
playlist += 'Disc ' + iter.discnumber;
if (iter.discsubtitle && (volumes.get(iter.discnumber) || 0) == 1) {
playlist += ' – ' + iter.discsubtitle;
lastSubtitle = iter.discsubtitle;
}
playlist += '[/b][/size]';
duration = tracks.filter(it => it.discnumber == iter.discnumber)
.reduce((acc, it) => acc + it.duration, 0);
if (duration > 0) playlist += ' [size=2][i][' + makeTimeString(duration) + '][/i][/size]';
playlist += '[/color]';
tnOffset = tracks.filter(track => track.discnumber == iter.discnumber).reduce(computeLowestTrack, undefined) - 1 || 0;
if (tnOffset) addMessage('Info: volume ' + iter.discnumber + ' track numbering not starting from 1', 'ua-info');
}
if (iter.discsubtitle != lastSubtitle) {
if (block != 1 || iter.discsubtitle) block1();
if (iter.discsubtitle) {
playlist += '[color=' + prefs.tracklist_work_color + '][size=2][b]' + iter.discsubtitle + '[/b][/size]';
duration = tracks.filter(it => it.discsubtitle == iter.discsubtitle)
.reduce((acc, it) => acc + it.duration, 0);
if (duration > 0) playlist += ' [size=1][i][' + makeTimeString(duration) + '][/i][/size]';
playlist += '[/color]';
}
lastSubtitle = iter.discsubtitle;
}
if (iter.classical_work != lastWork) {
if (iter.classical_work) {
block2();
playlist += '[color=' + prefs.tracklist_work_color + '][size=2][b]';
if (release.composers.length != 1 && classicalWorks.get(iter.classical_work).composer) {
playlist += classicalWorks.get(iter.classical_work).composer + ': ';
}
playlist += iter.classical_work;
playlist += '[/b]';
if (classicalWorks.get(iter.classical_work).performer
&& classicalWorks.get(iter.classical_work).performer != release.artist) {
playlist += ' (' + classicalWorks.get(iter.classical_work).performer + ')';
}
playlist += '[/size]';
duration = tracks.filter(it => it.classical_work == iter.classical_work)
.reduce((acc, it) => acc + it.duration, 0);
if (duration > 0) playlist += ' [size=1][i][' + makeTimeString(duration) + '][/i][/size]';
playlist += '[/color]';
} else {
if (block > 2) block1();
}
lastWork = iter.classical_work;
}
if (vinyltrackParser.test(iter.tracknumber)) {
if (block == 3 && lastSide && RegExp.$1 != lastSide) playlist += '\n';
lastSide = RegExp.$1;
}
block3();
} // prologue
} else { // single
playlist += '[align=center]';
playlist += isRED ? '[pad=20|20|20|20]' : '';
playlist += '[size=4][b][color=' + prefs.tracklist_artist_color + ']' + release.artist + '[/color]';
playlist += isRED ? '[hr]' : divs[0].repeat(32);
playlist += tracks[0].title + '[/b]';
if (tracks[0].composer) {
playlist += '\n[i][color=' + prefs.tracklist_composer_color + '](' + tracks[0].composer + ')[/color][/i]';
}
playlist += '\n\n[color=' + prefs.tracklist_duration_color +'][' + makeTimeString(tracks[0].duration) + '][/color][/size]';
if (isRED) playlist += '[/pad]';
playlist += '[/align]';
}
return playlist;
}
function genAlbumHeader() {
return !isVA && artists[0].length >= 3 ? '[size=4]' +
joinArtists(artists[0], artist => '[artist]' + artist + '[/artist]') + ' – ' + release.album + '[/size]\n\n' : '';
}
function findPreviousUploads() {
let search = new URLSearchParams(document.location.search);
if (search.get('groupid')) GM_xmlhttpRequest({
method: 'GET',
url: document.location.origin + '/torrents.php?action=grouplog&groupid=' + search.get('groupid'),
responseType: 'document',
onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
var dom = domParser.parseFromString(response.responseText, "text/html");
dom.querySelectorAll('table > tbody > tr.rowa').forEach(function(tr) {
if (/^\s*deleted\b/i.test(tr.children[3].textContent))
scanLog('Torrent ' + tr.children[1].firstChild.textContent);
});
},
}); else {
let query = '';
if (!isVA && artists[0].length >= 1 && artists[0].length <= 3) query = artists[0].join(', ') + ' - ';
query += release.album;
scanLog(query);
}
function scanLog(query) {
GM_xmlhttpRequest({
method: 'GET',
url: document.location.origin + '/log.php?search=' + encodeURIComponent(query),
responseType: 'document',
onload: function (response) {
if (response.readyState != 4 || response.status != 200) return;
var dom = domParser.parseFromString(response.responseText, "text/html");
dom.querySelectorAll('table > tbody > tr.rowb').forEach(function(tr) {
var size, msg = tr.children[1].textContent.trim();
if (/\b[\d\s]+(?:\.\d+)?\s*(?:([KMGT])I?)?B\b/.test(msg)) size = get_size_from_string(RegExp.lastMatch);
if (!msg.includes('deleted') || (/\[(.*)\/(.*)\/(.*)\]/.test(msg) ?
!release.codec || release.codec != RegExp.$1
//|| !release.encoding || release.encoding != RegExp.$2
|| !media || media != RegExp.$3 :
!size || !albumSize || Math.abs(albumSize / size - 1) >= 0.1)) return;
addMessage('Warning: possibly same release previously uploaded and deleted: ' + msg, 'ua-warning');
});
},
});
}
function get_size_from_string(str) {
var matches = /\b([\d\s]+(?:\.\d+)?)\s*(?:([KMGT])I?)?B\b/.exec(str.replace(',', '.').toUpperCase());
if (!matches) return null;
var size = parseFloat(matches[1].replace(/\s+/g, ''));
if (matches[2] == 'K') { size *= Math.pow(1024, 1) }
else if (matches[2] == 'M') { size *= Math.pow(1024, 2) }
else if (matches[2] == 'G') { size *= Math.pow(1024, 3) }
else if (matches[2] == 'T') { size *= Math.pow(1024, 4) }
return Math.round(size);
}
}
function getHomoIdentifier(id) {
id = id.toUpperCase();
return tracks.every(track => track.identifiers[id] && track.identifiers[id] == tracks[0].identifiers[id]) ?
tracks[0].identifiers[id] : null;
}
function fetchOnline_Music(url, weak = false) {
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, totalDiscs,
title, trackArtist, catalogue, encoding, format, bitrate, duration, country, genres = [], trs;
if (url.toLowerCase().includes('qobuz.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
var error = new Error('Error parsing Qobus release page'), mainArtist;
if ((ref = dom.querySelector('div.album-meta > h2.album-meta__artist')) == null) throw error;
artist = ref.title || ref.textContent.trim();
if ((ref = dom.querySelector('div.album-meta > h1.album-meta__title')) == null) throw error;
album = ref.title || ref.textContent.trim();
ref = dom.querySelector('div.album-meta > ul > li:first-of-type');
if (ref != null) releaseDate = normalizeDate(ref.textContent);
ref = dom.querySelector('div.album-meta > ul > li:nth-of-type(2) > a');
if (ref != null) mainArtist = ref.title || ref.textContent.trim();
ref = dom.querySelector('p.album-about__copyright');
albumYear = ref != null && extractYear(ref.textContent) || extractYear(releaseDate);
dom.querySelectorAll('section#about > ul > li').forEach(function(it) {
function matchLabel(lbl) { return it.textContent.trimLeft().startsWith(lbl) }
if (/\b(\d+)\s*(?:dis[ck]|disco|disque)/i.test(it.textContent)) totalDiscs = parseInt(RegExp.$1);
if (/\b(\d+)\s*(?:track|pist[ae]|tracce|traccia)/i.test(it.textContent)) totalTracks = parseInt(RegExp.$1);
if (['Label', 'Etichetta', 'Sello'].some(l => it.textContent.trimLeft().startsWith(l))) label = it.children[0].textContent.trim()
else if (['Composer', 'Compositeur', 'Komponist', 'Compositore', 'Compositor'].some(matchLabel)) {
composer = it.children[0].textContent.trim();
if (/\bVarious\b/i.test(composer)) composer = null;
} else if (['Genre', 'Genere', 'Género'].some(g => it.textContent.startsWith(g)) && it.children.length > 0) {
genres = Array.from(it.querySelectorAll('a')).map(elem => elem.textContent.trim());
/*
if (genres.length >= 1 && ['Pop/Rock'].includes(genres[0])) genres.shift();
if (genres.length >= 2 && ['Alternative & Indie'].includes(genres[genres.length - 1])) genres.shift();
if (genres.length >= 1 && ['Metal', 'Heavy Metal'].some(genre => genres.includes(genre))) {
while (genres.length > 1) genres.shift();
}
*/
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(',', '.'));
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);
});
getDescFromNode('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]';
}
trs = dom.querySelectorAll('div.player__tracks > div.track > div.track__items');
var works = dom.querySelectorAll('div.player__item > p.player__work');
if (!totalTracks) totalTracks = trs.length;
trs.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;
tracks.push([
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,
undefined,
response.finalUrl,
undefined,
description,
undefined,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_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) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
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();
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
getDescFromNode('div#albumtab-info > p', response.finalUrl);
trs = dom.querySelectorAll('ul.playlist > li.pltrack');
totalTracks = trs.length;
trs.forEach(function(tr) {
discSubtitle = tr;
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(tr.querySelector('span.track').textContent.trim());
title = tr.querySelector('span.title').textContent.trim().replace(/\s+/g, ' ');
duration = timeStringToTime(tr.querySelector('span.time').textContent);
trackArtist = undefined;
tracks.push([
artist,
album,
albumYear,
releaseDate,
label,
undefined, // catalogue
undefined, // country
'lossless',
'FLAC', // format
undefined,
undefined,
24,
sr * 1000,
undefined, // channels
'WEB',
genres.join('; '),
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
undefined,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_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) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
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('span.back-link-text > br');
if (ref != null && ref.nextSibling != null) label = ref.nextSibling.textContent.trim(); else {
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(function(tag) {
if ([
artist,
].every(t => tag.textContent.trim().toLowerCase() != t.toLowerCase())) tags.add(tag.textContent.trim());
});
description = [];
dom.querySelectorAll('div.tralbumData').forEach(function(div) {
if (!div.classList.contains('tralbum-tags')) description.push(html2php(div, response.finalUrl).trim());
});
description = description.filter(p => p).join('\n\n').flatten();
trs = dom.querySelectorAll('table.track_list > tbody > tr[itemprop="tracks"]');
totalTracks = trs.length;
trs.forEach(function(tr) {
trackNumber = parseInt(tr.querySelector('div.track_number').textContent);
title = (tr.querySelector('div.title span.track-title')
|| tr.querySelector('div.title span[itemprop="name"]')).textContent.trim().replace(/\s+/g, ' ');
ref = tr.querySelector('span.time');
duration = ref != null ? timeStringToTime(ref.textContent) : undefined;
trackArtist = undefined;
tracks.push([
artist,
album,
albumYear,
releaseDate,
label,
undefined, // catalogue
undefined, // country
undefined, //'lossless',
undefined, //'FLAC',
undefined,
undefined,
undefined,
undefined,
undefined, // channels
'WEB',
tags.toString(),
discNumber,
totalDiscs,
undefined,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
undefined,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_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) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
artist = getArtists(dom.querySelectorAll('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 = extractYear(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;
genres = undefined;
if (/\/jazz\//i.test(response.finalUrl)) genres = 'Jazz';
if (/\/classical\//i.test(response.finalUrl)) genres = 'Classical';
getDescFromNode('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;
trs = dom.querySelectorAll('div.has--sample');
totalTracks = trs.length;
trackNumber = 0;
trs.forEach(function(tr) {
trackNumber = ++trackNumber;
title = tr.querySelector('p.c-track__title').textContent.trim().replace(/\s+/g, ' ');
duration = timeStringToTime(tr.querySelector('div.c-track__duration').textContent);
let parent = tr;
if (tr.classList.contains('c-track')) {
parent = tr.parentNode.parentNode;
if (parent.classList.contains('c-expander')) parent = parent.parentNode;
discSubtitle = parent.querySelector(':scope > div > div > div > p.c-track__title');
discSubtitle = discSubtitle != null ? discSubtitle.textContent.trim().replace(/\s+/g, ' ') : undefined;
} else {
discSubtitle = null;
}
trackArtist = getArtists(parent.querySelectorAll(':scope > div.c-track__details > ul > li'));
if (trackArtist.equalTo(artist)) trackArtist = [];
tracks.push([
artist.join('; '),
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
undefined, // encoding
undefined, // format
undefined,
undefined, // bitrate
undefined, // BD
undefined, // SR
undefined, // channels
'WEB',
genres,
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist.join(', '),
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
undefined,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
function getArtists(nodeList) {
var artists = [];
nodeList.forEach(function(it) {
if (it.textContent.startsWith('Record')) return;
splitArtists(it.textContent.trim()).forEach(function(it) {
artists.push(it.replace(/\s*\([^\(\)]*\)$/, ''));
});
});
return artists;
}
} });
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,
responseType: 'json',
onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
var json = response.response;
const removeArtistNdx = /\s*\(\d+\)$/;
function getArtists(root) {
function filterArtists(rx, anv = true) {
return Array.isArray(root.extraartists) && 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(' / ') + ')';
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 = '[url=https://www.discogs.com/label/' + it.id + ']' +
it.name.replace(removeArtistNdx, '') + '[/url]';
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 = '[url=https://www.discogs.com/artist/' + it.id + ']' +
(it.anv || it.name).replace(removeArtistNdx, '') + '[/url]';
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;
});
}
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)|Remasterizado|Remasterisée|Reissue|.+?\s+Release|Enhanced|Promo)$/.test(it)) {
editions.push(it);
}
});
if (media) return;
if (it.name.includes('File')) {
if (['FLAC', 'WAV', 'AIF', 'AIFF', 'PCM'].some(k => it.descriptions.includes(k))) {
media = 'WEB'; encoding = 'lossless'; format = 'FLAC';
} else if (it.descriptions.includes('AAC')) {
media = 'WEB'; encoding = 'lossy'; format = 'AAC'; bd = undefined;
if (/(\d+)\s*kbps\b/i.test(it.text)) bitrate = parseInt(RegExp.$1);
} else if (it.descriptions.includes('MP3')) {
media = 'WEB'; encoding = 'lossy'; format = 'MP3'; bd = undefined;
if (/(\d+)\s*kbps\b/i.test(it.text)) bitrate = parseInt(RegExp.$1);
}
} else if (['CD', 'DVD', 'Vinyl', 'LP', '7"', '12"', '10"', '5"', 'SACD', 'Hybrid', 'Blu',
'Cassette','Cartridge', 'Laserdisc', 'VCD'].some(k => it.name.includes(k))) media = it.name;
});
if (json.master_url) {
var yearWritable = elementWritable(document.getElementById('year'));
var tagsWritable = elementWritable(document.getElementById('tags'));
if (yearWritable || tagsWritable) GM_xmlhttpRequest({
method: 'GET',
url: json.master_url,
responseType: 'json',
onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
if (yearWritable && (albumYear = response.response.year) > 0) {
document.getElementById('year').value = albumYear;
}
if (tagsWritable) {
var tags = new TagManager();
if (json.genres) tags.add(...json.genres);
if (json.styles) tags.add(...json.styles);
if (response.response.genres) tags.add(...response.response.genres);
if (response.response.styles) tags.add(...response.response.styles);
if (tags.length > 0) document.getElementById('tags').value = tags.toString();
}
},
});
}
json.tracklist.forEach(function(track) {
if (track.type_.toLowerCase() == 'heading') {
discSubtitle = track.title;
} else if (track.type_.toLowerCase() == 'track') {
if (/^([a-zA-Z]+)?(\d+)-(\w+)$/.test(track.position)) {
if (RegExp.$1) identifiers.push('VOL_MEDIA=' + RegExp.$1.replace(/\s/g, '\x1B'));
discNumber = RegExp.$2;
trackNumber = RegExp.$3;
} else {
discNumber = undefined;
trackNumber = track.position;
}
let trackArtists = getArtists(track);
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;
}
let performer = Array.isArray(track.extraartists) && track.extraartists
.map(artist => (artist.anv || artist.name).replace(removeArtistNdx, ''))
.filter(function(artist) {
return !albumArtists.slice(2).some(it => Array.isArray(it) && it.includes(artist))
&& !trackArtists.slice(2).some(it => Array.isArray(it) && it.includes(artist))
});
tracks.push([
artist,
album,
undefined, //json.year,
json.released,
label.join(' / '),
catalogue.join(' / '),
json.country,
encoding,
format,
undefined,
bitrate,
bd,
undefined, // samplerate
undefined, // channels
media,
(json.genres ? json.genres.join('; ') : '') + (json.styles ? ' | ' + json.styles.join('; ') : ''),
discNumber,
json.format_quantity,
discSubtitle,
trackNumber,
json.tracklist.length,
track.title,
trackArtist,
Array.isArray(performer) && performer.join('; ') || undefined,
stringyfyRole(3), // composers
stringyfyRole(4), // conductors
stringyfyRole(2), // remixers
stringyfyRole(5), // DJs/compilers
stringyfyRole(6), // producers
timeStringToTime(track.duration),
undefined,
undefined,
undefined,
undefined,
undefined,
undefined, //'https://www.discogs.com/release/' + json.id,
undefined,
description.flatten(),
identifiers.join(' '),
].join('\x1E'));
function stringyfyRole(ndx) {
return (Array.isArray(trackArtists[ndx]) && trackArtists[ndx].length > 0 ?
trackArtists : albumArtists)[ndx].join('; ');
}
}
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
},
});
return true;
} else if (url.toLowerCase().includes('supraphonline.cz')) {
GM_xmlhttpRequest({ method: 'GET', url: url.replace(/\?.*$/, ''), onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
const copyrightParser = /^(?:\([PC]\)|℗|©)$/i;
var ndx, conductor = [], origin = new URL(response.finalUrl).origin;
genres = undefined; artist = [];
dom.querySelectorAll('h2.album-artist > a').forEach(function(it) {
artist.pushUnique(it.title);
});
isVA = false;
if (artist.length == 0 && (ref = dom.querySelector('h2.album-artist[title]')) != null) {
if (vaParser.test(ref.title)) isVA = true;
}
ref = dom.querySelector('span[itemprop="byArtist"] > meta[itemprop="name"]');
if (ref != null && vaParser.test(ref.content)) isVA = true;
if (isVA) artist = [];
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) genres = ref.content;
if ((ref = dom.querySelector('li.album-version > div.selected > div')) != null) {
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; }
if (/\b(?:CD)\b/.test(ref.textContent)) { media = 'CD'; }
if (/\b(?:LP)\b/.test(ref.textContent)) { media = 'Vinyl'; }
}
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('První vydání')) albumYear = extractYear(it.lastChild.data);
//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 (copyrightParser.test(it.children[0].textContent) && !albumYear) albumYear = extractYear(it.lastChild.data);
});
const creators = ['autoři', 'interpreti', 'tělesa', 'digitalizace'];
artists = [];
for (i = 0; i < 4; ++i) artists[i] = {};
dom.querySelectorAll('ul.sidebar-artist > li').forEach(function(it) {
if ((ref = it.querySelector('h3')) != null) {
ndx = undefined;
creators.forEach((it, _ndx) => { if (ref.textContent.includes(it)) ndx = _ndx });
} else {
if (typeof ndx != 'number') return;
let role;
if (ndx == 2) role = 'ensemble';
else if ((ref = it.querySelector('span')) != null) role = translateRole(ref);
if ((ref = it.querySelector('a')) != null) {
if (!Array.isArray(artists[ndx][role])) artists[ndx][role] = [];
var href = new URL(ref.href);
artists[ndx][role].pushUnique([ref.textContent.trim(), origin + href.pathname]);
}
}
});
getDescFromNode('div[itemprop="description"] p', response.finalUrl, true);
composer = [];
var performers = [], DJs = [];
function dumpArtist(ndx, role) {
if (!role || role == 'undefined') return;
if (description.length > 0) description += '\x1C' ;
description += '[color=#9576b1]' + role + '[/color] – ';
//description += artists[ndx][role].map(artist => '[artist]' + artist[0] + '[/artist]').join(', ');
description += artists[ndx][role].map(artist => '[url=' + artist[1] + ']' + artist[0] + '[/url]').join(', ');
}
for (i = 1; i < 3; ++i) Object.keys(artists[i]).forEach(function(role) { // performers
var a = artists[i][role].map(a => a[0]);
artist.pushUnique(...a);
(['conductor', 'choirmaster'].includes(role) ? conductor : role == 'DJ' ? DJs : performers).pushUnique(...a);
if (i != 2) dumpArtist(i, role);
});
Object.keys(artists[0]).forEach(function(role) { // composers
composer.pushUnique(...artists[0][role].map(it => it[0])
.filter(it => !pseudoArtistParsers.some(rx => rx.test(it))));
dumpArtist(0, role);
});
Object.keys(artists[3]).forEach(role => { dumpArtist(3, role) }); // ADC & mastering
var promises = [];
dom.querySelectorAll('table.table-tracklist > tbody > tr').forEach(function(row) {
promises.push(row.id && (ref = row.querySelector('td > a.trackdetail')) != null ? new Promise(function(resolve, reject) {
var id = parseInt(row.id.replace(/^track-/i, ''));
GM_xmlhttpRequest({
method: 'GET',
url: origin + ref.pathname + ref.search,
context: id,
onload: function(response) {
if (response.readyState == 4 || response.status == 200) {
var domDetail = domParser.parseFromString(response.responseText, 'text/html');
var track = domDetail.getElementById('track-' + response.context);
if (track != null) {
resolve([track, domDetail.querySelector('div[data-swap="trackdetail-' + response.context + '"] > div > div.row')]);
} else reject('Track detail not located');
} else {
reject('Response error ' + response.status + ' (' + response.statusText + ')');
}
},
onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
ontimeout: function() { reject('Timeout') },
});
}) : Promise.resolve([row, null]));
});
Promise.all(promises).then(function(rows) {
rows.forEach(function(tr) {
if (!(tr[0] instanceof HTMLElement)) throw new Error('Assertion failed: tr[0] != HTMLElement');
if (tr[0].id && tr[0].classList.contains('track')) {
tr[2] = [];
for (i = 0; i < 8; ++i) tr[2][i] = [];
if (!(tr[1] instanceof HTMLElement)) return;
tr[1].querySelectorAll('div[class]:nth-of-type(2) > ul > li > span').forEach(function(li) {
function oneOf(...arr) { return arr.some(role => key == role) }
var key = translateRole(li);
var val = li.nextElementSibling.textContent.trim();
if (pseudoArtistParsers.some(rx => rx.test(val))) return;
if (key.startsWith('remix')) {
tr[2][2].pushUnique(val);
} else if (oneOf('music', 'lyrics', 'music+lyrics', 'original lyrics', 'czech lyrics', 'libreto', 'music improvisation', 'author')) {
tr[2][3].pushUnique(val);
} else if (oneOf('conductor', 'choirmaster')) {
tr[2][4].pushUnique(val);
} else if (key == 'DJ') {
tr[2][5].pushUnique(val);
} else if (key == 'produced by') {
tr[2][6].pushUnique(val);
} else if (key == 'recorded by') {
} else {
tr[2][7].pushUnique(val);
}
});
}
});
var guests = rows.filter(tr => tr.length >= 3).map(it => it[2][7])
.reduce((acc, trpf) => trpf.filter(trpf => acc.includes(trpf)))
.filter(it => !artist.includes(it));
rows.forEach(function(tr) {
if (tr[0].classList.contains('cd-header')) {
discNumber = /\b\d+\b/.test(tr[0].querySelector('h3').firstChild.data.trim())
&& parseInt(RegExp.lastMatch) || undefined;
}
if (tr[0].classList.contains('song-header')) {
discSubtitle = tr[0].children[0].title.trim() || undefined;
}
if (tr[0].id && tr[0].classList.contains('track')) {
var copyright, trackGenre, trackYear, recordPlace, recordDate, identifiers = '';
if (/^track-(\d+)$/i.test(tr[0].id)) identifiers = 'TRACK_ID=' + RegExp.$1;
trackNumber = /^\s*(\d+)\.?\s*$/.test(tr[0].children[0].firstChild.textContent) ?
parseInt(RegExp.$1) : undefined;
title = tr[0].querySelector('meta[itemprop="name"]').content;
duration = durationFromMeta(tr[0]);
if (tr[1] instanceof HTMLElement) {
tr[1].querySelectorAll('div[class]:nth-of-type(1) > ul > li > span').forEach(function(li) {
if (li.textContent.startsWith('Nahrávka dokončena')) {
identifiers += ' RECYEAR=' + extractYear(recordDate = li.nextSibling.data.trim());
}
if (li.textContent.startsWith('Místo nahrání')) {
recordPlace = li.nextSibling.data.trim();
}
if (li.textContent.startsWith('Rok prvního vydání')) {
identifiers += ' PUBYEAR=' + (trackYear = parseInt(li.nextSibling.data));
}
//if (copyrightParser.test(li.textContent)) copyright = li.nextSibling.data.trim();
if (li.textContent.startsWith('Žánr')) trackGenre = li.nextSibling.data.trim();
});
}
if (!isVA && tr[2][0].equalTo(artist)) tr[2][0] = [];
tracks.push([
isVA ? VA : artist.join('; '),
album,
/*trackYear || */albumYear || undefined,
releaseDate,
label,
catalogue,
undefined, // country
encoding,
format,
undefined,
undefined,
bd,
sr * 1000,
undefined, // channels
media,
translateGenre(genres) + ' | ' + translateGenre(trackGenre),
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
joinArtists(tr[2][0]),
tr[2][7].join('; ') || performers.join('; '),
tr[2][3].join(', ') || composer.join(', '),
tr[2][4].join('; ') || conductor.join('; '),
tr[2][2].join('; '),
tr[2][5].join('; ') || DJs.join('; '),
tr[2][6].join('; '),
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
identifiers,
].join('\x1E'));
}
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
}).catch(e => { alert(e) });
function translateGenre(genre) {
if (!genre || typeof genre != 'string') return undefined;
[
['Orchestrální hudba', 'Orchestral Music'],
['Komorní hudba', 'Chamber Music'],
['Vokální', 'Classical, Vocal'],
['Klasická hudba', 'Classical'],
['Melodram', 'Classical, Melodram'],
['Symfonie', 'Symphony'],
['Vánoční hudba', 'Christmas Music'],
[/^(?:Alternativ(?:ní|a))$/i, 'Alternative'],
['Dechová hudba', 'Brass Music'],
['Elektronika', 'Electronic'],
['Folklor', 'Folclore, World Music'],
['Instrumentální hudba', 'Instrumental'],
['Latinské rytmy', 'Latin'],
['Meditační hudba', 'Meditative'],
['Vojenská hudba', 'Military Music'],
['Pro děti', 'Children'],
['Pro dospělé', 'Adult'],
['Mluvené slovo', 'Spoken Word'],
['Audiokniha', 'audiobook'],
['Humor', 'humour'],
['Pohádka', 'Fairy-Tale'],
].forEach(function(subst) {
if (typeof subst[0] == 'string' && genre.toLowerCase() == subst[0].toLowerCase()
|| subst[0] instanceof RegExp && subst[0].test(genre)) genre = subst[1];
});
return genre;
}
function translateRole(elem) {
if (!(elem instanceof HTMLElement)) return undefined;
var role = elem.textContent.trim().toLowerCase().replace(/\s*:.*$/, '');
[
[/\b(?:klavír)\b/, 'piano'],
[/\b(?:housle)\b/, 'violin'],
[/\b(?:varhany)\b/, 'organ'],
[/\b(?:cembalo)\b/, 'harpsichord'],
[/\b(?:trubka)\b/, 'trumpet'],
[/\b(?:soprán)\b/, 'soprano'],
[/\b(?:alt)\b/, 'alto'],
[/\b(?:baryton)\b/, 'baritone'],
[/\b(?:bas)\b/, 'basso'],
[/\b(?:syntezátor)\b/, 'synthesizer'],
[/\b(?:zpěv)\b/, 'vocals'],
[/^(?:čte|četba)$/, 'narration'],
['vypravuje', 'narration'],
['komentář', 'commentary'],
['hovoří', 'spoken by'],
['hovoří a zpívá', 'speaks and sings'],
['improvizace', 'improvisation'],
['hudební těleso', 'ensemble'],
['hudba', 'music'],
['text', 'lyrics'],
['hudba+text', 'music+lyrics'],
['původní text', 'original lyrics'],
['český text', 'czech lyrics'],
['hudební improvizace', 'music improvisation'],
['autor', 'author'],
['účinkuje', 'participating'],
['řídí', 'conductor'],
['dirigent', 'conductor'],
['sbormistr', 'choirmaster'],
['produkce', 'produced by'],
['nahrál', 'recorded by'],
['digitální přepis', 'A/D transfer'],
].forEach(function(subst) {
if (typeof subst[0] == 'string' && role.toLowerCase() == subst[0].toLowerCase()
|| subst[0] instanceof RegExp && subst[0].test(role)) role = role.replace(subst[0], subst[1]);
});
return role;
}
} });
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) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
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 = extractYear(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();
}
});
getDescFromNode('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, response.finalUrl).trim().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;
tracks.push([
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,
undefined,
response.finalUrl,
undefined,
description,
EAN,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
} });
return true;
} else if (url.toLowerCase().includes('nativedsd.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
var NDSD_ID = 'ORIGINALFORMAT=DSD', genre;
ref = dom.querySelector('div.the-content > header > h2');
if (ref != null) artist = ref.firstChild.data.trim();
ref = dom.querySelector('div.the-content > header > h1');
if (ref != null) album = ref.firstChild.data.trim();
ref = dom.querySelector('div.the-content > header > h3');
if (ref != null) composer = ref.firstChild.data.trim();
ref = dom.querySelector('div.the-content > header > h1 > small');
if (ref != null) albumYear = extractYear(ref.firstChild.data);
releaseDate = albumYear; // weak
ref = dom.querySelector('div#breadcrumbs > div[class] > a:nth-of-type(2)');
if (ref != null) label = ref.firstChild.data.trim();
if (label == 'Albums') label = undefined;
if ((ref = dom.querySelector('h2#sku')) != null) {
if (/^Catalog Number: (.*)$/m.test(ref.firstChild.textContent)) catalogue = RegExp.$1;
if (/^ID: (.*)$/m.test(ref.lastChild.textContent)) NDSD_ID += ' NATIVEDSD_ID=' + RegExp.$1;
}
getDescFromNode('div.the-content > div.entry > p', response.finalUrl, false);
ref = dom.querySelector('div#repertoire > div > p');
if (ref != null) {
let repertoire = html2php(ref, url);
if (description) description += '\x1C\x1C';
let ndx = repertoire.indexOf('\n[b]Track');
description += (ndx >= 0 ? repertoire.slice(0, ndx) : repertoire).trim().flatten();
}
ref = dom.querySelectorAll('div#techspecs > table > tbody > tr');
if (ref.length > 0) {
if (description) description += '\x1C\x1C';
description += '[b][u]Tech specs[/u][/b]';
ref.forEach(function(it) {
description += '\n[b]'.concat(it.children[0].textContent.trim(), '[/b] ',
it.children[1].textContent.trim()).replace(/\n/g, '\x1C').replace(/\r/g, '\x1D');
});
}
trs = dom.querySelectorAll('div#track-list > table > tbody > tr[id^="track"]');
totalTracks = trs.length;
trs.forEach(function(tr) {
if ((ref = tr.children[0].children[0]) != null) {
trackNumber = parseInt(ref.firstChild.data.trim().replace(/\..*$/, ''));
}
let trackComposer;
if ((ref = tr.children[1]) != null) {
title = ref.firstChild.textContent.trim();
trackComposer = ref.childNodes[2] && ref.childNodes[2].textContent.trim() || undefined;
}
if ((ref = tr.children[2]) != null) duration = timeStringToTime(ref.firstChild.data);
tracks.push([
artist,
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
'lossless', // encoding
'FLAC', // format
undefined,
undefined, // bitrate
24, // bd
88200,
undefined, // channels
'WEB',
genre, // 'Jazz'
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
trackComposer || composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
NDSD_ID + ' TRACK_ID=' + tr.id.replace(/^track-/i, ''),
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
function getArtists(elem) {
if (elem == null) return undefined;
var artists = [];
splitArtists(elem.textContent.trim()).forEach(function(it) {
artists.push(it.replace(/\s*\([^\(\)]*\)$/, ''));
});
return artists.join(', ');
}
} });
return true;
} else if (url.toLowerCase().includes('junodownload.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
var ID = '';
if (/\/([\d\-]+)\/?$/.test(response.finalUrl)) ID = 'JUNODOWNLOAD_ID=' + RegExp.$1 + ' ';
if ((ref = dom.querySelector('h2.product-artist > a')) != null) artist = titleCase(ref.firstChild.data.trim());
if ((ref = dom.querySelector('meta[itemprop="name"]')) != null) album = ref.content;
if ((ref = dom.querySelector('meta[itemprop="author"]')) != null) label = ref.content;
if ((ref = dom.querySelector('span[itemprop="datePublished"]')) != null) releaseDate = ref.firstChild.data.trim();
dom.querySelectorAll('div.mb-3 > strong').forEach(function(it) {
if (it.textContent.startsWith('Genre')) {
ref = it;
while ((ref = ref.nextElementSibling) != null && ref.nodeName == 'A') genres.push(ref.textContent.trim());
} else if (it.textContent.startsWith('Cat')) {
if ((ref = it.nextSibling) != null && ref.nodeType == 3) catalogue = ref.data;
}
});
trs = dom.querySelectorAll('div.product-tracklist > div[itemprop="track"]');
totalTracks = trs.length;
trs.forEach(function(tr) {
trackNumber = undefined;
tr.querySelector('div.track-title').childNodes.forEach(function(n) {
if (trackNumber || n.nodeType != 3) return;
trackNumber = n.data.trim().replace(/\s*\..*$/, '');
});
title = tr.querySelector('span[itemprop="name"]').textContent.trim();
ref = tr.querySelector('meta[itemprop="byArtist"]');
trackArtist = ref != null ? ref.content : undefined;
if (trackArtist && trackArtist == artist) trackArtist = undefined;
duration = durationFromMeta(tr);
tracks.push([
artist,
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
undefined, // encoding
undefined, // format
undefined,
undefined, // bitrate
undefined, // bd
undefined, // SR
undefined, // channels
'WEB',
genres.join('; '),
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
ID ? undefined : response.finalUrl,
undefined,
undefined, // description
ID + 'BPM=' + tr.children[2].textContent.trim(),
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
} });
return true;
} else if (url.toLowerCase().includes('hdtracks.com') || url.toLowerCase().includes('hdtracks.co.uk')
|| url.toLowerCase().includes('hdtracks.de')) {
GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
dom.querySelectorAll('div.album-main-details > ul > li > span').forEach(function(it) {
if (it.textContent.startsWith('Title')) album = it.nextSibling.data.trim();
if (it.textContent.startsWith('Artist')) artist = it.nextElementSibling.textContent.trim();
if (it.textContent.startsWith('Genre')) {
ref = it;
while ((ref = ref.nextElementSibling) != null) genres.push(ref.textContent.trim());
}
if (it.textContent.startsWith('Label')) label = it.nextElementSibling.textContent.trim();
if (it.textContent.startsWith('Release Date')) releaseDate = normalizeDate(it.nextSibling.data.trim());
});
if (!albumYear) albumYear = extractYear(releaseDate);
trs = dom.querySelectorAll('table#track-table > tbody > tr[id^="track"]');
totalTracks = trs.length;
trs.forEach(function(tr) {
trackNumber = parseInt(tr.querySelector('td:first-of-type').textContent.trim());
title = tr.querySelector('td.track-name').textContent.trim();
duration = timeStringToTime(tr.querySelector('td:nth-of-type(3)').textContent.trim());
format = tr.querySelector('td:nth-of-type(4) > span').textContent.trim();
sr = tr.querySelector('td:nth-of-type(5)').textContent.trim().replace(/\/.*/, '');
if (/^([\d\.\,]+)\s*\/\s*(\d+)$/.test(sr)) {
sr = Math.round(parseFloat(RegExp.$1.replace(',', '.')) * 1000);
bd = parseInt(RegExp.$2);
} else sr = Math.round(parseFloat(sr) * 1000);
tracks.push([
artist,
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
'lossless',
undefined, // format
undefined,
undefined, // bitrate
bd || 24,
sr || undefined,
undefined, // channels
'WEB',
genres.join('; '),
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
undefined, // description
undefined,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
} });
return true;
} else if (/^https?:\/\/(?:\w+\.)?deezer\.com\/(?:\w+\/)*album\/(\d+)/i.test(url)) {
GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.deezer.com/album/' + RegExp.$1,
responseType: 'json',
onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
var json = response.response;
isVA = vaParser.test(json.artist.name);
var identifiers = 'DEEZER_ID=' + json.id + ' RELEASETYPE=' + json.record_type;
json.tracks.data.forEach(function(track, ndx) {
trackArtist = track.artist.name;
if (!isVA && trackArtist && trackArtist == json.artist.name) trackArtist = undefined;
tracks.push([
isVA ? VA : json.artist.name,
json.title,
undefined, //extractYear(json.release_date),
json.release_date,
json.label,
json.upc,
undefined, // country
undefined, // encoding
undefined, // format
undefined,
undefined, // bitrate
undefined, // bd
undefined, // SR
undefined, // channels
'WEB',
json.genres.data.map(it => it.name).join('; '),
discNumber,
totalDiscs,
discSubtitle,
ndx + 1,
json.nb_tracks,
track.title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
track.duration,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined, //'https://www.deezer.com/album/' + json.id,
undefined,
undefined, // description
identifiers + ' TRACK_ID=' + track.id,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
},
});
return true;
} else if (/^https?:\/\/(?:\w+\.)?spotify\.com\/(?:\w+\/)*albums?\/(\w+)/i.test(url)) {
querySpotifyAPI('https://api.spotify.com/v1/albums/' + RegExp.$1).then(function(json) {
isVA = json.artists.length == 1 && vaParser.test(json.artists[0].name);
artist = json.artists.map(artist => artist.name);
totalDiscs = json.tracks.items.reduce((acc, track) => Math.max(acc, track.disc_number), 0);
var identifiers = 'RELEASETYPE=' + json.album_type + ' SPOTIFY_ID=' + json.id;
var image = json.images.reduce((acc, image) => image.width * image.height > acc.width * acc.height ? image : acc);
//if (image) identifiers += ' IMGURL=' + image.url;
json.tracks.items.forEach(function(track, ndx) {
trackArtist = track.artists.map(artist => artist.name);
if (!isVA && json.artists.length > 0 && trackArtist.equalTo(artist)) trackArtist = [];
tracks.push([
isVA ? VA : joinArtists(artist),
json.name,
undefined, //extractYear(json.release_date),
json.release_date,
json.label,
json.external_ids.upc,
undefined, // country
undefined, // encoding
undefined, // format
undefined,
undefined, // bitrate
undefined, // BD
undefined, // SR
undefined, // channels
'WEB',
json.genres.join('; '),
totalDiscs > 1 ? track.disc_number : undefined,
totalDiscs > 1 ? totalDiscs : undefined,
undefined, // discSubtitle
track.track_number,
json.total_tracks,
track.name,
joinArtists(trackArtist),
undefined,
composer,
undefined,
undefined,
compiler,
producer,
track.duration_ms / 1000,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined, //'https://open.spotify.com/album/' + json.id,
undefined,
undefined, // description
identifiers +
' EXPLICIT=' + Number(track.explicit) +
' TRACK_ID=' + track.id,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
}).catch(e => { alert(e) });
return true;
function querySpotifyAPI(api_url) {
if (!api_url) return Promise.reject('No API URL');
return setToken().then(function(credentials) {
return new Promise(function(resolve, reject) {
GM_xmlhttpRequest({
method: 'GET',
url: api_url,
headers: {
'Accept': 'application/json',
'Authorization': credentials.token_type + ' ' + credentials.access_token,
},
responseType: 'json',
onload: function(response) {
if (response.readyState == 4 && response.status == 200) resolve(response.response);
else reject('GM_xmlhttpRequest(' + response.finalUrl + '): readyState=' + response.readyState + ', status=' + response.status);
},
onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
ontimeout: function() { reject('Timeout') },
});
});
});
}
function setToken() {
if (isTokenValid()) return Promise.resolve(spotifyCredentials);
if (!prefs.spotify_clientid || !prefs.spotify_clientsecret) return Promise.reject('Spotify credentials not set');
return new Promise(function(resolve, reject) {
const data = new URLSearchParams({
grant_type: 'client_credentials',
});
GM_xmlhttpRequest({
method: 'POST',
url: 'https://accounts.spotify.com/api/token',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': data.toString().length,
'Authorization': 'Basic ' + btoa(prefs.spotify_clientid + ':' + prefs.spotify_clientsecret),
},
responseType: 'json',
data: data.toString(),
onload: function(response) {
if (response.readyState == 4 && response.status == 200) {
spotifyCredentials = response.response;
spotifyCredentials.expires = new Date().getTime() + spotifyCredentials.expires_in;
if (isTokenValid()) resolve(spotifyCredentials); else reject('Invalid token');
} else reject('Response error ' + response.status + ' (' + JSON.parse(response.response).error + ')');
},
onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
ontimeout: function() { reject('Timeout') },
});
});
}
function isTokenValid() {
return spotifyCredentials.token_type && spotifyCredentials.token_type.toLowerCase() == 'bearer'
&& spotifyCredentials.access_token && spotifyCredentials.expires >= new Date().getTime() + 30;
}
} else if (url.toLowerCase().includes('prostudiomasters.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'document', onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
var ID = '';
if (/\/page\/(\d+)$/i.test(response.finalUrl)) ID = 'PROSTUDIOMASTERS_ID=' + RegExp.$1 + ' ';
artist = Array.from(dom.querySelectorAll('h2.ArtistName > a')).map(node => node.textContent.trim());
if ((ref = dom.querySelector('h3.AlbumName')) != null) album = ref.textContent.trim();
if ((ref = dom.querySelector('div.pline')) != null
&& /^(?:[℗©]\s*)+(\d{4})\s+(.+)/.test(ref.textContent.trim())) {
releaseDate = RegExp.$1;
label = RegExp.$2;
}
getDescFromNode('div.album-info', response.finalUrl, false);
//albumYear = extractYear(releaseDate);
trs = dom.querySelectorAll('div.album-tracks > div.tracks > table > tbody > tr');
totalTracks = Array.from(trs).filter(tr => tr.classList.contains('track-playable')).length;
trs.forEach(function(tr) {
if (tr.classList.contains('track-playable')) {
trackArtist = undefined; sr = undefined; bd = undefined; format = undefined;
title = undefined; trackNumber = undefined; duration = undefined;
var trackId = tr.getAttribute('data-track-id');
if (trackId) trackId = 'TRACK_ID=' + trackId;
if ((ref = tr.querySelector('div.num')) != null) trackNumber = parseInt(ref.firstChild.textContent.trim());
if ((ref = tr.querySelector('td.track-name > div.name')) != null) {
title = ref.firstChild.textContent.trim();
if ((ref = ref.querySelector(':scope small')) != null) {
trackArtist = splitArtists(ref.firstChild.textContent);
trackArtist = trackArtist.equalTo(artist) ? undefined : joinArtists(trackArtist);
}
}
if ((ref = tr.querySelector('td:last-of-type')) != null) duration = timeStringToTime(ref.firstChild.data);
if ((ref = tr.querySelector('span.track-format')) != null && /^(\d+(?:[,\.]\d+)?)\s*([kM]Hz)(?:\s+(\d+)-bit)?\s*\|\s*(\S+)$/i.test(ref.textContent.trim())) {
sr = parseFloat(RegExp.$1);
if (RegExp.$2 == 'kHz') sr *= 1024;
if (RegExp.$2 == 'MHz') sr *= 1024**2;
sr = Math.round(sr);
bd = parseInt(RegExp.$3) || undefined;
format = RegExp.$4;
}
tracks.push([
artist.join('; '),
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
undefined, //'lossless', // encoding
format,
undefined,
undefined, // bitrate
bd,
sr,
undefined, // channels
'WEB',
undefined, // genre
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
ID ? undefined : response.finalUrl,
undefined,
description,
ID.concat(trackId).trim(),
].join('\x1E'));
} else if ((ref = tr.querySelector('div.grouping-title')) != null) {
discSubtitle = ref.textContent.trim();
}
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
} });
return true;
/*
} else if (url.toLowerCase().includes('soundcloud.com') && prefs.soundcloud_clientid) {
// SC.initialize({
// client_id: prefs.soundcloud_clientid,
// redirect_uri: 'https://dont.spam.me/',
// });
SC.connect().then(function() { return SC.resolve(url) }).then(function(json) {
isVA = vaParser.test(json.artist.name);
var identifiers = 'SOUNDCLOUD_ID=' + json.id + ' RELEASETYPE=' + json.record_type;
json.tracks.data.forEach(function(track, ndx) {
trackArtist = track.artist.name;
if (!isVA && trackArtist && trackArtist == json.artist.name) trackArtist = undefined;
tracks.push([
isVA ? VA : json.artist.name,
json.title,
undefined, //extractYear(json.release_date),
json.release_date,
json.label,
json.upc,
undefined, // country
undefined, // encoding
undefined, // format
undefined,
undefined, // bitrate
undefined, // bd
undefined, // SR
undefined, // channels
'WEB',
json.genres.data.map(it => it.name).join('; '),
discNumber,
totalDiscs,
discSubtitle,
ndx + 1,
json.nb_tracks,
track.title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
track.duration,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined, //'https://www.deezer.com/album/' + json.id,
undefined,
undefined, // description
identifiers + ' TRACK_ID=' + track.id,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
});
return true;
*/
} else if (url.toLowerCase().includes('play.google.com/store/music/album/')) {
GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'document', onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
var search = new URLSearchParams(new URL(response.finalUrl).search);
var ID = search.get('id'), trackID, aggregateRating;
ID = ID ? 'GOOGLE_ID=' + ID + ' ' : '';
var root = dom.querySelector('div[itemtype="https://schema.org/MusicAlbum"]');
if (root == null) throw new Error('Unexpected Google Play metadata structure');
if ((ref = root.querySelector('div[itemprop="byArtist"]')) != null) {
artist = Array.from(ref.querySelectorAll('meta[itemprop="name"]')).map(it => it.content);
isVA = artist.length == 1 && vaParser.test(artist[0]);
}
if ((ref = root.querySelector('meta[itemprop="name"]')) != null) album = ref.content;
genres = Array.from(root.querySelectorAll('meta[itemprop="genre"]')).map(elem => elem.content);
if ((ref = root.querySelector('meta[itemprop="datePublished"]')) != null) releaseDate = ref.content;
//albumYear = extractYear(releaseDate);
if ((ref = root.querySelector('meta[itemprop="numTracks"]')) != null) totalTracks = parseInt(ref.content);
if ((ref = root.querySelector('meta[itemprop="ratingValue"]')) != null) aggregateRating = parseFloat(ref.content);
//getDescFromNode('???', response.finalUrl, false);
if ((ref = dom.querySelector('h1[class][itemprop="name"] > span')) != null
&& (ref = ref.parentNode.parentNode.querySelector('div[class] > span[class]')) != null
&& /\bExplicit/i.test(ref.textContent)) ID += 'EXPLICIT=1 ';
if ((ref = dom.querySelector('span > a[itemprop="genre"]')) != null) try {
label = ref.parentNode.nextElementSibling.textContent.trim().replace(/^(?:[©℗]|\([cCpP]\))\s*\d{4}\s+/, '');
} catch(e) {
console.warn('Unexpected HTML structure (' + e + ')');
}
var volumes = dom.querySelectorAll('c-wiz > div > h2');
if (volumes.length <= 0) {
//dom.querySelectorAll('c-wiz > div > table > tbody > tr[class]').forEach(scanPlaylist);
trackNumber = 0;
root.querySelectorAll('div[itemprop="track"]').forEach(function(tr) {
trackArtist = undefined; title = undefined; trackID = '';
if ((ref = tr.querySelector('meta[itemprop="url"]')) != null) {
search = new URLSearchParams(new URL(ref.content).search);
trackID = search.get('tid');
trackID = trackID ? 'TRACK_ID=' + trackID : '';
}
++trackNumber;
if ((ref = tr.querySelector('div[itemprop="byArtist"]')) != null) {
trackArtist = Array.from(ref.querySelectorAll('meta[itemprop="name"]')).map(it => it.content);
trackArtist = (isVA || !Array.isArray(artist) || !trackArtist.equalTo(artist)) && joinArtists(trackArtist) || undefined;
}
if ((ref = tr.querySelector('meta[itemprop="name"]')) != null) title = ref.content;
duration = durationFromMeta(tr);
addTrack();
});
} else volumes.forEach(function(volume) {
discSubtitle = volume.textContent.trim();
if (/^(?:Dis[ck]|Disco|Disque)\s+(\d+)$/i.test(discSubtitle)) {
discNumber = parseInt(RegExp.$1);
discSubtitle = undefined;
} else discNumber = undefined;
volume.nextElementSibling.querySelectorAll('tbody > tr[class]').forEach(scanPlaylist);
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
function scanPlaylist(tr) {
trackArtist = undefined; title = undefined; duration = undefined;
if ((ref = tr.querySelector('td:nth-of-type(1) > div')) != null) trackNumber = parseInt(ref.textContent);
if ((ref = tr.querySelector('td[itemprop="name"]')) != null) title = ref.textContent.trim();
if ((ref = tr.querySelector('td:nth-of-type(3)')) != null) duration = timeStringToTime(ref.textContent);
trackArtist = Array.from(tr.querySelectorAll('td:nth-of-type(4) > a')).map(it => it.textContent.trim());
trackArtist = (isVA || !Array.isArray(artist) || !trackArtist.equalTo(artist)) && joinArtists(trackArtist) || undefined;
addTrack();
}
function addTrack() {
tracks.push([
isVA ? VA : artist.join('; '),
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
undefined, // encoding
undefined, // format
undefined,
undefined, // bitrate
undefined, // bd
undefined, // sr
undefined, // channels
'WEB',
genres.join('; '),
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
ID ? undefined : response.finalUrl,
undefined,
description,
ID.concat(trackID).trim(),
].join('\x1E'));
}
} });
return true;
} else if (url.toLowerCase().includes('7digital.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'document', onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
ref = dom.querySelector('table.release-track-list');
var ID = ref != null ? '7DIGITAL_ID=' + ref.dataset.releaseid + ' ' : '';
artist = Array.from(dom.querySelectorAll('h2.release-info-artist > span[itemprop="byArtist"] > meta[itemprop="name"]')).map(node => node.content);
if ((ref = dom.querySelector('h1.release-info-title')) != null) album = ref.textContent.trim();
if ((ref = dom.querySelector('div.release-date-info > p')) != null) releaseDate = normalizeDate(ref.textContent);
//albumYear = extractYear(releaseDate);
if ((ref = dom.querySelector('div.release-label-info > p')) != null) label = ref.textContent.trim();
dom.querySelectorAll('dl.release-data > dt.release-data-label').forEach(function(dt) {
if (/\bGenres?:/.test(dt.textContent)) genres = Array.from(dt.nextElementSibling.querySelectorAll('a')).map(a => a.textContent.trim());
});
//getDescFromNode('div.album-info', response.finalUrl, false);
totalTracks = dom.querySelectorAll('table.release-track-list > tbody > tr.release-track').length;
dom.querySelectorAll('table.release-track-list').forEach(function(table) {
discNumber = undefined;
if ((ref = table.querySelector('caption > h4.release-disc-info')) != null) {
discSubtitle = ref.textContent.trim();
if (/\bDisc\s+(\d+)(?:\s+of\s+(\d+))?\b/i.test(discSubtitle)) {
discNumber = parseInt(RegExp.$1);
totalDiscs = parseInt(RegExp.$2);
discSubtitle = undefined;
}
} else discSubtitle = undefined;
table.querySelectorAll('tbody > tr.release-track').forEach(function(tr) {
trackArtist = undefined; sr = undefined; bd = undefined; format = undefined;
title = undefined; trackNumber = undefined;
var trackId = tr.dataset.trackid;
if (trackId) trackId = 'TRACK_ID=' + trackId;
if ((ref = tr.querySelector('td.release-track-preview > em.release-track-preview-text')) != null) trackNumber = ref.textContent.trim();
if ((ref = tr.querySelector('td.release-track-name > meta[itemprop="name"]')) != null) title = ref.content;
duration = durationFromMeta(tr);
tracks.push([
artist.join('; '),
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
undefined, //'lossless', // encoding
format,
undefined,
undefined, // bitrate
undefined, // bd
undefined, // sr
undefined, // channels
'WEB',
genres.join('; '),
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
compiler,
producer,
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
response.finalUrl,
undefined,
description,
ID.concat(trackId).trim(),
].join('\x1E'));
});
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
} });
return true;
} else if (url.toLowerCase().includes('e-onkyo.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'document', onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
var ID = /\/album\/(\w+)\/?$/.test(response.finalUrl) ? 'EONKYO_ID=' + RegExp.$1 + ' ' : '';
artist = Array.from(dom.querySelectorAll('div.jacketDetailArea p.artistsName > a'))
.map(node => node.textContent.trim());
if ((ref = dom.querySelector('div.jacketDetailArea p.packageTtl')) != null) album = ref.textContent.trim();
if ((ref = dom.querySelector('div.jacketDetailArea p.recordlabelName > a')) != null) label = ref.textContent.trim();
if ((ref = dom.querySelector('div.jacketDetailArea p.releaseDay > a')) != null) releaseDate = normalizeDate(ref.textContent);
if ((ref = dom.querySelector('div.jacketDetailArea p.packageNoteDetail')) != null
&& /^\s*(?:\(C\)|©)\s+(\d{4})\b/i.test(ref.lastChild.textContent)) albumYear = parseInt(RegExp.$1);
//getDescFromNode('div#credit', response.finalUrl, true);
if (/\s+\(\s*(?:(\d+)[\-\s]*bit)?\s*\/?\s*(?:(\d+(?:\.\d+)?)\s*kHz)?\s*\)\s*$/i.test(album)) {
album = RegExp.leftContext;
bd = parseInt(RegExp.$1) || undefined;
sr = parseFloat(RegExp.$2) * 1000 || undefined;
}
trs = dom.querySelectorAll('dl.musicList > dd.musicBox');
totalTracks = trs.length;
trs.forEach(function(tr) {
title = undefined; title = undefined; duration = undefined; trackArtist = undefined;
//var trackId = tr.dataset.trackid;
//if (trackId) trackId = 'TRACK_ID=' + trackId;
if ((ref = tr.querySelector('div.musicListNo')) != null) trackNumber = ref.textContent.trim();
if ((ref = tr.querySelector('div.musicTtl > span')) != null) title = ref.title;
//trackArtist = tr.children[5].textContent.trim();
//if (trackArtist == artist.join(', ')) trackArtist = undefined;
if ((ref = tr.querySelector('div.musicTime')) != null) duration = timeStringToTime(ref.textContent.trim());
tracks.push([
artist.join('; '),
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
'lossless', // encoding
'FLAC', // format
undefined,
undefined, // bitrate
bd,
sr,
undefined, // channels
'WEB',
undefined, // genre
discNumber,
totalDiscs,
discSubtitle,
trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
undefined, // compiler
undefined, // producer
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
!ID ? response.finalUrl : undefined,
undefined,
description,
ID,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
} });
return true;
} else if (url.toLowerCase().includes('store.acousticsounds.com')) {
GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'document', onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
'): readyState=' + response.readyState + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
var ID = /\/(\d+)\/$/.test(response.finalUrl) ? 'ACOUSTICSOUNDS_ID=' + RegExp.$1 + ' ' : '';
artist = Array.from(dom.querySelectorAll('div > h1 > a')).map(node => node.textContent.trim());
if ((ref = dom.querySelector('div > h1')) != null) album = ref.lastChild.wholeText.trim().replace(/\s*-\s*/, '');
dom.querySelectorAll('div > p > table > tbody > tr > td:first-of-type').forEach(function(td) {
if (/^(?:Label):/i.test(td.textContent)) label = td.nextElementSibling.textContent.trim();
if (/^(?:Genre):/i.test(td.textContent)) genres[0] = td.nextElementSibling.textContent.trim();
if (/^(?:Product\s+No):/i.test(td.textContent)) catalogue = td.nextElementSibling.textContent.trim();
if (/^(?:Category):/i.test(td.textContent)
&& /^(.+)\s+(\d+(?:\.\d+)?)\s*kHz(?:\s*\/\s*(\d+)[\s\-]?bit)?\s+Download\b/.test(td.nextElementSibling.textContent.trim())) {
format = RegExp.$1;
sr = parseFloat(RegExp.$2) * 1000;
bd = parseInt(RegExp.$3);
}
});
getDescFromNode('div#description > p', response.finalUrl, true);
trs = dom.querySelectorAll('div#tracks > table > tbody > tr');
totalTracks = trs.length;
trackNumber = 0;
trs.forEach(function(tr) {
title = undefined; title = undefined; duration = undefined; trackArtist = undefined;
if ((ref = tr.querySelector('td[nowrap]')) != null) title = ref.textContent.trim();
tracks.push([
artist.join('; '),
album,
albumYear,
releaseDate,
label,
catalogue,
undefined, // country
['FLAC', 'DSD'].includes(format) ? 'lossless' : undefined,
format,
undefined,
undefined, // bitrate
bd,
sr,
undefined, // channels
'WEB',
genres.join('; '),
discNumber,
totalDiscs,
discSubtitle,
++trackNumber,
totalTracks,
title,
trackArtist,
undefined,
composer,
undefined,
undefined,
undefined, // compiler
undefined, // producer
duration,
undefined,
undefined,
undefined,
undefined,
undefined,
!ID ? response.finalUrl : undefined,
undefined,
description,
ID,
].join('\x1E'));
});
clipBoard.value = tracks.join('\n');
fillFromText_Music();
} });
return true;
}
if (!weak) {
addMessage('This domain not supported', 'ua-critical');
clipBoard.value = '';
}
return false;
function getDescFromNode(selector, url, quote = false) {
description = [];
dom.querySelectorAll(selector).forEach(function(node) {
var p = html2php(node, url).trim();
if (p) description.push(p);
});
description = description.join('\n\n').flatten();
if (quote && description.length > 0 && !description.includes('[quote]')) {
description = '[quote]' + description + '[/quote]';
}
}
function durationFromMeta(elem) {
var m = elem.querySelector('meta[itemprop="duration"]');
if (m == null) return undefined;
if (/^PT?(?:(?:(\d+)H)?(\d+)M)?(\d+)S$/i.test(m.content))
return (parseInt(RegExp.$1) || 0) * 60**2 + (parseInt(RegExp.$2) || 0) * 60 + (parseInt(RegExp.$3) || 0);
m = timeStringToTime(m.content);
return m != null ? m : undefined;
}
} // fetchOnline_Music
function trackComparer(a, b) {
var cmp;
if (release.totaldiscs > 1) {
cmp = a.discnumber - b.discnumber;
if (!isNaN(cmp) && cmp != 0) return cmp;
} else {
cmp = (a.discsubtitle || '').localeCompare(b.discsubtitle || '');
//if (cmp != 0) return cmp;
}
cmp = parseInt(a.tracknumber) - parseInt(b.tracknumber);
if (!isNaN(cmp)) return cmp;
var m1 = vinyltrackParser.exec(a.tracknumber.toUpperCase());
var m2 = vinyltrackParser.exec(b.tracknumber.toUpperCase());
return m1 != null && m2 != null ?
m1[1].localeCompare(m2[1]) || parseFloat(m1[2]) - parseFloat(m2[2]) :
a.tracknumber.toUpperCase().localeCompare(b.tracknumber.toUpperCase());
}
function getStoreUrl() {
for (var it of [
['DISCOGS_ID', 'https://www.discogs.com/release/{ID}'],
['ITUNES_ID', 'https://music.apple.com/album/{ID}'],
['APPLE_ID', 'https://music.apple.com/album/{ID}'],
['SPOTIFY_ID', 'https://open.spotify.com/album/{ID}'],
['DEEZER_ID', 'https://www.deezer.com/album/{ID}'],
['JUNODOWNLOAD_ID', 'https://www.junodownload.com/products/{ID}'],
['PROSTUDIOMASTERS_ID', 'https://www.prostudiomasters.com/album/page/{ID}'],
['GOOGLE_ID', 'https://play.google.com/store/music/album/?id={ID}'],
['EONKYO_ID', 'https://www.e-onkyo.com/music/album/{ID}/'],
['ACOUSTICSOUNDS_ID', 'https://store.acousticsounds.com/d/{ID}/'],
]) {
let ID = getHomoIdentifier(it[0]);
if (ID) return it[1].replace('{ID}', ID);
}
return undefined;
}
function getCoverOnline(url) {
if (url) GM_xmlhttpRequest({
method: 'GET',
url: url,
onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
var ref, dom = domParser.parseFromString(response.responseText, 'text/html');
function testDomain(url, selector) {
return typeof url == 'string' && response.finalUrl.toLowerCase().includes(url.toLowerCase()) ?
dom.querySelector(selector) : null;
}
if ((ref = testDomain('qobuz.com', 'div.album-cover > img')) != null) {
setImage(ref.src);
} else if ((ref = testDomain('highresaudio.com', 'div.albumbody > img.cover[data-pin-media]')) != null) {
setImage(ref.dataset.pinMedia);
} else if ((ref = testDomain('bandcamp.com', 'div#tralbumArt > a.popupImage')) != null) {
setImage(ref.href);
} else if ((ref = testDomain('7digital.com', 'span.release-packshot-image > img[itemprop="image"]')) != null) {
setImage(ref.src);
} else if ((ref = testDomain('hdtracks.com', 'p.product-image > img')) != null) {
setImage(ref.src);
} else if ((ref = testDomain('discogs.com', 'div#view_images > p:first-of-type > span > img')) != null) {
setImage(ref.src);
} else if ((ref = testDomain('supraphonline.cz', 'meta[itemprop="image"]')) != null) {
setImage(ref.content.replace(/\?.*$/, ''));
} else if ((ref = testDomain('prestomusic.com', 'div.c-product-block__aside > a')) != null) {
setImage(ref.href.replace(/\?\d+$/, ''));
} else if ((ref = testDomain('bontonland.cz', 'a.detailzoom')) != null) {
setImage(ref.href);
} else if ((ref = testDomain('nativedsd.com', 'a#album-cover')) != null) {
setImage(ref.href);
} else if ((ref = testDomain('deezer.com', 'meta[property="og:image"]')) != null) {
setImage(ref.content);
} else if ((ref = testDomain('junodownload.com', 'meta[property="og:image"]')) != null) {
setImage(ref.content);
} else if ((ref = testDomain('spotify.com', 'c')) != null) {
setImage(ref.content);
} else if ((ref = testDomain('prostudiomasters.com', 'img.album-art')) != null) {
setImage(ref.currentSrc || ref.src);
} else if ((ref = testDomain('play.google.com/store/music/album/', 'meta[itemprop="image"]')) != null) {
setImage(ref.content);
} else if ((ref = testDomain('e-onkyo.com', 'figure > a.colorbox')) != null) {
setImage(new URL(response.finalUrl).origin + ref.pathname);
} else if ((ref = testDomain('store.acousticsounds.com', 'div#detail > link[rel="image_src"]')) != null) {
setImage(ref.href.replace(/\/medium\//i, '/large/'));
}
},
//onerror: response => { throw new Error('Response error ' + response.status + ' (' + response.statusText + ')') },
//ontimeout: function() { throw new Error('Timeout') },
});
}
function reqSelectFormats(...vals) {
vals.forEach(function(val) {
[
['MP3', 0],
['FLAC', 1],
['AAC', 2],
['AC3', 3],
['DTS', 4],
].forEach(function(fmt) {
if (val.toLowerCase() == fmt[0].toLowerCase()
&& (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(function(it) {
if ((typeof val == 'string' ? val.toLowerCase() : val)
== (typeof it[0] == 'string' ? it[0].toLowerCase() : it[0])) ndx = it[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 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 joinArtists(arr, decorator = artist => artist) {
if (!Array.isArray(arr)) return null;
if (arr.some(artist => artist.includes('&'))) return arr.map(decorator).join(', ');
if (arr.length < 3) return arr.map(decorator).join(' & ');
return arr.slice(0, -1).map(decorator).join(', ') + ' & ' + decorator(arr.slice(-1).pop());
}
} // fillFromText_Music
function fillFromText_Apps(weak = false) {
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)
throw new Error('GM_xmlhttpRequest readyState=' + response.status + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
i = dom.querySelector('h1.item_title > span');
var title = i == null ? undefined : i.textContent
.replace(/\s+\((?:x|ia|em)(?:64)\)/ig, ' (64-bit)')
.replace(/\s+\(x(?:86|32)\)/ig, ' (32-bit)')
.replace(/\s+(?:Build)\s+(\d+)\b/g, ' build $1')
.replace(/\s+(?:Multilingual|Multi(?:-|\s)*lang(?:uage)?)\b/g, ' multilingual');
description = html2php(dom.querySelector('section.descr'), response.finalUrl).trim();
if (/\s*^[ \t]*(?:\[i\]\[\/i\])?Homepage\s*$.*/im.test(description)) description = RegExp.leftContext;
description = description.split(/[ \t]*\r?\n/).slice(6).map(line => line.trim()).join('\n')
.replace(/^[ \t]*(?:\[i\]\[\/i\])?Screenshots:?\s*/igm, '')
.replace(/^[ \t]*(?:\[i\]\[\/i\])?(\[b\]Release\s+Notes:?\[\/b\])(?:[ \t]*\r?\n)+/igm, '$1\n')
.replace(/\[hr\]/ig, '\n');
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) {
let url;
if (ref.pathname.toLowerCase().startsWith('/confirm/url/') && urlParser.test(ref.text)) {
url = ref.text.trim();
} else url = ref.href;
description += '\n\n[b]Product page:[/b]\n[url]' + removeRedirect(url) + '[/url]';
}
writeDescription(collapseGaps(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 internalTags = Array.from(dom.querySelectorAll('ul.item_tags_list > li > a[rel="tag"]'))
.map(elem => elem.textContent.toLowerCase().trim());
if ((ref = dom.querySelector('a.cat:last-of-type > span')) != null) {
if (ref.textContent.toLowerCase() == 'windows') {
tags.add('apps.windows');
if (/\b(?:(?:x|ia|em)64)\b/i.test(releaseInfo) || /\(64[-\s]*bit\)/i.test(title)) tags.add('win64');
if (/\b(?:x86|x32)\b/i.test(releaseInfo) || /\(32[-\s]*bit\)/i.test(title)) tags.add('win32');
}
if (ref.textContent.toLowerCase() == 'macos') tags.add('apps.mac');
if (ref.textContent.toLowerCase() == 'linux' || ref.textContent.toLowerCase() == 'unix') tags.add('apps.linux');
if (ref.textContent.toLowerCase() == 'android') tags.add('apps.android');
if (ref.textContent.toLowerCase() == 'ios') tags.add('apps.ios');
}
if (tags.length > 0 && elementWritable(ref = document.getElementById('tags'))) ref.value = tags.toString();
if (title && !/\(\d+-?bit\)/i.test(title)) {
if (tags.includes('win64') && !tags.includes('win32')) title += ' (64-bit)';
if (tags.includes('win32') && !tags.includes('win64')) title += ' (32-bit)';
}
if (elementWritable(ref = document.getElementById('title'))) ref.value = title || '';
}, });
return true;
}
if (!weak) {
addMessage('This domain not supported', 'ua-critical');
clipBoard.value = '';
}
return false;
}
function fillFromText_Ebooks(weak = false) {
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) throw new Error('GM_xmlhttpRequest readyState=' + response.status + ', status=' + response.status);
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 && elementWritable(ref = document.getElementById('title') || document.querySelector('input[name="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 = extractYear(i.textContent))) description += ' (' + i + ')';
ref.value = description;
}
ref = dom.querySelector('section#description > div');
if (ref != null) description = html2php(ref).replace(/^\s*\[img\].*?\[\/img\]\s*/i, '').trim();
if (description.length > 0 && !description.includes('[quote]')) description = '[quote]' + description + '[/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)) {
url = new URL('https://www.worldcat.org/isbn/' + detail.children[1].textContent.trim());
val = '[url=' + url.href + ']' + detail.children[1].textContent.trim() + '[/url]';
findOCLC(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;
});
url = new URL(response.finalUrl);
description += '\n\n[b]More info:[/b]\n[url]' + url.href + '[/url]';
writeDescription(collapseGaps(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 && elementWritable(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) throw new Error('GM_xmlhttpRequest readyState=' + response.status + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
i = dom.querySelectorAll('a.authorName > span');
if (i.length > 0 && elementWritable(ref = document.getElementById('title') || document.querySelector('input[name="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 = extractYear(i.textContent))) description += ' (' + i + ')';
ref.value = description;
}
var description = [];
dom.querySelectorAll('div#description span:last-of-type').forEach(function(node) {
description = html2php(node, response.finalUrl).trim();
});
if (description.length > 0 && !description.includes('[quote]')) {
description = '[quote]' + description.trim() + '[/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) && (/\b(\d{13})\b/.test(val) || /\b(\d{10})\b/.test(val))) {
url = new URL('https://www.worldcat.org/isbn/' + RegExp.$1);
val = '[url=' + url.href + ']' + strip(detail.children[1].textContent) + '[/url]';
findOCLC(url);
}
description += '\n[b]' + lbl + ':[/b] ' + val;
});
if ((ref = dom.querySelector('span[itemprop="ratingValue"]')) != null) {
description += '\n[b]Rating:[/b] ' + Math.round(parseFloat(ref.firstChild.textContent) * 20) + '%';
}
url = new URL(response.finalUrl);
// if ((ref = dom.querySelector('div#buyButtonContainer > ul > li > a.buttonBar')) != null) {
// let u = new URL(ref.href);
// description += '\n[url=' + url.origin + u.pathname + '?' + u.search + ']Libraries[/url]';
// }
description += '\n\n[b]More info and reviews:[/b]\n[url]' + url.origin + url.pathname + '[/url]';
dom.querySelectorAll('div.clearFloats.bigBox').forEach(function(bigBox) {
if (bigBox.id == 'aboutAuthor' && (ref = bigBox.querySelector('h2 > a')) != null) {
description += '\n\n[b][url=' + ref.href + ']' + ref.textContent.trim() + '[/url][/b]';
if ((ref = bigBox.querySelector('div.bigBoxBody a > div[style*="background-image"]')) != null) {
}
if ((ref = bigBox.querySelector('div.bookAuthorProfile__about > span[id]:last-of-type')) != null) {
description += '\n' + html2php(ref, response.finalUrl).trim()
.replace(/^\[i\]Librarian\s+Note:.*?\[\/i\]\s+/i, '');
}
} else if ((ref = bigBox.querySelector('h2 > a[href^="/trivia/"]')) != null) {
description += '\n\n[b][url=' + ref.href + ']' + ref.textContent.trim() + '[/url][/b]';
if ((ref = bigBox.querySelector('div.bigBoxContent > div.mediumText')) != null) {
description += '\n' + ref.firstChild.textContent.trim();
}
// } else if ((ref = bigBox.querySelector('h2 > a[href^="/work/quotes/"]')) != null) {
// description += '\n\n[b][url=' + ref.href + ']' + ref.textContent.trim() + '[/url][/b]';
// bigBox.querySelectorAll('div.bigBoxContent > div.stacked > span.readable').forEach(function(quote) {
// description += '\n' + ref.firstChild.textContent.trim();
// });
}
});
writeDescription(collapseGaps(description));
if ((ref = dom.querySelector('div.editionCover > img')) != null) setImage(ref.src.replace(/\?.*/, ''));
dom.querySelectorAll('div.elementList > div.left').forEach(tag => { tags.add(tag.textContent.trim()) });
if (tags.length > 0 && elementWritable(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) throw new Error('GM_xmlhttpRequest readyState=' + response.status + ', status=' + response.status);
dom = domParser.parseFromString(response.responseText, 'text/html');
i = dom.querySelectorAll('span[itemprop="author"] > a');
if (i != null && elementWritable(ref = document.getElementById('title') || document.querySelector('input[name="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 = extractYear(i.textContent))) description += ' (' + i + ')';
ref.value = description;
}
ref = dom.querySelector('p[itemprop="description"]');
if (ref != null) description = html2php(ref, response.finalUrl).trim();
if (description.length > 0 && !description.includes('[quote]')) description = '[quote]' + description + '[/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) {
url = new URL('https://www.worldcat.org/isbn/' + RegExp.$1.replace(/-/g, ''));
val = '[url=' + url.href + ']' + detail.children[1].textContent.trim() + '[/url]';
findOCLC(url);
}
description += '\n[b]' + lbl + '[/b] ' + val;
});
url = new URL(response.finalUrl);
description += '\n\n[b]More info:[/b]\n[url]' + url.origin + url.pathname + '[/url]';
writeDescription(collapseGaps(description));
if ((ref = dom.querySelector('div#icover_mid > a')) != null) setImage(ref.href.replace(/\?.*/, ''));
if ((ref = dom.querySelector('div#lbImage')) != null && /\burl\("(.*)"\)/i.test(i.style.backgroundImage)) {
setImage(RegExp.$1.replace(/\?.*/, ''));
}
dom.querySelectorAll('h5[itemprop="genre"] > a').forEach(tag => { tags.add(tag.textContent.trim()) });
dom.querySelectorAll('a.tag').forEach(tag => { tags.add(tag.textContent.trim()) });
if (tags.length > 0 && elementWritable(ref = document.getElementById('tags'))) ref.value = tags.toString();
}, });
return true;
}
if (!weak) {
addMessage('This domain not supported', 'ua-critical');
clipBoard.value = '';
}
return false;
function joinAuthors(nodeList) {
if (typeof nodeList != 'object') return null;
return Array.from(nodeList).map(it => it.textContent.trim()).join(' & ');
}
function findOCLC(url) {
if (!url) return false;
var oclc = document.querySelector('input[name="oclc"]');
if (!elementWritable(oclc)) return false;
GM_xmlhttpRequest({
method: 'GET',
url: url,
onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
var dom = domParser.parseFromString(response.responseText, 'text/html');
var ref = dom.querySelector('tr#details-oclcno > td:last-of-type');
if (ref != null) oclc.value = ref.textContent.trim();
},
});
return true;
}
}
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 writeDescription(desc) {
if (typeof desc != 'string') return;
if (elementWritable(ref = document.querySelector('textarea#desc')
|| document.querySelector('textarea#description'))) 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 (!elementWritable(image)) return false;
image.value = url;
if (prefs.auto_preview_cover/* && image.id*/) imagePreview(image, url);
if (prefs.auto_rehost_cover) {
if (rehostItBtn != null) rehostItBtn.click(); else {
testImageUrl(url).then(result => rehost2PTPIMG([result])).then(function(urls) {
if (urls.length <= 0) return;
image.value = urls[0];
if (prefs.auto_preview_cover/* && image.id*/) imagePreview(image, urls[0]);
}).catch(e => { alert(e) });
}
}
}
function elementWritable(elem) {
return elem != null && !elem.disabled && (overwrite || !elem.value || isNWCD && elem.value == '---');
}
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);
}
function addArtistField() { exec(function() { AddArtistField() }) }
function removeArtistField() { exec(function() { RemoveArtistField() }) }
}
function html2php(node, url, tagChain = []) {
if (typeof node != 'object') return null;
switch (node.nodeType) {
case Node.TEXT_NODE:
return node.wholeText.replace(/\s+/g, ' ');
case Node.ELEMENT_NODE: {
let tags = [], _tags = [], text = [];
for (let i = 0; i < 5; ++i) text[i] = '';
switch (node.nodeName) {
case 'P':
text[0] = '\n'; text[4] = '\n';
break;
case 'DIV':
text[0] = '\n\n'; text[4] = '\n\n';
break;
case 'DT':
text[4] = '\n';
break;
case 'DD':
text[4] = '\n';
if (isRED) addTag('pad=0|0|0|30'); else text[0] = ' ';
break;
case 'LABEL':
addTag('b');
text[0] = '\n\n';
break;
case 'BR':
return '\n';
case 'HR':
return isRED ? '[hr]' : '\n';
case 'B': case 'STRONG':
addTag('b');
break;
case 'I': case 'EM': case 'DFN': case 'CITE': case 'VAR':
addTag('i');
break;
case 'U': case 'INS':
addTag('u');
break;
case 'DEL':
addTag('s');
break;
case 'CODE': case 'SAMP': case 'KBD':
addTag('code');
text[2] = node.textContent;
break;
case 'PRE':
addTag('pre');
text[2] = node.textContent;
break;
case 'BLOCKQUOTE': case 'QUOTE':
addTag('quote');
break;
case 'Q':
text[1] = '"'; text[3] = '"';
break;
case 'H1':
addTag('size=5'); addTag('b');
text[0] = '\n\n'; text[4] = '\n\n';
break;
case 'H2':
addTag('size=4'); addTag('b');
text[0] = '\n\n'; text[4] = '\n\n';
break;
case 'H3':
addTag('size=3'); addTag('b');
text[0] = '\n\n'; text[4] = '\n\n';
break;
case 'H4': case 'H5': case 'H6':
addTag('b');
text[0] = '\n\n'; text[4] = '\n\n';
break;
case 'SMALL':
addTag('size=1');
break;
case 'OL': case 'UL':
_tags.push(node.nodeName.toLowerCase());
break;
case 'DL':
_tags.push(node.nodeName.toLowerCase());
break;
case 'LI':
switch (tagChain.reverse().find(tag => /^[ou]l$/.test(tag))) {
case 'ol': text[0] = '[#] '; text[4] = '\n'; break;
case 'ul': text[0] = '[*] '; text[4] = '\n'; break;
default: return '';
}
break;
case 'A': {
addTag(node.textContent.trim().length > 0 ? 'url=' + removeRedirect(node.href) : 'url');
if (node.textContent.trim().length <= 0) text[2] = removeRedirect(node.href);
break;
}
case 'IMG':
addTag('img');
text[2] = node.dataset.src || node.src;
break;
case 'DETAILS': {
let summary = node.querySelector('summary');
summary = summary != null ? '='.concat(summary.textContent.trim()) : '';
addTag('hide' + summary);
break;
}
case 'AUDIO': case 'BASE': case 'BUTTON': case 'CANVAS': case 'COL': case 'COLGROUP': case 'DATALIST':
case 'DIALOG': case 'EMBED': case 'FIELDSET': case 'FORM': case 'HEAD': case 'INPUT': case 'LEGEND':
case 'LINK': case 'MAP': case 'META': case 'METER': case 'NOSCRIPT': case 'OBJECT': case 'OPTGROUP':
case 'OPTION': case 'PARAM': case 'PROGRESS': case 'SELECT': case 'SOURCE': case 'STYLE': case 'SUMMARY':
case 'SVG': case 'TEMPLATE': case 'TEXTAREA': case 'TITLE': case 'TRACK': case 'VIDEO':
return '';
}
if (['left', 'center', 'right'].some(al => node.style.textAlign.toLowerCase() == al)) {
addTag('align=' + node.style.textAlign.toLowerCase());
}
if (node.style.fontWeight >= 700) addTag('b');
switch (node.style.fontStyle.toLowerCase()) {
case 'italic': addTag('i'); break;
}
switch (node.style.textDecorationLine.toLowerCase()) {
case 'underline': addTag('u'); break;
case 'line-through': addTag('s'); break;
}
if (node.style.color) {
ctxt.fillStyle = elem.style.color;
if (/^#(?:[a-f0-8]{2}){3,4}$/i.test(ctxt.fillStyle)) addTag('color=' + ctxt.fillStyle);
}
if (!text[2]) node.childNodes.forEach(function(node) {
text[2] += html2php(node, url, tagChain.concat(tags.concat(_tags).map(tag => tag.replace(/=.*$/, ''))));
});
return text[0].concat((text[1] || text[2] || text[3] ? tags.map(tag => '[' + tag + ']').join('').concat(text[1],
text[2], text[3], tags.reverse().map(tag => '[/' + tag.replace(/=.*$/, '') + ']').join('')) : ''), text[4]);
function addTag(tag) {
if (tagChain.concat(tags.map(tag => tag.replace(/=.*$/, ''))).includesCaseless(tag.replace(/=.*$/, ''))) return;
tags.push(tag);
}
}
}
return '';
}
function imagePreview(anchor, src, size) {
if ((child = document.getElementById('cover-preview')) == null) {
if (!(anchor instanceof HTMLElement)) return;
elem = document.createElement('div');
elem.style = 'padding-top: 10px; float: right; width: 90%;';
child = document.createElement('img');
child.id = 'cover-preview';
elem.append(child);
var div = document.createElement('div');
div.id = 'cover-size';
elem.append(div);
anchor.parentNode.previousElementSibling.append(document.createElement('br'));
anchor.parentNode.previousElementSibling.append(elem);
}
div = div || document.getElementById('cover-size');
if (size > 0) {
if (size < 1024**1) size = Math.round(size) + ' B';
else if (size < 1024**2) size = (Math.round(size * 100 / 1024**1) / 100) + ' KiB';
else if (size < 1024**3) size = (Math.round(size * 100 / 1024**2) / 100) + ' MiB';
else if (size < 1024**4) size = (Math.round(size * 100 / 1024**3) / 100) + ' GiB';
else size = (Math.round(size * 100 / 1024**4) / 100) + ' TiB';
}
div.textContent = size || '';
if (typeof src == 'string' && urlParser.test(src)) child.onload = function(evt) {
this.onload = null;
if (!this.naturalWidth || !this.naturalHeight) return;
if (size) div.textContent += ' (' + this.naturalWidth + '×' + this.naturalHeight + ')';
else div.textContent = this.naturalWidth + '×' + this.naturalHeight;
};
child.src = src || '';
}
function removeRedirect(uri) {
return typeof uri != 'string' ? null : [
'anonymz.com/?',
'anonym.to/?',
'nullrefer.com/?',
'dereferer.me/?',
'reho.st/?',
].reduce(function(acc, it) {
if (acc.toLowerCase().startsWith('https://' + it)) return acc.slice(it.length + 8);
if (acc.toLowerCase().startsWith('http://' + it)) return acc.slice(it.length + 7);
return acc;
}, uri);
}
function titleCase(str) {
return str.toLowerCase().split(' ').map(x => x[0].toUpperCase() + x.slice(1)).join(' ');
}
function collapseGaps(str) {
return str.replace(/(?:[ \t]*\r?\n){3,}/g, '\n\n').replace(/\[(\w+)\]\[\/\1\]/ig,'').trim();
}
function reInParenthesis(expr) { return new RegExp('\\s+\\([^\\(\\)]*'.concat(expr, '[^\\(\\)]*\\)$'), 'i') }
function reInBrackets(expr) { return new RegExp('\\s+\\[[^\\[\\]]*'.concat(expr, '[^\\[\\]]*\\]$'), 'i') }
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 normalizeDate(str) {
if (typeof str != 'string') return null;
if (/\b(d{4}-\d+-\d+|\d{1,2}\/\d{1,2}\/\d{2})\b/.test(str)) return RegExp.$1; // US (clash with BE, IT)
if (/\b(\d{1,2})\/(\d{1,2})\/(\d{4})\b/.test(str)) return RegExp.$2 + '/' + RegExp.$1 + '/' + RegExp.$3; // UK, IRL, FR
if (/\b(\d{1,2})-(\d{1,2})-(\d{2})\b/.test(str)) return RegExp.$2 + '/' + RegExp.$1 + '/' + RegExp.$3; // NL
if (/\b(\d{1,2})\.\s?(\d{1,2})\.\s?(\d{2}|\d{4})\b/.test(str)) return RegExp.$2 + '/' + RegExp.$1 + '/' + RegExp.$3; // AT, CH, DE, LU, CE
if (/\b(\d{4})\.\s?(\d{1,2})\.\s?(\d{1,2})\b/.test(str)) return RegExp.$2 + '/' + RegExp.$3 + '/' + RegExp.$1; // JP
return extractYear(str);
}
function extractYear(expr) {
if (typeof expr == 'number') return Math.round(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 testImageUrl(url) {
if (!urlParser.test(url)) return Promise.reject('not an image');
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].some(function(ext) {
return url.toLowerCase().endsWith('.'.concat(ext));
})) return Promise.resolve(url); // weak quick test
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() { resolve(this.src) };
img.onerror = img.onabort = function() { reject("not an image") };
img.src = url;
});
}
function testImageUrls(urls) { return Array.isArray(urls) ? Promise.all(urls.map(testImageUrl)) : null }
function imageClear(evt) {
evt.target.value = '';
if (prefs.auto_preview_cover) imagePreview(evt.target, null);
}
function imageClear1(evt) {
if (evt.buttons != 4) return;
evt.target.value = '';
}
function imageDropHandler(evt) {
if (evt.dataTransfer == null) return;
if (evt.dataTransfer.files.length > 0 && evt.dataTransfer.files[0].type.toLowerCase().startsWith('image/')) {
evt.preventDefault();
evt.stopPropagation();
evt.target.disabled = true;
if (evt.target.hTimer) {
clearTimeout(evt.target.hTimer);
delete evt.target.hTimer;
}
evt.target.style.backgroundColor = '#800000';
upload2PTPIMG([evt.dataTransfer.files[0]])
.then(results => {
evt.target.value = results[0];
evt.target.style.backgroundColor = '#004000';
if (prefs.auto_preview_cover) imagePreview(evt.target, results[0], evt.dataTransfer.files[0].size);
evt.target.hTimer = setTimeout(function() {
evt.target.style.backgroundColor = null;
delete evt.target.hTimer;
}, 10000);
})
.catch(function(e) {
evt.target.style.backgroundColor = null;
alert(e);
})
.then(function() { evt.target.disabled = false });
} else if (evt.dataTransfer.items.length > 0) processImageUrl(evt, evt.dataTransfer);
}
function imagePasteHandler(evt) {
if (evt.clipboardData == null) return;
if (evt.clipboardData.items.length > 0) processImageUrl(evt, evt.clipboardData);
}
function processImageUrl(evt, evtData) {
testImageUrl((evtData.getData('text/uri-list') || evtData.getData('text/plain')).split(/\r?\n/)[0]).then(function(url) {
evt.preventDefault();
evt.stopPropagation();
evt.target.value = url;
if (prefs.auto_rehost_cover) {
if (rehostItBtn != null) {
rehostItBtn.click();
if (prefs.auto_preview_cover) imagePreview(evt.target, evt.target.value);
} else {
evt.target.disabled = true;
rehost2PTPIMG([url])
.then(function(results) {
evt.target.value = results[0];
if (prefs.auto_preview_cover) imagePreview(evt.target, results[0]);
})
.catch(function(e) { alert(e) })
.then(function() { evt.target.disabled = false });
}
} else if (prefs.auto_preview_cover) imagePreview(evt.target, url);
}).catch(function(e) { console.warn(e) });
}
function descDropHandler(evt) {
if (evt.dataTransfer == null) return;
if (evt.dataTransfer.files.length > 0) {
let images = [];
Array.from(evt.dataTransfer.files).forEach(function(file) {
switch (file.type) {
case 'text/plain':
evt.preventDefault();
evt.stopPropagation();
evt.target.disabled = true;
file.getText('utf-8').then(function(text) {
var isDR = file.name.toLowerCase().endsWith('foo_dr.txt') && /^Official DR value:\s*DR(\d+)\b/im.test(text);
if (isDR) text = '[hide=DR' + RegExp.$1 + '][pre]' + text + '[/pre]';
if (evt.target.textLength <= 0) {
if (isDR) text += '[/hide]';
evt.target.value = text;
} else if (isDR && /\[hide=DR\d*\]\[pre\]\[\/pre\]/i.test(evt.target.value)) {
evt.target.value = RegExp.leftContext + text + RegExp.rightContext;
} else {
if (isDR) text += '[/hide]';
evt.target.value += '\n\n'.concat(text);
}
}).catch(function(e) { alert(e) }).then(function() { evt.target.disabled = false });
break;
case 'image/png':
case 'image/jpeg':
case 'image/gif':
case 'image/bmp':
case 'image/webp':
//case 'image/svg+xml':
evt.preventDefault();
evt.stopPropagation();
images.push(file);
break;
}
});
if (images.length > 0) {
evt.target.disabled = true;
evt.target.style.background = '#FF000010 no-repeat center center url(' + ulImgData +')';
//evt.target.style.background = '#FF000010 url("https://svgshare.com/i/H16.svg") no-repeat center center';
upload2PTPIMG(images).then(urlHandler.bind({ tag: 'img' })).catch(function(e) { alert(e) }).then(function() {
evt.target.style.background = null;
evt.target.disabled = false;
});
}
} else if (evt.dataTransfer.items.length > 0) {
let content = evt.dataTransfer.getData('text/uri-list');
if (content) {
evt.preventDefault();
evt.stopPropagation();
content = content.split(/\r?\n/);
testImageUrls(content).then(function(urls) {
evt.target.disabled = true;
rehost2PTPIMG(urls).then(urlHandler.bind({ tag: 'img' })).catch(function(e) { alert(e) }).then(function() {
evt.target.disabled = false;
});
}).catch(function(e) {
let as = domParser.parseFromString(evt.dataTransfer.getData('text/html'), 'text/html').body.querySelectorAll('a');
urlHandler.bind({ tag: 'url', titles: Array.from(as).map(a => a.textContent.trim()) })(content);
});
} else if (content = evt.dataTransfer.getData('text/html')) {
evt.preventDefault();
evt.stopPropagation();
content = html2php(domParser.parseFromString(content, 'text/html').body).trim();
if (evt.target.textLength <= 0) evt.target.value = content; else evt.target.value += '\n\n'.concat(content);
} else if (content = evtData.getData('text/plain')) {
evt.preventDefault();
evt.stopPropagation();
if (evt.target.textLength <= 0) evt.target.value = content; else evt.target.value += '\n\n'.concat(content);
}
}
function urlHandler(urls) {
var rx = new RegExp('\\[' + this.tag + '\\]\\[\\/' + this.tag + '\\]', 'i');
urls.forEach(function(url, ndx) {
if (url.length <= 0 || !urlParser.test(urls)) return;
var php = '[' + this.tag;
php += Array.isArray(this.titles) && this.titles[ndx] ? '=' + url + ']' + this.titles[ndx] : ']' + url;
php += '[/' + this.tag +']';
if (evt.target.textLength <= 0) {
evt.target.value = php;
} else if (rx.test(evt.target.value)) {
evt.target.value = RegExp.leftContext + php + RegExp.rightContext;
} else evt.target.value += '\n\n'.concat(php);
}.bind(this));
}
}
function descPasteHandler(evt) {
if (evt.clipboardData == null || evt.clipboardData.items.length <= 0) return;
var content = evt.clipboardData.getData('text/html');
if (!content) return;
evt.preventDefault();
evt.stopPropagation();
content = html2php(domParser.parseFromString(content, 'text/html').body).trim();
var selStart = evt.target.selectionStart;
evt.target.value = evt.target.value.slice(0, evt.target.selectionStart)
.concat(content, evt.target.value.slice(evt.target.selectionEnd));
evt.target.setSelectionRange(selStart + content.length, selStart + content.length);
}
function rehostDropHandler(evt) {
if (evt.dataTransfer == null || evt.dataTransfer.files.length <= 0) return;
var image = document.getElementById('image') || document.querySelector('input[name="image"]');
if (image == null) return;
evt.preventDefault();
evt.stopPropagation();
evt.currentTarget.disabled = true;
if (evt.currentTarget.hTimer) {
clearTimeout(evt.currentTarget.hTimer);
delete evt.currentTarget.hTimer;
}
evt.currentTarget.value = 'Uploading...';
evt.currentTarget.style.backgroundColor = '#A00000';
var evtSrc = evt.currentTarget;
upload2PTPIMG(evt.dataTransfer.files).then(function(results) {
if (urlParser.test(results[0])) {
image.value = results[0];
evtSrc.style.backgroundColor = '#008000';
if (prefs.auto_preview_cover/* && image.id*/) imagePreview(image, results[0], evt.dataTransfer.files[0].size);
evtSrc.hTimer = setTimeout(function() {
evtSrc.style.backgroundColor = null;
delete evtSrc.hTimer;
}, 10000);
} else evtSrc.style.backgroundColor = null;
}).catch(function(e) {
alert(e);
evtSrc.style.backgroundColor = null;
}).then(function() {
evtSrc.value = evtSrc.dataset.caption;
evtSrc.disabled = false;
});
}
function clear0(evt) { evt.target.value = '' }
function clear1(evt) { if (evt.buttons == 4) clear0(evt) }
function autoFill(evt) {
autofill = true;
setTimeout(fillFromText, prefs.autfill_delay);
}
function voidDragHandler(evt) { evt.preventDefault() }
function upload2PTPIMG(files, elem) {
try {
var apiKey = prefs.ptpimg_api_key || JSON.parse(window.localStorage.ptpimg_it).api_key;
if (!apiKey) throw new Error('API key not set');
} catch(e) { return Promise.reject(e) }
return new Promise(function(resolve, reject) {
var readers = Array.from(files).filter(function(file) {
return file instanceof File && file.type.startsWith('image/');
}).map(file => new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() { resolve({ file: file, data: reader.result }) };
reader.onerror = reader.onabort = function() { reject('FileReader error (' + file.name + ')') };
reader.readAsBinaryString(file);
}));
if (readers.length <= 0) resolve([]); else Promise.all(readers).then(function(results) {
const boundary = '------NN-GGn-PTPIMG';
var data = '--' + boundary + '\r\n';
results.forEach(function(result, ndx) {
data += 'Content-Disposition: form-data; name="file-upload[' + ndx +
']"; filename="' + result.file.name.toASCII() + '"\r\n';
data += 'Content-Type: ' + result.file.type + '\r\n\r\n';
data += result.data + '\r\n';
data += '--' + boundary + '\r\n';
});
data += 'Content-Disposition: form-data; name="api_key"\r\n\r\n';
data += apiKey + '\r\n';
data += '--' + boundary + '--\r\n';
GM_xmlhttpRequest({
method: 'POST',
url: 'https://ptpimg.me/upload.php',
responseType: 'json',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': data.length,
},
data: data,
binary: true,
onload: function(response) {
if (response.readyState == 4 && response.status == 200) {
resolve(response.response.map(item => 'https://ptpimg.me/' + item.code + '.' + item.ext));
} else {
reject('Response error ' + response.readyState + '/' + response.status + ' (' + response.statusText + ')');
}
},
onprogress: elem instanceof HTMLInputElement ?
arg => { elem.value = 'Uploading... (' + arg.position + '%)' } : undefined,
onerror: function() { reject('GM_xmlhttpRequest error') },
ontimeout: function() { reject('Timeout') },
});
});
});
}
function rehost2PTPIMG(urls) {
try {
if (!Array.isArray(urls)) throw new Error('Bad parameter (urls)');
var apiKey = prefs.ptpimg_api_key || JSON.parse(window.localStorage.ptpimg_it).api_key;
if (!apiKey) throw new Error('API key not set');
} catch(e) {
return Promise.reject(e);
}
return new Promise(function(resolve, reject) {
const boundary = '------NN-GGn-PTPIMG';
const dcTest = /^https?:\/\/(?:\w+\.)?discogs\.com\//i;
var data = '--' + boundary + '\r\n';
data += 'Content-Disposition: form-data; name="link-upload"\r\n\r\n';
data += urls.filter(url => urlParser.test(url))
.map(url => dcTest.test(url.trim()) ? 'https://reho.st/' + url.trim() : url.trim()).join('\r\n') + '\r\n';
data += '--' + boundary + '\r\n';
data += 'Content-Disposition: form-data; name="api_key"\r\n\r\n';
data += apiKey + '\r\n';
data += '--' + boundary + '--\r\n';
GM_xmlhttpRequest({
method: 'POST',
url: 'https://ptpimg.me/upload.php',
responseType: 'json',
headers: {
'Content-type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': data.length,
},
data: data,
onload: function(response) {
if (response.readyState == 4 && response.status == 200) {
resolve(response.response.map(item => 'https://ptpimg.me/' + item.code + '.' + item.ext));
} else {
reject('Response error ' + response.readyState + '/' + response.status + ' (' + response.statusText + ')');
}
},
onerror: function() { reject('GM_xmlhttpRequest error') },
ontimeout: function() { reject('Timeout') },
});
});
}