// ==UserScript==
// @name [RED/NWCD] Upload Assistant
// @namespace https://gf.qytechs.cn/users/321857-anakunda
// @version 1.203
// @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%),)]$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=$trim(%compilation%) ][DISCID=$trim(%DISCID%) ][ISRC=$trim(%ISRC%) ][EXPLICIT=$trim(%EXPLICIT%) ][ORIGINALFORMAT=$trim($replace(%ORIGINALFORMAT%, ,_)) ][ASIN=$trim(%ASIN%) ][DISCOGS_ID=$trim(%discogs_release_id%) ][MBR_ID=$trim(%MUSICBRAINZ_ALBUMID%) ][SOURCEID=$trim($replace(%SOURCEID%, ,_)) ][BPM=$trim(%BPM%) ][BARCODE=$trim($replace($if3(%barcode%,%UPC%,%EAN%,%MCN%), ,)) ])
//
// 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
// - indies.eu
// - beatport.com
// - traxsource.com
// - musicbrainz.org
//
// Ebooks releases:
// - martinus.cz, martinus.sk
// - goodreads.com
// - databazeknih.cz
//
// Application releases:
// - sanet.st
'use strict';
const isFirefox = /\b(?:Firefox)\b/.test(navigator.userAgent) || window.InstallTrigger;
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;
const imghostOrigin = 'https://ptpimg.me';
const descriptionFields = ['album_desc', 'body', 'description', 'release_desc', 'release_lineage'];
//const promiseAll = Promise.allSettled || Promise.all;
const spotify_clientid = '6d358a207c634b1ebac640149a6090da';
const spotify_clientsecret = '4c59880a4ec241ed9c89a24e66468c64';
const discogs_token = 'CISOUfiQctZCkUedWJzPhzTXxRYihifZgflZAfEm';
const lastfm_api_key = 'b9f26370d7266fbb3151b2ad4f7a74c9';
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('cleanup_descriptions', 1); // pre-submit cleanup to all description fields (remove empty placeholders, redundant info and garbage like empty tag pairs etc.)
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_rehost_cover', 1); // PTPIMG / using 3rd party script
prefs.set('auto_preview_cover', 1);
prefs.set('huge_image_warning', 5); // threshold in MB for making bandwith stressing cover size warning // 0 to disable
prefs.set('cover_lookup_provider', 'all'); // itunes | lastfm | deezer | qobuz | musicbrainz | google | all | empty for no lookup
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('check_integrity_online', 1); // If provided URL tag, compare local release with release online and lookup for discrepancies
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 (DSD2PCM direct / 64fp / 30kHz lowpass)');
prefs.set('ptpimg_api_key', '');
prefs.set('discogs_key', ''); // Applicxation/Consumer Key
prefs.set('discogs_secret', ''); // Application/Consumer Secret
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('tracklist_style', 1); // 1: classical, 2: propertional font right-justified
prefs.set('sort_tracklist', 1);
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); // PHPBB fonst size
prefs.set('title_separator', '. '); // divisor of track# and title
prefs.set('pad_leader', ' ');
prefs.set('bpm_summary', 1);
prefs.set('tracklist_head_color', '#62a6ad'); // #4682B4 / #a7bdd0
// classical tracklist only components colouring
prefs.set('tracklist_disctitle_color', '#2bb7b7'); // #bb831c
prefs.set('tracklist_work_color', '#98984d'); // #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;
font: 11px "Segoe UI", Calibri, sans-serif;
}
.ua-messages-bg { padding: 15px; text-align: left; background-color: darkslategray; }
.ua-critical { color: red; font-weight: bold; font-size: 13px; }
.ua-warning { color: #ff8d00; font-weight: 500; font-size: 12px; }
.ua-notice { color: #e3d67b; }
.ua-info { color: white; }
.ua-button { vertical-align: middle; background-color: transparent; }
.ua-button2 { /*color: beige; */width: 13em; font: 300 x-small "Segoe UI", Calibri, sans-serif; }
.ua-input {
font: 600 x-small "Segoe UI", Calibri, sans-serif;
background-color: antiquewhite;
width: 620px; height: 40px;
margin-top: 8px; margin-bottom: 8px;
}
#cover-preview {
width: 100%;
/*box-shadow: 3px 3px 3px;*/
}
#cover-size {
width: 100%;
color: white; background-color: #0a4a75;
font: 12px "Segoe UI", Calibri, sans-serif;
text-align: center;
/*padding-top: 5px;*/
}
::placeholder {
font: small "Segoe UI", Calibri, sans-serif;
opacity: 0.25;
/*text-shadow: 0px 0px 2px #000;*/
font-weight: bold;
}
`;
var ref, tbl, elem, child, rehostItBtn;
var spotifyCredentials = {}, discogsCredentials = {}, siteArtistsCache = {}, notSiteArtistsCache = [];
var messages = null, autofill = false, dom, domParser = new DOMParser();
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.className = 'ua-button2';
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.className = 'ua-button2';
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.className = 'ua-button2';
child.style.height = '52px';
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.className = 'ua-button2';
child.style.height = '52px';
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.className = 'ua-button2';
child.style.height = '52px';
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.className = 'ua-input';
child.spellcheck = false;
child.placeholder = 'Paste / drag & drop here selected album from foobar2000, or URL from supported site';
child.onpaste = uaInsert;
if (!isNWCD) child.ondrop = uaInsert; else child.ondrop = child.ondragstart = child.ondragover = function(evt) {
evt.preventDefault();
evt.stopPropagation();
return false;
};
if (isFirefox) child.oninput = fixFirefoxDropBug;
var desc = document.getElementById('body');
if (desc != null && urlParser.test(desc.value)) {
child.value = RegExp.$1;
desc.value = '';
if (prefs.autfill_delay > 0) {
autofill = true;
setTimeout(fillFromText, prefs.autfill_delay);
};
}
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('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';
setTimeout(setHandlers, 2000);
});
}
setHandlers();
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
&& Array.from(arr).sort().toString() == Array.from(this).sort().toString();
};
Array.prototype.homogeneous = 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');
};
String.prototype.titleCase = function() {
return this.toLowerCase().split(' ').map(x => x[0].toUpperCase() + x.slice(1)).join(' ');
};
String.prototype.collapseGaps = function() {
return this.replace(/(?:[ \t]*\r?\n){3,}/g, '\n\n').replace(/\[(\w+)\]\[\/\1\]/ig,'').trim();
};
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 = reader.ontimeout = error => { reject('FileReader error (' + this.name + ')') };
reader.readAsText(this, encoding);
}.bind(this));
};
class HTML extends String { };
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|Modern\s+Creative|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 Array.from(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, sourceUrl, 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 dcRlsParser = /^https?:\/\/(?:\w+\.)*discogs\.com\/releases?\/(\d+)(?=$|\/|\?)/i;
const mbrRlsParser = /^https?:\/\/musicbrainz\.org\/(?:\w+\/)*release\/([\w\-]+)/i;
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 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,
];
var onlineSource = urlParser.test(clipBoard.value) && RegExp.$1, isVA;
(onlineSource ? fetchOnline_Music(onlineSource) :
Promise.resolve(clipBoard.value.split(/(?:\r?\n)+/).filter(line => line.trim().length > 0).map(function(line) {
var metaData = line.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,
description: metaData.shift().trim() || undefined,
identifiers: {},
};
metaData.shift().trim().split(/\s+/).forEach(function(it) {
if (/([\w\-]+)[=:](.*)/.test(it)) track.identifiers[RegExp.$1.toUpperCase()] = RegExp.$2.replace(/\x1B/g, ' ');
});
return 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 }
}))
).then(parseTracks).catch(e => { if (e) addMessage(e, 'ua-critical') });
return;
function parseTracks(tracks) {
if (tracks.length <= 0) {
clipBoard.value = '';
throw 'FATAL: no tracks found';
}
var albumBitrate = 0, totalTime = 0, albumSize = 0, media, release = { totaldiscs: 1, srs: [] };
tracks.forEach(function(track) {
if (!track.artist) {
clipBoard.value = '';
throw new HTML('FATAL: main artist must be defined in every track' + ruleLink('2.3.16.4'));
}
if (!track.album) {
clipBoard.value = '';
throw new HTML('FATAL: album title must be defined in every track' + ruleLink('2.3.16.4'));
}
if (!track.tracknumber) {
clipBoard.value = '';
throw new HTML('FATAL: all track numbers must be defined' + ruleLink('2.3.16.4'));
}
if (!track.title) {
clipBoard.value = '';
throw new HTML('FATAL: all track titles must be defined' + ruleLink('2.3.16.4'));
}
if (track.duration != undefined && isUpload && (isNaN(track.duration) || track.duration <= 0)) {
clipBoard.value = '';
throw 'FATAL: invalid track #' + track.tracknumber + ' length: ' + track.duration;
}
if (track.codec && !['FLAC', 'MP3', 'AAC', 'DTS', 'AC3'].includes(track.codec)) {
clipBoard.value = '';
throw 'FATAL: disallowed codec present (' + track.codec + ')';
}
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.description == '.') track.description = undefined;
if (track.description) {
track.description = track.description.expand();
if (prefs.remap_texttools_newlines) track.description = track.description.replace(/__/g, '\r\n').replace(/_/g, '\n') // ambiguous
track.description = track.description.collapseGaps();
}
if (track.dr != null) track.dr = parseInt(track.dr); // DR0
totalTime += track.duration;
albumBitrate += track.bitrate * track.duration;
albumSize += track.filesize;
});
if (!tracks.every(it => it.discnumber > 0) && !tracks.every(it => !it.discnumber)) {
clipBoard.value = '';
throw 'FATAL: inconsistent release (mix of tracks with and without disc number)';
}
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 homogeneous = new Set(tracks.map(it => it[propName]).filter(it => it != undefined && it != null));
if (homogeneous.size > 1) {
var diverses = '', it = homogeneous.values(), val;
while (!(val = it.next()).done) diverses += '<br>\t' + val.value;
clipBoard.value = '';
throw new HTML('FATAL: mixed releases not accepted (' +
propNameLiteral + ') - supposedly user compilation' + diverses);
}
release[propName] = homogeneous.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('descriptions', 'description');
setProperty('genres', 'genre');
setProperty('urls', 'url');
setProperty('coverUrls', 'cover_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;
clipBoard.value = '';
throw 'FATAL: disallowed ' + str + ' present (' + arr.filter(validator) + ')';
}
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 albumBPM = Math.round(tracks.reduce(function(acc, track) {
return acc + parseInt(track.identifiers.BPM) * track.duration;
}, 0) / totalTime);
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, 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(new HTML('Warning: all tracks composers must be set for clasical music' + ruleLink('2.3.17')), 'ua-warning');
//return false;
}
// Processing artists: recognition, splitting and dividing to categores
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 artistStrips = [
/\s+(?:aka|AKA)\.?\s+(.*)/,
/\s+\([^\(\)]+\)$/,
/\s+\[[^\[\]]+\]$/,
/\s+\{[^\{\}]+\}$/,
];
const 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 = Array.from(artists[1]);
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, '');
}
});
if (!iter.remixer) 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(array) {
if (array) {
var result = [];
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.prototype.push.apply(result, j.filter(function(artist) {
return !array.includesCaseless(artist) && !pseudoArtistParsers.some(rx => rx.test(artist));
}));
} else result.push(array[i - 1]);
}
});
return result;
}
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);
});
if (splitAmpersands(trackArtists).equalTo(artists[0])
&& splitAmpersands(trackGuests).equalTo(albumGuests)) trackArtist = undefined;
}
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|Édition))\)$/i,
/\s+\[((?:Remaster(?:ed)?|Remasterizado|Remasterisée|Reissu(?:ed)?|Deluxe|Enhanced|Expanded|Limited|Version)\b[^\[\]]*|[^\[\]]*\b(?:Edition|Version|Promo|Release|Édition))\]$/i,
/\s+-\s+([^\[\]\(\)\-\−\—\–]*\b(?:(?:Remaster(?:ed)?|Remasterizado|Remasterisée|Bonus\s+Track)\b[^\[\]\(\)\-\−\—\–]*|Reissue|Edition|Version|Promo|Enhanced|Release|Édition))$/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 : '');
[/\s+\(([^\(\)]+)\)\s*$/, /\s+\[([^\[\]]+)\]\s*$/, /\s+\{([^\{\}]+)\}\s*$/].forEach(function(rx) {
var version = tracks.map(track => rx.test(track.title) ? RegExp.$1 : null);
version = version.homogeneous() && version[0] || undefined;
if (!editionTitle && /\b(?:Remastered|Remasterisée|Remasterizado|Acoustic|Instrumental)\b/i.test(version)) {
editionTitle = version;
}
if (!releaseType && /\b(?:Live)\b/i.test(version)) releaseType = getReleaseIndex('Live album');
});
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 ?
!isVA && release.label == release.artist || /^(?:independent|vlastní\s+náklad)$/i.test(release.label) ?
'self-released' : release.label.split(/\s*;\s*/g).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(' / ')
|| 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 (tracks.every(it => it.identifiers.ORIGINALFORMAT && it.identifiers.ORIGINALFORMAT.includes('DSD'))) {
isFromDSD = true;
}
// Release type
if (!releaseType) {
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 = [
/\(+(\([^\(\)]*\))\)+/,
/\[+(\[[^\[\]]*\])\]+/,
/\{+(\{[^\{\}]*\})\}+/,
];
tracks.forEach(function(track) {
doubleParsParsers.forEach(function(rx) {
if (!rx.test(track.title)) return;
addMessage('Warning: doubled parentheses in track #' +
track.tracknumber + ' title ("' + track.title + '")', 'ua-warning');
//track.title.replace(rx, RegExp.$1);
});
});
if (tracks.length > 1 && tracks.map(track => track.title).homogeneous()) {
addMessage('Warning: all tracks having same title: ' + tracks[0].title, 'ua-warning');
}
if (isUpload) findPreviousUploads();
// Album description
sourceUrl = 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'; if (tracks.length > 1) summary += 's';
if (totalTime > 0) summary += ', ' + makeTimeString(totalTime);
description.push(summary);
}
if (sourceUrl || release.urls.length > 0) description.push(getUrls());
if (release.catalogs.length == 1 && /^\d{10,}$/.test(release.catalogs[0]) || /^\d{10,}$/.test(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.descriptions.length > 0) Array.prototype.push.apply(description, release.descriptions);
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 = '';
if (prefs.bpm_summary && albumBPM > 0) {
if (description.length <= 0) description = '\n';
description += '\nAverage album BPM: [code]' + albumBPM + '[/code]';
}
/*if (release.release_date) {
let rd = new Date(release.release_date);
if (!isNaN(rd)) description = '\n\nRelease date: ' + rd.toDateString();
}*/
let vinylRipInfo;
if (release.descriptions.length > 0) {
description += '\n\n';
if (release.descriptions.length == 1 && release.descriptions[0]
&& (matches = vinylTest.exec(release.descriptions[0])) != null) {
vinylRipInfo = release.descriptions[0].slice(matches.index).trim().split(/(?:[ \t]*\r?\n)+/);
description += release.descriptions[0].slice(0, matches.index).trim();
} else description += release.descriptions.join('\n\n');
}
if (elementWritable(ref = document.getElementById('album_desc'))) {
ref.value = genPlaylist().concat(description);
preview(0);
}
if ((ref = document.getElementById('body') || document.querySelector('textarea[name="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
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' : '';
}
let lineage = '', rlsDesc = '';
let drInfo = '[hide=DR' + (release.drs.length == 1 ? release.drs[0] : '') + '][pre][/pre]';
let hasSR = Object.keys(release.srs).length > 0;
let srInfo = hasSR ? Object.keys(release.srs).sort((a, b) => release.srs[b] - release.srs[a])
.map(f => f / 1000).join('/').concat('kHz') : null;
if (release.bds.some(bd => bd > 16)) {
if (['Blu-Ray', 'DVD', 'SACD'].includes(media)) {
if (!isNWCD) rlsDesc = srInfo;
addChannelInfo();
if (media == 'SACD' || isFromDSD) addDSDInfo();
if (prefs.cleanup_descriptions) addDRInfo();
//addRGInfo();
addHybridInfo();
drInfo += '[/hide]';
} else if (media == 'Vinyl') {
let hassr = hasSR && (!isNWCD || Object.keys(release.srs).length > 1);
if (hassr) lineage = srInfo + ' ';
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(l => '\n'.concat([
// RuTracker translation
['Код класса состояния винила', 'Vinyl condition class'],
['Устройство воспроизведения', 'Turntable'],
['Головка звукоснимателя', 'Cartridge'],
['Картридж', 'Cartridge'],
['Предварительный усилитель', 'Preamplifier'],
['АЦП', 'ADC'],
['Программа-оцифровщик', 'Software'],
['Обработка', 'Post-processing'],
].reduce((acc, it) => acc.replace(it[0], it[1]), l))).join('');
} else lineage += (hassr ? 'Vinyl' : ' vinyl') + ' rip by [color=blue][/color]\n\n[u]Lineage:[/u]\n';
let imgs = '\n[img][/img]'.repeat(6);
if (!isNWCD) drInfo += '\n'.concat(imgs); else lineage += '\n\n[hide]'.concat(imgs.slice(1), '[/hide]');
drInfo += '[/hide]';
} else { // WEB Hi-Res
if (!isNWCD || Object.keys(release.srs).length > 1) rlsDesc = srInfo;
if (release.channels && release.channels != 2) addChannelInfo();
if (isFromDSD) addDSDInfo();
if (!isFromDSD || prefs.cleanup_descriptions) addDRInfo();
//addRGInfo();
addHybridInfo();
if (isFromDSD || prefs.cleanup_descriptions || Object.keys(release.srs).length == 1
&& Object.keys(release.srs)[0] == 88200) drInfo += '[/hide]'; else drInfo = null;
}
} else { // 16bit or lossy
if (Object.keys(release.srs).some(f => f != 44100)) rlsDesc = srInfo;
if (release.channels && release.channels != 2) addChannelInfo();
addDRInfo();
//addRGInfo();
if (prefs.cleanup_descriptions) drInfo += '[/hide]'; else drInfo = null;
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] + ')';
}
lineage = _encoder_settings;
}
}
function addDSDInfo() {
var nfo = ' DSD64';
if (prefs.sacd_decoder) nfo += ' using ' + prefs.sacd_decoder;
nfo += '\nOutput gain: [code]+0dB[/code]';
if (isNWCD) lineage = 'From' .concat(nfo); else {
if (rlsDesc.length > 0) rlsDesc += ' from'; else rlsDesc = 'From';
rlsDesc += nfo;
}
}
function addDRInfo() {
if (release.drs.length < 1 || document.getElementById('release_dynamicrange') != null) return;
var nfo = 'DR' + release.drs[0];
if (release.drs[0] < 4) nfo = '[color=red]'.concat(nfo, '[/color]');
if (rlsDesc.length > 0) rlsDesc += ' | ';
rlsDesc += nfo;
}
function addRGInfo() {
if (release.rgs.length != 1) return;
if (rlsDesc.length > 0) rlsDesc += ' | ';
rlsDesc += 'RG'; //rlsDesc += 'RG ' + rgs[0];
}
function addChannelInfo() {
if (!release.channels) return;
var chi = getChanString(release.channels);
if (chi.length <= 0) return;
if (rlsDesc.length > 0) rlsDesc += ', '; else rlsDesc = 'Channels configuration: ';
rlsDesc += chi;
}
function addHybridInfo() {
if (release.bds.length > 1) release.bds.filter(bd => bd != 24).forEach(function(bd) {
var 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 (rlsDesc.length > 0) rlsDesc += '\n';
rlsDesc += 'Note: track';
if (hybrid_tracks.length > 1) rlsDesc += 's';
rlsDesc += ' #' + hybrid_tracks.join(', ') +
(hybrid_tracks.length > 1 ? ' are' : ' is') + ' ' + bd + 'bit lossless';
});
}
rlsDesc = rlsDesc.length > 0 ? [rlsDesc] : [];
if ((ref = document.getElementById('release_lineage')) != null) {
lineage = lineage ? [lineage] : [];
if (drInfo) rlsDesc.push(drInfo);
if (sourceUrl || release.urls.length > 0) lineage.push(getUrls());
if (elementWritable(ref)) {
ref.value = lineage.join('\n\n');
preview(1);
}
} else {
if (lineage.length > 0) rlsDesc.push(lineage);
if (drInfo) rlsDesc.push(drInfo);
if (sourceUrl || release.urls.length > 0) rlsDesc.push(getUrls());
}
if (elementWritable(ref = document.getElementById('release_desc'))) {
ref.value = rlsDesc.join('\n\n');
if (rlsDesc.length > 0) preview(isNWCD ? 2 : 1);
}
if (release.encoding == 'lossless' && release.codec == 'FLAC'
&& release.bds.includes(24) && release.dirpaths.length == 1) {
if ((ref = document.getElementById('release_desc')) != null) GM_xmlhttpRequest({
method: 'GET',
url: new URL('file://'.concat(release.dirpaths[0], '\\foo_dr.txt')).href,
responseType: 'blob',
onload: function(response) {
if (response.readyState != 4 || response.status != 200) return;
if (/(\[hide=DR\d*\]\[pre\])\[\/pre\]/im.test(ref.value)) {
var ndx = RegExp.lastIndex + RegExp.$1.length;
ref.value = ref.value.slice(0, ndx).concat(response.responseText, ref.value.slice(ndx));
}
},
onerror: error => { console.error('foo_dr.txt not exists or is forbidden to read') },
onabort: abort => { console.error('XHR: abort') },
ontimeout: timeout => { console.error('XHR: timeout') },
});
}
}
if (!isNWCD && elementWritable(document.getElementById('image') || document.querySelector('input[name="image"]'))) {
if (release.coverUrls.length == 1) setCover(release.coverUrls[0]).catch(searchCoverOnline);
else getCoverOnline();
}
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 (!onlineSource && prefs.check_integrity_online) (sourceUrl || release.urls.length > 0 ?
fetchOnline_Music(sourceUrl || release.urls[0], true) : Promise.reject('No URL'))
.catch(reason => lookupOnlineSource().then(function(result) {
if (typeof result == 'object') return parseLastFm(result);
if (urlParser.test(result)) return fetchOnline_Music(result, true);
return Promise.reject('Unhandled result');
}))
.then(onlineCheck)
.catch(function(reason) {
if (!media || media == 'WEB') tracks.forEach(function(track) {
if (track.duration < 29.6 || iter.duration > 30.4) return;
addMessage('Warning: track ' + track.tracknumber + ' possible track preview', 'ua-warning');
});
});
if (prefs.clean_on_apply) clipBoard.value = '';
prefs.save();
return;
// ---------------------------------------------------------------------------------------------------------------
function genPlaylist(pad = true, header = true) {
var style = prefs.tracklist_style;
if (!style || style <= 0) return null;
if (style == 2 && (tracks.map(track => track.title).some(notMonospaced))
|| tracks.map(track => track.track_artist).some(notMonospaced)
|| composerEmphasis && tracks.map(track => track.composer).some(notMonospaced)) style = 1;
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).homogeneous()) classicalWorks.get(iter).performer = work[0].track_artist;
if (work.map(it => it.composer).homogeneous()) classicalWorks.get(iter).composer = work[0].composer;
} else {
work.forEach(function(track) {
delete track.classical_work;
delete track.classical_title;
});
classicalWorks.delete(iter);
}
}
}
let track, 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.toString())))
&& !tracks.every(it => vinyltrackParser.test(it.tracknumber.toString().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.toString().toUpperCase()) && parseInt(RegExp.$3), acc);
}, 0);
if (vinylTrackWidth) {
vinylTrackWidth = vinylTrackWidth.toString().length;
tracks.forEach(function(it) {
if (vinyltrackParser.test(it.tracknumber.toString().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]';
if (canSort && prefs.sort_tracklist) tracks.sort(trackComparer);
for (iter of tracks) {
var trackArtist = getRealTrackArtist(iter), title = '';
var 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 (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 (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 getUrls() {
var result = [];
if (sourceUrl) result.push(sourceUrl);
Array.prototype.push.apply(result, release.urls.filter(function(url) {
return !sourceUrl || url.toLowerCase() != sourceUrl.toLowerCase();
}));
return result.map(url => urlParser.test(url) ? '[url]' + url + '[/url]' : url).join('\n');
}
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 barcode() {
var r = getHomoIdentifier('BARCODE');
if (r) r = parseInt(r.replace(/\s+/g, ''));
if (r) return r;
if (release.catalogs.length == 1) r = parseInt(release.catalogs[0].replace(/[\s\-]/g, ''));
return r > 10**10 ? r : undefined;
}
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}/'],
['INDIESSCOPE_ID', 'https://www.indies.eu/alba/{ID}/'],
['BEATPORT_ID', 'https://www.beatport.com/release/2/{ID}'],
['TRAXSOURCE_ID', 'https://www.traxsource.com/title/{ID}/'],
['MBR_ID', 'https://musicbrainz.org/release/{ID}'],
['ASIN', 'https://www.amazon.com/gp/product/{ID}'],
['AMAZON_ID', 'https://www.amazon.com/gp/product/{ID}'],
]) {
let ID = getHomoIdentifier(it[0]);
if (ID) return it[1].replace('{ID}', ID);
}
return undefined;
}
function getCoverOnline() {
var url = sourceUrl || release.urls[0];
if (i = getHomoIdentifier('APPLE_ID') || getHomoIdentifier('ITUNES_ID')
|| /^https?:\/\/(?:\w+\.)*apple\.com\/\/.*\/(\d+)$/i.test(url) && RegExp.$1) {
return queryItunesAPI('lookup', { id: i })
.then(result => result.resultCount > 0 ? setItunesImage(result.results[0]) : Promise.reject('no cover'))
.catch(searchCoverOnline);
} else if (i = getHomoIdentifier('DEEZER_ID')
|| /^https:\/\/(?:\w+\.)*deezer\.com\/(\w+\/)*album\/(\d+)$/i.test(url) && RegExp.$1) {
return queryDeezerAPI('album/' + i)
.then(result => result.total > 0 ? setDeezerImage(result.data[0]) : Promise.reject('No cover'))
.catch(searchCoverOnline);
} else if ((prefs.discogs_key && prefs.discogs_secret || discogs_token)
&& (i = getHomoIdentifier('DISCOGS_ID') || dcRlsParser.test(url) && RegExp.$1)) {
return queryDiscogsAPI('releases/' + i).then(function(release) {
return release.images.length > 0 ? setCover(release.images[0].uri) : Promise.reject('no matches');
}).catch(searchCoverOnline);
} else if ((i = getHomoIdentifier('MBR_ID') || mbrRlsParser.test(url) && RegExp.$1)) {
return getMusicbrainzCovers(i).then(function(covers) {
return covers && covers[1].length > 0 ? setCover(covers[1][0]) : Promise.reject('no matches');
}).catch(searchCoverOnline);
} else if (url) GM_xmlhttpRequest({
method: 'GET',
url: dcRlsParser.test(url) ? 'https://www.discogs.com/release/' + RegExp.$1 + '/images' : url,
onload: function(response) {
if (response.readyState != 4 || response.status != 200) {
defaultErrorHandler(response);
return searchCoverOnline();
}
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) {
return setCover(ref.src.replace(/_\d{3}(?=\.\w+$)/, '_max'))
.catch(reason => setCover(ref.src)).catch(searchCoverOnline);
} else if ((ref = testDomain('highresaudio.com', 'div.albumbody > img.cover[data-pin-media]')) != null) {
return setCover(ref.dataset.pinMedia);
} else if ((ref = testDomain('bandcamp.com', 'div#tralbumArt > a.popupImage')) != null) {
return setCover(ref.href);
} else if ((ref = testDomain('7digital.com', 'span.release-packshot-image > img[itemprop="image"]')) != null) {
return setCover(ref.src);
} else if ((ref = testDomain('hdtracks.', 'p.product-image > img')) != null) {
return setCover(ref.src);
} else if ((ref = testDomain('discogs.com', 'div#view_images > p:first-of-type > span > img')) != null) {
return setCover(ref.src);
} else if ((ref = testDomain('prestomusic.com', 'div.c-product-block__aside > a')) != null) {
return setCover(ref.href.replace(/\?\d+$/, ''));
} else if ((ref = testDomain('bontonland.cz', 'a.detailzoom')) != null) {
return setCover(ref.href);
} else if ((ref = testDomain('nativedsd.com', 'a#album-cover')) != null) {
return setCover(ref.href);
} else if ((ref = testDomain('prostudiomasters.com', 'img.album-art')) != null) {
return setCover(ref.currentSrc || ref.src);
} else if ((ref = testDomain('e-onkyo.com', 'figure > a.colorbox')) != null) {
return setCover(new URL(response.finalUrl).origin + ref.pathname);
} else if ((ref = testDomain('store.acousticsounds.com', 'div#detail > link[rel="image_src"]')) != null) {
return setCover(ref.href.replace(/\/medium\//i, '/large/'));
} else if ((ref = testDomain('indies.eu', 'div.obrazekDetail > img')) != null) {
return setCover(ref.src);
} else if ((ref = testDomain('beatport.com', 'div > img.interior-release-chart-artwork')) != null) {
return setCover(ref.src);
} else if ((ref = testDomain('supraphonline.cz', 'meta[itemprop="image"]')) != null) {
return setCover(ref.content.replace(/\?.*$/, ''));
} else if ((ref = dom.querySelector('meta[property="og:image"]')
|| dom.querySelector('meta[itemprop="image"]')) != null && ref.content) {
return setCover(ref.content);
} else return searchCoverOnline();
},
onerror: function(error) {
defaultErrorHandler(error);
searchCoverOnline();
},
onabort: abort => { defaultAbortHandler(abort, false) },
ontimeout: function(timeout) {
defaultTimeoutHandler(timeout, false);
searchCoverOnline();
},
}); else return searchCoverOnline();
}
function searchCoverOnline() {
switch (typeof prefs.cover_lookup_provider == 'string' && prefs.cover_lookup_provider.toLowerCase()) {
case 'itunes': return searchCoverOnline_iTunes();
case 'deezer': return searchCoverOnline_Deezer();
case 'google': return searchCoverOnline_GooglePlay();
case 'musicbrainz': return searchCoverOnline_MBR();
case 'lastfm': return searchCoverOnline_LastFM();
case 'qobuz': return searchCoverOnline_Qobuz();
case 'all':
return searchCoverOnline_iTunes()
.catch(searchCoverOnline_LastFM)
.catch(searchCoverOnline_Deezer)
.catch(searchCoverOnline_MBR)
.catch(searchCoverOnline_Qobuz)
.catch(searchCoverOnline_GooglePlay);
}
return Promise.reject('no valid service selected');
function searchCoverOnline_iTunes() {
return queryItunesAPI('search', {
//country: 'US',
term: '"' + (isVA ? VA : release.artist) + '" "' + release.album + '"',
media: 'music',
entity: 'album',
}).then(function(result) {
if (result.resultCount <= 0) return Promise.reject('iTunes: no results');
return setItunesImage(result.results[0])
.then(release => { info('iTunes', release.collectionViewUrl, release.collectionId) });
});
}
function searchCoverOnline_Deezer() {
return queryDeezerAPI('search', {
q: 'artist:"' + (isVA ? VA : release.artist) + '" album:"' + release.album + '"',
strict: 'on',
order: 'RANKING',
}).then(function(result) {
if (result.total <= 0) return Promise.reject('Deezer: no results');
return setDeezerImage(result.data[0])
.then(release => { info('Deezer', 'https://www.deezer.com/album/'+ release.id, release.id) });
});
}
function searchCoverOnline_GooglePlay() {
return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET',
url: 'https://play.google.com/store/search?' + new URLSearchParams({
q: '"' + (isVA ? VA : release.artist) + '" "' + release.album + '"',
c: 'music',
}).toString(),
onload: function(response) {
if (response.readyState != 4 || response.status != 200) return reject(defaulErrorHandler(response));
var dom = domParser.parseFromString(response.responseText, 'text/html');
resolve(dom.querySelectorAll('div:first-of-type + div[jscontroller]:last-of-type'));
},
onerror: error => reject(defaultErrorHandler(error)),
onabort: abort => reject(defaultAbortHandler(abort)),
ontimeout: timeout => reject(defaultTimeoutHandler(timeout)),
})).then(function(results) {
if (results.length > 0) for (var ndx = 0; ndx < results.length; ++ndx) {
let items = [];
results[ndx].querySelectorAll(':scope > div').forEach(function(result) {
var img = result.querySelector('span > span > img');
img = img != null ? (img.src || img.dataset.src).replace(/=[a-z]\d+$/, '=w0') : null;
var title = result.querySelector('a > div[title]');
if (title != null) {
var artist = title.parentNode.parentNode.parentNode.querySelector('a > div:not([title])')
artist = artist != null ? artist.textContent.trim() : null;
var url = title.parentNode.href;
var id = /\?id=(\w+)\b/i.test(title.parentNode.href) && RegExp.$1 || null;
title = title.textContent.trim();
} else title = null;
items.push({ id: id, url: url, artist: artist, album: title, imgUrl: img });
});
if (items.length > 0 && items[0].imgUrl) return setCover(items[0].imgUrl)
.then(release => { info('Google Play Music', items[0].url, items[0].id) });
}
return Promise.reject('Google Play Music: no matches');
});
}
function searchCoverOnline_MBR() {
var barCode = barcode();
(barCode ? queryMusicbrainzAPI('release', { query: 'barcode:' + barCode }) : Promise.reject('No barcode'))
.catch(function(reason) {
var asin = getHomoIdentifier('ASIN');
if (!asin) return Promise.rečject('No ASIN');
return queryMusicbrainzAPI('release', { query: 'asin:' + asin.replace(/\s+/g, '') });
}).catch(reason => queryMusicbrainzAPI('release', {
query: 'release:"' + release.album + '" AND artist:"' + (isVA ? VA : release.artist) + '"',
})).then(function(result) {
if (result.count <= 0) return Promise.reject('Musicbrainz: no matches');
return Promise.all(result.releases.map(release => getMusicbrainzCovers(release.id)));
}).then(function(releases) {
for (var rls of releases) if (rls && rls[1].length > 0) return setCover(rls[1][0]).then(() => {
if (/\/release\/(\S+)$/i.test(rls[0])) info('Musicbrains', rls[0], RegExp.$1);
});
return Promise.reject('Musicbrainz: no covers found');
});
}
function searchCoverOnline_LastFM() {
return queryLastFmAPI('album.getinfo', {
artist: (isVA ? VA : release.artist),
album: release.album,
}).then(function(result) {
if (result.error) return Promise.reject(result.message);
var r = result.album.image.filter(image => image.size == /*'extralarge'*/'mega');
if (r.length <= 0) return Promise.reject('Last.fm: no cover for matched album');
r = r[0]['#text'];
if (!r) return Promise.reject('Last.fm: no cover for matched album');
return setCover(r.replace(/\/\d+x\d+\//, '/')).catch(reason => setCover(r))
.then(() => { info('Last.fm', result.album.url, result.album.id || result.album.mbid || '#N/A') });
});
}
function searchCoverOnline_Qobuz() {
return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET',
url: 'https://www.qobuz.com/search?' + new URLSearchParams({
q: (isVA ? VA : release.artist) + ' ' + release.album,
i: 'boutique',
}).toString(),
onload: function(response) {
if (response.readyState != 4 || response.status != 200) return reject(defaultErrorHandler(response));
var dom = domParser.parseFromString(response.responseText, 'text/html');
var results = dom.querySelectorAll('div.search-results > div.product');
if (results.length <= 0) return reject('Qobuz: no matches');
resolve(results[0]);
},
onerror: error => reject(defaultErrorHandler(error)),
onabort: abort => reject(defaultAbortHandler(abort)),
ontimeout: timeout => reject(defaultTimeoutHandler(timeout)),
})).then(function(album) {
var img = album.querySelector('div.album-cover > a > img');
if (img == null) return Promise.reject('Qobuz: invalid page structure or missing cover for matched album');
setCover((img.dataset.src || img.src).replace(/_\d+(?=\.\w+$)/, '_max'))
.catch(reason => setCover((img.dataset.src || img.src).replace(/_\d+(?=\.\w+$)/, '_600')))
.catch(reason => setCover(img.dataset.src || img.src))
.then(function(imgUrl) {
var a = album.querySelector('div.album-title > a');
if (a != null) info('Qobuz', 'https://www.qobuz.com' + new URL(a.href).pathname,
a.href.replace(/^.*\//, ''));
return imgUrl;
});
});
}
function info(service, url, id) {
addMessage(new HTML('Info: used cover image from ' + service + ' release ID ' +
'<a style="color: #00f3ff;" target="_blank" href="'+ url + '">' + id + '</a>'), 'ua-info');
}
}
function setItunesImage(result) {
return setCover(result.artworkUrl100.replace('100x100bb', '100000x100000-999')).then(() => result);
}
function setDeezerImage(result) {
return setCover(result.album.cover_xl.replace('1000x1000-000000-80-0-0', '1400x1400-000000-100-0-0'))
.then(() => result.album);
}
function onlineCheck(onlineTracks) {
if (!Array.isArray(onlineTracks) || onlineTracks.length <= 0) return Promise.reject('No tracklist');
if ((release.artist || '').toLowerCase() != (onlineTracks[0].artist || '').toLowerCase()
&& onlineTracks.map(track => track.artist).homogeneous()) {
addMessage(new HTML('Notice: online album main artist mismatch ("' +
safeText(release.artist).bold() + '" ≠ "' + safeText(onlineTracks[0].artist).bold() + '")'), 'ua-notice');
}
if ((release.album || '').toLowerCase() != (onlineTracks[0].album || '').toLowerCase()
&& onlineTracks.map(track => track.album).homogeneous()) {
addMessage(new HTML('Notice: online album title mismatch ("' +
safeText(release.album).bold() + '" ≠ "' + safeText(onlineTracks[0].album).bold() + '")'), 'ua-notice');
}
if (onlineTracks[0].label && onlineTracks.map(track => track.label).homogeneous()
&& (release.label || '').toASCII().toLowerCase().replace(/-/g, '/') != onlineTracks[0].label.toASCII().toLowerCase().replace(/-/g, '/')) {
addMessage(new HTML('Notice: online album label mismatch ("' +
safeText(release.label).bold() + '" ≠ "' + safeText(onlineTracks[0].label).bold() + '")'), 'ua-notice');
}
if (onlineTracks[0].album_year && onlineTracks.map(track => track.album_year).homogeneous()
&& release.album_year != onlineTracks[0].album_year) {
addMessage(new HTML('Warning: online album year mismatch (' +
(release.album_year || '').toString().bold() + ' ≠ ' + onlineTracks[0].album_year.toString().bold() + ')'), 'ua-warning');
}
if (onlineTracks[0].release_date && onlineTracks.map(track => track.release_date).homogeneous()
&& new Date(release.release_date.toString()).getDateValue()
!= new Date(onlineTracks[0].release_date.toString()).getDateValue()) {
addMessage(new HTML('Notice: online album release date mismatch (' +
(release.release_date || '').toString().bold() + ' ≠ ' + onlineTracks[0].release_date.toString().bold() + ')'), 'ua-notice');
}
if (tracks.length != onlineTracks.length) {
addMessage(new HTML('Warning: online album different tracklist length (' + tracks.length.toString().bold() +
' ≠ ' + onlineTracks.length.toString().bold() + ')'), 'ua-warning');
}
if (totalTime > 0) {
let ttOnline = onlineTracks.reduce((acc, track) => acc + (track.duration || NaN), 0);
if (ttOnline > 0 && Math.abs(totalTime - ttOnline) * 100 / ttOnline > (media != 'Vinyl' ? 0.75 : 2.5)) {
addMessage(new HTML('Warning: online album duration mismatch (' + makeTimeString(totalTime).bold() +
' ≠ ' + makeTimeString(ttOnline).bold() + ')'), 'ua-warning');
}
}
if (tracks.length == onlineTracks.length) for (let ndx = 0; ndx < tracks.length; ++ndx) {
if ((tracks[ndx].title || '').toLowerCase() != (onlineTracks[ndx].title || '').toLowerCase()) {
addMessage('Notice: online track #' + (ndx + 1) + ' title mismatch ("' +
(tracks[ndx].title || '') + '" ≠ "' + (onlineTracks[ndx].title || '') + '")', 'ua-notice');
}
if (tracks[ndx].tracknumber != onlineTracks[ndx].tracknumber) {
addMessage('Warning: online track #' + (ndx + 1) + ' track number mismatch (' +
tracks[ndx].tracknumber + ' ≠ ' + onlineTracks[ndx].tracknumber + ')', 'ua-warning');
}
if (onlineTracks[ndx].discnumber && (onlineTracks[ndx].discnumber > 1 || tracks[ndx].discnumber)
&& tracks[ndx].discnumber != onlineTracks[ndx].discnumber) {
addMessage('Warning: online track #' + (ndx + 1) + ' disc number mismatch (' +
tracks[ndx].discnumber + ' ≠ ' + onlineTracks[ndx].discnumber + ')', 'ua-warning');
}
if (onlineTracks[ndx].track_artist
&& (tracks[ndx].track_artist || '').toLowerCase() != onlineTracks[ndx].track_artist.toLowerCase()) {
addMessage('Notice: online track #' + (ndx + 1) + ' track artist mismatch ("' +
(tracks[ndx].track_artist || '') + '" ≠ "' + (onlineTracks[ndx].track_artist || '') + '")', 'ua-notice');
}
if (onlineTracks[ndx].discsubtitle
&& (tracks[ndx].discsubtitle || '').toLowerCase() != onlineTracks[ndx].discsubtitle.toLowerCase()) {
addMessage('Notice: online track #' + (ndx + 1) + ' disc subtitle mismatch ("' +
(tracks[ndx].discsubtitle || '') + '" ≠ "' + (onlineTracks[ndx].discsubtitle || '') + '")', 'ua-notice');
}
let timeDif = tracks[ndx].duration && onlineTracks[ndx].duration
&& Math.abs(tracks[ndx].duration - onlineTracks[ndx].duration);
if (timeDif >= (media != 'Vinyl' ? 1.5 : 5)) {
addMessage((timeDif >= (media != 'Vinyl' ? 3 : 8) ? 'Warning' : 'Notice') + ': online track #' + (ndx + 1) + ' duration mismatch (' +
makeTimeString(tracks[ndx].duration) + ' ≠ ' + makeTimeString(onlineTracks[ndx].duration) +
')', (timeDif >= (media != 'Vinyl' ? 3 : 8) ? 'ua-warning' : 'ua-notice'));
}
}
}
function lookupOnlineSource() {
const mbrRlsPrefix = 'https://musicbrainz.org/release/';
var albumTitle = release.artist && release.album;
var barCode = barcode();
return (barCode ? querySpotifyAPI('search', { q: 'barcode:' + barCode, type: 'album' }).then(function(result) {
if (result.albums.total <= 0) return Promise.reject('Spotify: no matches');
if (result.albums.total > 1) return Promise.reject('Spotify: ambiguity');
info('Spotify', result.albums.items[0].external_urls.spotify, result.albums.items[0].id);
return result.albums.items[0].href;
}) : Promise.reject('No barcode')).catch(reason => reason == 'Spotify: ambiguity' ? Promise.reject(reason) :
albumTitle && (!media || ['CD', 'WEB'].includes(media)) ? querySpotifyAPI('search', {
q: 'artist:"' + release.artist + '" album:"' + release.album + '"',
type: 'album',
limit: 50,
}).then(function(result) {
if (result.albums.total <= 0) return Promise.reject('No matches');
var f = filter(true);
if (f.length > 1) return Promise.reject('Spotify: ambiguity');
if (f.length < 1) {
f = filter(false);
if (f.length > 1) return Promise.reject('Spotify: ambiguity');
if (f.length < 1) return Promise.reject('Spotify: no matches');
}
info('Spotify', f[0].external_urls.spotify, f[0].id);
return f[0].href;
function filter(strict) {
return result.albums.items.filter(function(album) {
return releasesCompare(album.artists.map(artist => artist.name), album.name, strict)
&& (release.release_type == getReleaseIndex('Single') ?
album.album_type == 'single' : album.album_type != 'single');
})
}
}) : Promise.reject('Insufficient information')).catch(reason => barCode ?
queryMusicbrainzAPI('release', { query: 'barcode:' + barCode }).then(MB_simpleCheck)
: Promise.reject('No barcode')).catch(function(reason) {
if (reason == 'Musicbrainz: ambiguity') return Promise.reject(reason);
var asin = getHomoIdentifier('ASIN');
if (!asin) return Promise.rečject('Musicbrainz: no matches');
return queryMusicbrainzAPI('release', { query: 'asin:' + asin.replace(/\s+/g, '') }).then(MB_simpleCheck);
}).catch(reason => albumTitle && (!media || ['CD', 'WEB'].includes(media))
&& (!release.totaldiscs || release.totaldiscs < 2) ? queryDeezerAPI('search', {
q: 'artist:"' + release.artist + '" album:"' + release.album + '"',
strict: 'on',
order: 'RANKING',
}).then(function(result) {
if (result.total <= 0) return Promise.reject('No matches');
var f = filter(true);
if (f.length > 1) return Promise.reject('Ambiguity');
if (f.length < 1) {
f = filter(false);
if (f.length > 1) return Promise.reject('Ambiguity');
if (f.length < 1) return Promise.reject('No matches');
}
info('Deezer', 'https://www.deezer.com/album/' + f[0].id, f[0].id);
return Promise.resolve('https://api.deezer.com/album/' + f[0].id);
function filter(strict) {
var albums = new Map();
result.data.forEach(function(match) {
if (!releasesCompare(match.artist.name, match.album.title, strict)) return;
if (!albums.has(match.album.id)) albums.set(match.album.id, match.album);
});
return Array.from(albums.values());
}
}) : Promise.reject('Insufficient information')).catch(function(reason) {
if (reason == 'Musicbrainz: ambiguity') return Promise.reject(reason);
if (!albumTitle || !media) return Promise.reject('Musicbrainz: no matches');
return queryMusicbrainzAPI('release', {
query: 'release:"' + release.album + '" AND artist:"' + (isVA ? VA : release.artist) + '"',
}).then(function(result) {
if (result.count < 1) return Promise.reject('Musicbrainz: no matches');
var f = filter(true);
if (f.length > 1) return Promise.reject('Musicbrainz: ambiguity');
if (f.length < 1) {
f = filter(false);
if (f.length > 1) return Promise.reject('Musicbrainz: ambiguity');
if (f.length < 1) return Promise.reject('Musicbrainz: no matches');
}
info('Musicbrainz', mbrRlsPrefix + f[0].id, f[0].id);
return mbrRlsPrefix + f[0].id;
function filter(strict) {
return result.releases.filter(function(album) {
return releasesCompare(release['artist-credit'].map(artist => artist.name), album.title, strict);
});
}
});
}).catch(reason => albumTitle && (!media || ['CD', 'WEB'].includes(media))
&& (!release.totaldiscs || release.totaldiscs < 2) ? queryLastFmAPI('album.getinfo', {
artist: (isVA ? VA : release.artist),
album: release.album,
}).then(function(result) {
if (result.error) return Promise.reject(result.message)
info('Last.fm', result.album.url, result.album.id || result.album.mbid || '#N/A');
return result.album;
}) : Promise.reject('Insufficient information')).catch(function(reason) {
if (!albumTitle && !barCode || !media) return Promise.reject('Insufficient information');
var query = { type: 'release' };
if (barCode) query.barcode = barCode; else {
query.artist = '"' + release.artist + '"';
query.release_title = '"' + release.album + '"';
//if (release.catalogs.length > 0) query.catno = release.catalogs.join('; ');
}
return queryDiscogsAPI('database/search', query).then(function(result) {
if (result.results.length <= 0) return Promise.reject('Discogs: no matches');
if (barCode) {
if (result.results.length > 1) return Promise.reject('Discogs: ambiguity');
var f = result.results;
} else {
f = filter(true);
if (f.length <= 0) return Promise.reject('Discogs: no matches');
if (f.length > 1) return Promise.reject('Discogs: ambiguity');
}
info('Discogs', 'https://www.discogs.com' + f[0].uri. f[0].id);
return f[0].resource_url;
function filter(caseless) {
var title2 = caseless ? release.artist.toLowerCase() + ' - ' + release.album.toLowerCase()
: release.artist + ' - ' + release.album;
return result.results.filter(function(album) {
return (caseless ? album.title.toLowerCase() : album.title).replace(/\s+\(\d+\)(?= - )/, '') == title2
&& (!Array.isArray(album.format) || album.format.some(format => dcFmtToGazelle(format) === media));
})
}
});
}).catch(function(reason) {
reason = 'online check not performed (no matches for this release)';
addMessage('Notice: ' + reason, 'ua-notice');
return Promise.reject(reason);
});
function info(service, url, id) {
addMessage(new HTML('Info: checking online against ' + service +
' release ID <a style="color: #00f3ff;" target="_blank" href="' + url + '">' + id + '</a>'), 'ua-info');
}
function MB_simpleCheck(result) {
if (result.count < 1) return Promise.reject('Musicbrainz: no matches');
if (result.count > 1) return Promise.reject('Musicbrainz: ambiguity');
info('Musicbrainz', mbrRlsPrefix + result.releases[0].id, result.releases[0].id);
return Promise.resolve(mbrRlsPrefix + result.releases[0].id);
}
}
function ruleLink(rule) {
return ' (<a href="https://redacted.ch/rules.php?p=upload#r' + rule + '" target="_blank">' + rule + '</a>)';
}
function releasesCompare(remoteArtist, remoteTitle, strict = true) {
var localArtist = (isVA ? VA : release.artist.toLowerCase()).toLowerCase();
if ((typeof remoteArtist == 'string' && localArtist != remoteArtist.toLowerCase()
|| Array.isArray(remoteArtist) && localArtist != remoteArtist.join(', ').toLowerCase()
&& localArtist != remoteArtist.join(' & ').toLowerCase()
&& localArtist != remoteArtist.join(' and ').toLowerCase()
&& localArtist != joinArtists(remoteArtist).toLowerCase())
&& !artists[0].equalTo(Array.isArray(remoteArtist) ? remoteArtist : (remoteArtist = splitArtists(remoteArtist)))
&& !artists[0].equalTo(splitAmpersands(remoteArtist))) return false;
var localTitle = release.album.toLowerCase();
if (localTitle == (remoteTitle = remoteTitle.toLowerCase())) return true;
if (strict) return false;
return localTitle.includes(remoteTitle) || remoteTitle.includes(localTitle);
}
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 reqSelectFormats(...vals) {
vals.forEach(function(val) {
['MP3', 'FLAC', 'AAC', 'AC3', 'DTS'].forEach(function(fmt, ndx) {
if (val.toLowerCase() == fmt.toLowerCase() && (ref = document.getElementById('format_' + ndx)) != null) {
ref.checked = true;
ref.onchange();
}
});
});
}
function reqSelectBitrates(...vals) {
vals.forEach(function(val) {
var ndx = 10;
[
192, 'APS (VBR)', 'V2 (VBR)', 'V1 (VBR)', 256, 'APX (VBR)',
'V0 (VBR)', 320, 'Lossless', '24bit Lossless', 'Other',
].forEach(function(it, _ndx) {
if ((typeof val == 'string' ? val.toLowerCase() : val)
== (typeof it == 'string' ? it.toLowerCase() : it)) ndx = _ndx;
});
if ((ref = document.getElementById('bitrate_' + ndx)) != null) {
ref.checked = true;
ref.onchange();
}
});
}
function reqSelectMedias(...vals) {
vals.forEach(function(val) {
['CD', 'DVD', 'Vinyl', 'Soundboard', 'SACD', 'DAT', 'Cassette', 'WEB', 'Blu-Ray'].forEach(function(med, ndx) {
if (val == med && (ref = document.getElementById('media_' + ndx)) != 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';
}
} // parseTracks
function fetchOnline_Music(url, weak = false) {
if (!urlParser.test(url)) return Promise.reject('Invalid URL');
const discParser = /^(?:CD|DIS[CK]\s+|VOLUME\s+|DISCO\s+|DISQUE\s+)(\d+)(?:\s+of\s+(\d+))?$/i;
var ref, dom, 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, media = 'WEB', imgUrl,
genres = [], trs, tracks = [], identifiers = {}, trackIdentifiers = {};
if (url.toLowerCase().includes('qobuz.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, onload: function(response) {
prologue(response);
const error = new Error('Failed to parse Qobus release page');
var 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');
if (ref != null) albumYear = extractYear(ref.textContent);
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 (pseudoArtistParsers.some(rx => rx.test(composer))) composer = undefined;
} 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; // defaults to CD quality
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) {
if (description) description += '\n';
description += '[align=center][url=https://www.qobuz.com' + ref.pathname +
'][img]https://ptpimg.me/4z35uj.png[/img][/url][/align]';
}
if ((ref = dom.querySelector('div.album-cover > img')) != null) {
imgUrl = ref.src.replace(/_\d{3}(?=\.\w+$)/, '_max');
}
trs = dom.querySelectorAll('div.player__item > div.player__tracks > div.track > div.track__items');
if (!totalTracks) totalTracks = trs.length;
resolve(Array.from(trs).map(function(tr) {
discSubtitle = discNumber = undefined;
trackIdentifiers = { TRACK_ID: tr.parentNode.dataset.track };
if (tr.parentNode.dataset.gtm) try {
let gtm = JSON.parse(tr.parentNode.dataset.gtm);
if (gtm.product.id) trackIdentifiers.QOBUZ_ID = gtm.product.id;
//if (gtm.product.type) trackIdentifiers.RELEASETYPE = gtm.product.type;
if (gtm.product.subCategory) var subCategory = [gtm.product.subCategory];
} catch(e) { console.warn(e) }
if ((ref = tr.parentNode.parentNode.parentNode.querySelector('p.player__work:first-child')) != null) {
discSubtitle = ref.textContent.trim();
guessDiscNumber();
}
return {
artist: artist,
album: album,
album_year: albumYear,
release_date: releaseDate,
label: label,
encoding: 'lossless',
codec: 'FLAC',
bd: bd || undefined,
sr: sr * 1000 || undefined,
channels: channels || undefined,
media: media,
genre: genres.join('; '),
discnumber: discNumber,
totaldiscs: totalDiscs,
discsubtitle: discSubtitle,
tracknumber: parseInt(tr.querySelector('span[itemprop="position"]').textContent),
totaltracks: totalTracks,
title: tr.querySelector('span.track__item--name').textContent.trim().replace(/\s+/g, ' '),
composer: composer,
duration: timeStringToTime(tr.querySelector('span.track__item--duration').textContent),
url: response.finalUrl,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
};
}));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('highresaudio.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, onload: function(response) {
prologue(response);
if ((ref = dom.querySelector('h1 > span.artist')) != null) artist = ref.textContent.trim();
if ((ref = dom.getElementById('h1-album-title')) != 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 = extractYear(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(format) {
if (!/^(FLAC)\s*(\d+(?:[\.\,]\d+)?)\b/.test(format.textContent)) return;
format = RegExp.$1;
sr = parseFloat(RegExp.$2.replace(',', '.'));
++i;
});
if (i > 1) sr = undefined; // ambiguous
getDescFromNode('div#albumtab-info > p', response.finalUrl);
if ((ref = dom.querySelector('div.albumbody > img.cover[data-pin-media]')) != null) imgUrl = ref.dataset.pinMedia;
trs = dom.querySelectorAll('ul.playlist > li.pltrack');
resolve(Array.from(trs).map(function(tr) {
discNumber = undefined; discSubtitle = tr;
while ((discSubtitle = discSubtitle.previousElementSibling) != null) {
if (discSubtitle.nodeName == 'LI' && discSubtitle.className == 'plinfo') {
discSubtitle = discSubtitle.textContent.replace(/\s*:$/, '').trim();
guessDiscNumber();
break;
}
}
return {
artist: artist,
album: album,
album_year: albumYear,
release_date: releaseDate,
label: label,
encoding: 'lossless',
codec: 'FLAC',
bd: 24,
sr: sr * 1000,
media: media,
genre: genres.join('; '),
discnumber: discNumber,
totaldiscs: totalDiscs,
discsubtitle: discSubtitle || undefined,
tracknumber: parseInt(tr.querySelector('span.track').textContent),
totaltracks: trs.length,
title: tr.querySelector('span.title').textContent.trim().replace(/\s+/g, ' '),
duration: timeStringToTime(tr.querySelector('span.time').textContent),
url: response.finalUrl,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
};
}));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('bandcamp.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, onload: function(response) {
prologue(response);
if ((ref = dom.querySelector('span[itemprop="byArtist"] > a')) != null) artist = ref.textContent.trim();
if ((ref = dom.querySelector('h2[itemprop="name"]')) != 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;
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');
if ((ref = dom.querySelector('div#tralbumArt > a.popupImage')) != null) imgUrl = ref.href;
trs = dom.querySelectorAll('table.track_list > tbody > tr[itemprop="tracks"]');
resolve(Array.from(trs).map(tr => ({
artist: artist,
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
media: media,
genre: tags.toString(),
discnumber: discNumber,
totaldiscs: totalDiscs,
tracknumber: parseInt(tr.querySelector('div.track_number').textContent),
totaltracks: trs.length,
title: (tr.querySelector('div.title span.track-title')
|| tr.querySelector('div.title span[itemprop="name"]')).textContent.trim().replace(/\s+/g, ' '),
duration: (ref = tr.querySelector('span.time')) != null && timeStringToTime(ref.textContent) || undefined,
url: response.finalUrl,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
})));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('prestomusic.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, onload: function(response) {
prologue(response);
artist = getArtists(dom.querySelectorAll('div.c-product-block__contributors > p'));
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if (isVA) artist = [];
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(li) {
if (li.firstChild.textContent.includes('Release Date')) {
releaseDate = extractYear(li.lastChild.textContent);
} else if (li.firstChild.textContent.includes('Label')) {
label = li.lastChild.textContent.trim();
} else if (li.firstChild.textContent.includes('Catalogue No')) {
catalogue = li.lastChild.textContent.trim();
}
});
composer = [];
dom.querySelectorAll('div#related > div > ul > li').forEach(function(li) {
if (li.parentNode.previousElementSibling.textContent.includes('Composers')) {
composer.push(li.firstChild.textContent.trim().replace(/^(.*?)\s*,\s+(.*)$/, '$2 $1'));
}
});
composer = composer.join(', ') || undefined;
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);
if ((ref = dom.querySelector('div.c-product-block__aside > a')) != null) imgUrl = ref.href.replace(/\?\d+$/, '');
trs = dom.querySelectorAll('div.has--sample');
trackNumber = 0;
resolve(Array.from(trs).map(function(tr) {
discNumber = discSubtitle = undefined;
var parent = tr;
if (tr.classList.contains('c-track')) {
parent = tr.parentNode.parentNode;
if (parent.classList.contains('c-expander')) parent = parent.parentNode;
if ((ref = parent.querySelector(':scope > div > div > div > p.c-track__title')) != null) {
discSubtitle = ref.textContent.trim().replace(/\s+/g, ' ');
guessDiscNumber();
}
}
trackArtist = getArtists(parent.querySelectorAll(':scope > div.c-track__details > ul > li'));
if (trackArtist.equalTo(artist)) trackArtist = [];
return {
artist: isVA ? VA : artist.join('; '),
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
catalog: catalogue,
media: 'WEB',
genre: genres,
discnumber: discNumber,
totaldiscs: totalDiscs,
discsubtitle: discSubtitle,
tracknumber: ++trackNumber,
totaltracks: trs.length,
title: (ref = tr.querySelector('p.c-track__title')) ? ref.textContent.trim().replace(/\s+/g, ' ') : undefined,
track_artist: joinArtists(trackArtist),
composer: composer,
duration: timeStringToTime(tr.querySelector('div.c-track__duration').textContent),
url: response.finalUrl,
description: description,
identifiers: mergeIds(),
};
}));
function getArtists(nodeList) {
var artists = [];
nodeList.forEach(function(_artists) {
_artists = _artists.textContent.trim();
if (_artists.startsWith('Record')) return;
splitArtists(_artists).forEach(artist => { artists.push(artist.replace(/\s*\([^\(\)]*\)$/, '')) });
});
return artists.filter(artist => artist.length > 0);
}
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('discogs.com/') && /\/releases?\/(\d+)\b/i.test(url)) {
return queryDiscogsAPI('releases/' + RegExp.$1).then(function(release) {
const removeArtistNdx = /\s*\(\d+\)$/;
const editionTest = /^(?:.+?\s+Edition|Remaster(?:ed)|Remasterizado|Remasterisée|Reissue|.+?\s+Release|Enhanced|Promo)$/;
media = undefined;
identifiers.DISCOGS_ID = release.id;
var master = release.master_url ? new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET',
url: release.master_url,
responseType: 'json',
onload: function(response) {
if (response.readyState == 4 && response.status == 200) resolve(response.response);
else reject(defaultErrorHandler(response));
},
})) : Promise.reject('master release not available');
var albumArtists = getArtists(release);
if (albumArtists[0].length > 0) {
artist = albumArtists[0].join('; ');
if (albumArtists[1].length > 0) artist += ' feat. ' + albumArtists[1].join('; ');
}
album = release.title;
var editions = [];
label = []; catalogue = [];
release.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 (release.companies && release.companies.length > 0) {
description = '[b]Companies, etc.[/b]\n';
let type_names = new Set(release.companies.map(it => it.entity_type_name));
type_names.forEach(function(type_name) {
description += '\n' + type_name + ' – ' + release.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 (release.extraartists && release.extraartists.length > 0) {
if (description) description += '\n\n';
description += '[b]Credits[/b]\n';
let roles = new Set(release.extraartists.map(it => it.role));
roles.forEach(function(role) {
description += '\n' + role + ' – ' + release.extraartists
.filter(artist => artist.role == role)
.map(function(artist) {
var result = '[url=https://www.discogs.com/artist/' + artist.id + ']' +
(artist.anv || artist.name).replace(removeArtistNdx, '') + '[/url]';
if (artist.tracks) result += ' (tracks: ' + artist.tracks + ')';
return result;
})
.join(', ');
});
}
if (release.notes) {
if (description) description += '\n\n';
description += '[b]Notes[/b]\n\n' + release.notes.trim();
}
if (Array.isArray(release.identifiers) && release.identifiers.length > 0) {
if (description) description += '\n\n';
description += '[b]Barcode and Other Identifiers[/b]\n';
release.identifiers.forEach(function(it) {
description += '\n' + it.type;
if (it.description) description += ' (' + it.description + ')';
description += ': ' + it.value;
});
}
[
['Single', 'Single'],
['EP', 'EP'],
['Compilation', 'Compilation'],
['Soundtrack', 'Soundtrack'],
].forEach(function(k) {
if (release.formats.every(it => Array.isArray(it.descriptions) && it.descriptions.includesCaseless(k[0]))) {
identifiers.RELEASETYPE = k[1];
}
});
release.identifiers.forEach(function(id) {
identifiers[id.type.toUpperCase().replace(/\s*\/\s*/g, '-').replace(/\W/g, '_')] = id.value;
});
if (identifiers.BARCODE) identifiers.BARCODE = identifiers.BARCODE;
release.formats.forEach(function(fmt) {
if (editionTest.test(fmt.text)) editions.push(fmt.text);
if (Array.isArray(fmt.descriptions)) fmt.descriptions.forEach(function(desc) {
if (editionTest.test(desc)) editions.push(desc);
});
if (media) return;
if (/\bFile\b/.test(fmt.name)) {
media = 'WEB';
if (['FLAC', 'WAV', 'AIF', 'AIFF', 'AIFC', 'PCM', 'ALAC', 'APE', 'WavPack']
.some(k => fmt.descriptions.includes(k))) {
encoding = 'lossless'; format = 'FLAC';
} else if (fmt.descriptions.includes('AAC')) {
encoding = 'lossy'; format = 'AAC'; bd = undefined;
if (/(\d+)\s*kbps\b/i.test(fmt.text)) bitrate = parseInt(RegExp.$1);
} else if (fmt.descriptions.includes('MP3')) {
encoding = 'lossy'; format = 'MP3'; bd = undefined;
if (/\b(\d+)\s*kbps\b/i.test(fmt.text)) bitrate = parseInt(RegExp.$1);
} else if (['DFF', 'DSD'].some(k => fmt.descriptions.includes(k))) {
encoding = 'lossless';
} else if (['AMR', 'MP2', 'ogg-vorbis', 'Opus', 'SHN', 'WMA'].some(k => fmt.descriptions.includes(k))) {
encoding = 'lossy';
}
} else media = dcFmtToGazelle(fmt.name) || undefined;
});
if (editions.length > 0) album += ' (' + editions.join(' / ') + ')';
if (Array.isArray(release.images) && release.images[0] && release.images[0].resource_url/*uri*/) {
imgUrl = release.images[0].resource_url/*uri*/;
}
totalTracks = release.tracklist.filter(track => track.type_.toLowerCase() == 'track').length;
return master.then(enumTracks, function(e) {
addMessage('Notice: ' + e, 'ua-notice');
return enumTracks({});
});
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),
];
}
function enumTracks(master) {
var tags = new TagManager();
if (release.genres) tags.add(...release.genres);
if (release.styles) tags.add(...release.styles);
if (master.genres) tags.add(...master.genres);
if (master.styles) tags.add(...master.styles);
release.tracklist.forEach(function(track) {
switch (track.type_.toLowerCase()) {
case 'heading':
discSubtitle = track.title;
break;
case 'track': {
trackIdentifiers = {};
if (/^([a-zA-Z]+)?(\d+)-(\w+)$/.test(track.position)) {
if (RegExp.$1) trackIdentifiers.VOL_MEDIA = RegExp.$1;
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: artist,
album: album,
album_year: master.year,
release_date: release.released,
label: label.join(' / '),
catalog: catalogue.join(' / '),
country: release.country,
encoding: encoding,
codec: format,
bitrate: bitrate,
bd: bd,
media: media,
genre: tags.toString(),
discnumber: discNumber,
totaldiscs: release.format_quantity,
discsubtitle: discSubtitle,
tracknumber: trackNumber,
totaltracks: totalTracks,
title: track.title,
track_artist: trackArtist,
performer: Array.isArray(performer) && performer.join('; ') || undefined,
composer: stringyfyRole(3),
conductor: stringyfyRole(4),
remixer: stringyfyRole(2),
compiler: stringyfyRole(5),
producer: stringyfyRole(6),
duration: timeStringToTime(track.duration),
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
});
function stringyfyRole(ndx) {
return (Array.isArray(trackArtists[ndx]) && trackArtists[ndx].length > 0 ?
trackArtists : albumArtists)[ndx].join('; ');
}
}
}
});
return tracks;
}
});
} else if (url.toLowerCase().includes('supraphonline.cz/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url.replace(/\?.*$/, ''), onload: function(response) {
prologue(response);
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);
});
if (artist.length == 0 && (ref = dom.querySelector('h2.album-artist[title]')) != null) {
isVA = vaParser.test(ref.title);
}
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 += '\n' ;
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
if ((ref = dom.querySelector('meta[itemprop="image"]')) != null) imgUrl = ref.content.replace(/\?.*$/, '');
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: function(error) {
reject(new Error('GM_xmlhttpRequest readyState=' + error.readyState +
', status=' + error.status + ' (' + error.error + ')'));
},
onabort: abort => { reject('XHR: abort') },
ontimeout: timeout => { reject('XHR: timeout') },
});
}) : Promise.resolve([row, null]));
});
return 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, trackIdentifiers = {};
if (/^track-(\d+)$/i.test(tr[0].id)) trackIdentifiers.TRACK_ID = RegExp.$1;
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')) {
trackIdentifiers.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í')) {
trackIdentifiers.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({
artist: isVA ? VA : artist.join('; '),
album: album,
album_year: /*trackYear || */albumYear || undefined,
release_date: releaseDate,
label: label,
catalog: catalogue,
encoding: encoding,
codec: format,
bd: bd,
sr: sr * 1000,
media: media,
genre: translateGenre(genres) + ' | ' + translateGenre(trackGenre),
discnumber: discNumber,
totaldiscs: totalDiscs,
discsubtitle: discSubtitle,
tracknumber: /^\s*(\d+)\.?\s*$/.test(tr[0].children[0].firstChild.textContent) ?
parseInt(RegExp.$1) : undefined,
totaltracks: totalTracks,
title: tr[0].querySelector('meta[itemprop="name"]').content,
track_artist: joinArtists(tr[2][0]),
performer: tr[2][7].join('; ') || performers.join('; '),
composer: tr[2][3].join(', ') || composer.join(', '),
conductor: tr[2][4].join('; ') || conductor.join('; '),
remixer: tr[2][2].join('; '),
compiler: tr[2][5].join('; ') || DJs.join('; '),
producer: tr[2][6].join('; '),
duration: durationFromMeta(tr[0]),
url: response.finalUrl,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
});
}
});
resolve(tracks);
});
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;
}
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('bontonland.cz/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, onload: function(response) {
prologue(response);
ref = dom.querySelector('div#detailheader > h1');
if (ref != null && /^(.*?)\s*:\s*(.*)$/.test(ref.textContent.trim())) {
artist = RegExp.$1;
isVA = vaParser.test(artist);
album = RegExp.$2;
}
media = 'CD';
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')) {
identifiers.BARCODE = it.nextElementSibling.textContent.trim();
}
});
getDescFromNode('div#detailtabpopis > div[class^="pravy"] > div > p:not(:last-of-type)', response.finalUrl, true);
if ((ref = dom.querySelector('a.detailzoom')) != null) imgUrl = ref.href;
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()));
resolve(Array.from(trackList).map(track => ({
artist: isVA ? VA : artist,
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
media: media,
tracknumber: track[1],
totaltracks: trackList.length,
title: track[2],
duration: timeStringToTime(track[3]),
url: response.finalUrl,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
})));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('nativedsd.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, onload: function(response) {
prologue(response);
if ((ref = dom.querySelector('div.the-content > header > h2')) != null) artist = ref.firstChild.data.trim();
isVA = vaParser.test(artist);
if ((ref = dom.querySelector('div.the-content > header > h1')) != null) album = ref.firstChild.data.trim();
if ((ref = dom.querySelector('div.the-content > header > h3')) != null) composer = ref.firstChild.data.trim();
if ((ref = dom.querySelector('div.the-content > header > h1 > small')) != 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)) identifiers.NATIVEDSD_ID = RegExp.$1;
}
identifiers.ORIGINALFORMAT = 'DSD';
getDescFromNode('div.the-content > div.entry > p', response.finalUrl, false);
if ((ref = dom.querySelector('div#repertoire > div > p')) != null) {
let repertoire = html2php(ref, url);
if (description) description += '\n\n';
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 += '\n\n';
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());
});
}
if ((ref = dom.querySelector('a#album-cover')) != null) imgUrl = ref.href;
trs = dom.querySelectorAll('div#track-list > table > tbody > tr[id^="track"]');
resolve(Array.from(trs).map(function(tr) {
title = undefined;
trackIdentifiers = { TRACK_ID: tr.id.replace(/^track-/i, '') };
var trackComposer;
if ((ref = tr.children[1]) != null) {
title = ref.firstChild.textContent.trim();
trackComposer = ref.childNodes[2] && ref.childNodes[2].textContent.trim() || undefined;
}
return {
artist: isVA ? VA : artist,
album: album,
album_year: albumYear,
release_date: releaseDate,
label: label,
catalog: catalogue,
encoding: 'lossless', // encoding
codec: 'FLAC', // format
bd: 24,
sr: 88200,
media: media,
genre: genres.join('; '), // 'Jazz'
discnumber: discNumber,
totaldiscs: totalDiscs,
discsubtitle: discSubtitle,
tracknumber: (ref = tr.children[0].children[0]) != null ?
parseInt(ref.firstChild.data.trim().replace(/\..*$/, '')) : undefined,
totaltracks: trs.length,
title: title,
composer: trackComposer || composer,
duration: (ref = tr.children[2]) != null ? timeStringToTime(ref.firstChild.data) : undefined,
url: response.finalUrl,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
};
}));
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(', ');
}
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('junodownload.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, onload: function(response) {
prologue(response);
if (/\/([\d\-]+)\/?$/.test(response.finalUrl)) identifiers.JUNODOWNLOAD_ID = RegExp.$1;
var productArtist;
if ((ref = dom.querySelectorAll('div.breadcrumb_text > span:not([class])')).length == 4) {
artist = Array.from(ref[ref.length - 1].querySelectorAll('a')).map(elem => elem.textContent.trim());
productArtist = ref[ref.length - 1].textContent.trim();
} else if ((ref = dom.querySelector('h2.product-artist')) != null) {
artist = Array.from(ref.querySelectorAll('a')).map(elem => elem.textContent.trim().titleCase());
productArtist = ref.textContent.trim().titleCase();
}
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if (isVA) artist = [];
if ((ref = dom.querySelector('meta[itemprop="name"]')) != null) album = ref.content.trim();
if ((ref = dom.querySelector('meta[itemprop="author"]')) != null) label = ref.content.trim();
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;
}
});
getDescFromNode('div[itemprop="review"]', response.finalUrl);
if ((ref = dom.querySelector('meta[property="og:image"]')) != null) imgUrl = ref.content;
trs = dom.querySelectorAll('div.product-tracklist > div[itemprop="track"]');
resolve(Array.from(trs).map(function(tr) {
trackIdentifiers = { BPM: tr.children[2].textContent.trim() };
trackNumber = undefined;
tr.querySelector('div.track-title').childNodes.forEach(function(n) {
if (trackNumber || n.nodeType != 3) return;
trackNumber = n.data.trim().replace(/\s*\..*$/, '');
});
trackArtist = (ref = tr.querySelector('meta[itemprop="byArtist"]')) != null ? ref.content : undefined;
title = (ref = tr.querySelector('span[itemprop="name"]')) != null ? ref.textContent.trim() : undefined;
if (title && trackArtist && title.startsWith(trackArtist + ' - ')) title = title.slice(trackArtist.length + 3);
if (trackArtist && trackArtist == productArtist) trackArtist = undefined;
return {
artist: isVA ? VA : artist.join('; '),
album: album,
album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
catalog: catalogue,
media: media,
genre: genres.join('; '),
discnumber: discNumber,
totaldiscs: totalDiscs,
discsubtitle: discSubtitle,
tracknumber: trackNumber,
totaltracks: trs.length,
title: title,
track_artist: trackArtist,
duration: durationFromMeta(tr),
url: !identifiers.JUNODOWNLOAD_ID ? response.finalUrl : undefined,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
};
}));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (/\bhdtracks(?:\.\w+)+\//i.test(url)) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, onload: function(response) {
prologue(response);
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());
});
isVA = vaParser.test(artist);
if ((ref = dom.querySelector('p.product-image > img')) != null) imgUrl = ref.src;
trs = dom.querySelectorAll('table#track-table > tbody > tr[id^="track"]');
resolve(Array.from(trs).map(function(tr) {
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);
return {
artist: isVA ? VA : artist,
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
catalog: catalogue,
encoding: 'lossless',
be: bd || 24,
sr: sr || undefined,
media: media,
genre: genres.join('; '),
//discnumber: discNumber,
//totaldiscs: totaldiscs,
//discsubtitle: discSubtitle,
tracknumber: (ref = tr.querySelector('td:first-of-type')) != null ? parseInt(ref.textContent.trim()) : undefined,
totaltracks: trs.length,
title: (ref = tr.querySelector('td.track-name')) != null ? ref.textContent.trim() : undefined,
duration: (ref = tr.querySelector('td:nth-of-type(3)')) != null ? timeStringToTime(ref.textContent.trim()) : undefined,
url: response.finalUrl,
identifiers: mergeIds(),
cover_url: imgUrl,
}
}));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (/^https?:\/\/(?:\w+\.)?deezer\.com\/(?:\w+\/)*album\/(\d+)/i.test(url)) {
return queryDeezerAPI('album/' + RegExp.$1).then(function(release) {
isVA = vaParser.test(release.artist.name);
identifiers.DEEZER_ID = release.id;
identifiers.RELEASETYPE = release.record_type;
if (release.upc) identifiers.BARCODE = release.upc;
if (release.cover_xl) imgUrl = release.cover_xl.replace('1000x1000-000000-80-0-0', '1400x1400-000000-100-0-0');
return release.tracks.data.map(function(track, ndx) {
trackIdentifiers = { TRACK_ID: track.id };
trackArtist = track.artist.name;
if (!isVA && trackArtist && trackArtist == release.artist.name) trackArtist = undefined;
return {
artist: isVA ? VA : release.artist.name,
album: release.title,
release_date: release.release_date,
label: release.label,
media: media,
genre: release.genres.data.map(it => it.name).join('; '),
tracknumber: ndx + 1,
totaltracks: release.nb_tracks,
title: track.title,
track_artist: trackArtist,
duration: track.duration,
//url: 'https://www.deezer.com/album/' + release.id,
identifiers: mergeIds(),
cover_url: imgUrl,
};
});
});
} else if (url.toLowerCase().includes('spotify.com/') && /\/albums?\/(\w+)$/i.test(url)) {
return querySpotifyAPI('albums/' + RegExp.$1).then(function(release) {
artist = release.artists.map(artist => artist.name);
isVA = release.artists.length == 0 || release.artists.length == 1 && vaParser.test(release.artists[0].name);
totalDiscs = release.tracks.items.reduce((acc, track) => Math.max(acc, track.disc_number), 0);
identifiers.SPOTIFY_ID = release.id;
identifiers.RELEASETYPE = release.album_type;
var image = release.images.reduce((acc, image) => image.width * image.height > acc.width * acc.height ? image : acc);
return release.tracks.items.map(function(track, ndx) {
trackIdentifiers = {
TRACK_ID: track.id,
EXPLICIT: Number(track.explicit),
};
trackArtist = track.artists.map(artist => artist.name);
if (!isVA && trackArtist.equalTo(artist)) trackArtist = [];
return {
artist: isVA ? VA : joinArtists(artist),
album: release.name,
release_date: release.release_date,
label: release.label,
catalog: release.external_ids.upc,
media: media,
genre: release.genres.join('; '),
discnumber: track.disc_number,
totaldiscs: totalDiscs,
discsubtitle: discSubtitle,
tracknumber: track.track_number,
totaltracks: release.total_tracks,
title: track.name,
track_artist: joinArtists(trackArtist),
duration: track.duration_ms / 1000,
//url: 'https://open.spotify.com/album/' + release.id,
identifiers: mergeIds(),
cover_url: image ? image.url : undefined,
};
});
});
} else if (url.toLowerCase().includes('prostudiomasters.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, responseType: 'document', onload: function(response) {
prologue(response);
if (/\/page\/(\d+)$/i.test(response.finalUrl)) identifiers.PROSTUDIOMASTERS_ID = RegExp.$1;
artist = Array.from(dom.querySelectorAll('h2.ArtistName > a')).map(node => node.textContent.trim());
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if (isVA) artist = [];
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);
if ((ref = dom.querySelector('img.album-art')) != null) imgUrl = ref.currentSrc || ref.src;
trs = dom.querySelectorAll('div.album-tracks > div.tracks > table > tbody > tr');
totalTracks = Array.from(trs).filter(tr => tr.classList.contains('track-playable')).length;
discNumber = 0;
trs.forEach(function(tr) {
if (tr.classList.contains('track-playable')) {
trackArtist = []; sr = bd = format = title = undefined; trackIdentifiers = {};
if (ref = tr.getAttribute('data-track-id')) trackIdentifiers.TRACK_ID = ref;
trackNumber = (ref = tr.querySelector('div.num')) != null ? parseInt(ref.firstChild.textContent.trim()) : undefined;
if (trackNumber == 1) ++discNumber;
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);
if (!isVA && trackArtist.equalTo(artist)) trackArtist = [];
}
}
if ((ref = tr.querySelector('span.track-format')) != null && /^(\d+(?:[,\.]\d+)?)\s*([kMG]Hz)(?:\s+(\d+)-bit)?\s*\|\s*(\S+)$/i.test(ref.textContent.trim())) {
sr = parseFloat(RegExp.$1);
['khz', 'mhz', 'ghz'].forEach((unit, ndx) => { if (RegExp.$2.toLowerCase() == unit) sr *= 1000 ** (ndx + 1) });
sr = Math.round(sr) || undefined;
bd = parseInt(RegExp.$3) || undefined;
format = RegExp.$4;
}
tracks.push({
artist: isVA ? VA : artist.join('; '),
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
catalog: catalogue,
codec: format,
bd: bd,
sr: sr,
media: media,
discnumber: discNumber,
totaldiscs: totalDiscs,
discsubtitle: discSubtitle,
tracknumber: trackNumber,
totaltracks: totalTracks,
title: title,
track_artist: joinArtists(trackArtist),
duration: (ref = tr.querySelector('td:last-of-type')) != null ? timeStringToTime(ref.firstChild.data) : undefined,
url: !identifiers.PROSTUDIOMASTERS_ID ? response.finalUrl : undefined,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
});
} else if ((ref = tr.querySelector('div.grouping-title')) != null) {
discSubtitle = ref.textContent.trim();
guessDiscNumber();
}
});
resolve(tracks);
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
/*
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(release) {
isVA = vaParser.test(release.artist.name);
identifiers.SOUNDCLOUD_ID = release.id;
identifiers.RELEASETYPE = release.record_type;
release.tracks.data.forEach(function(track, ndx) {
trackIdentifiers = { TRACK_ID: track.id };
trackArtist = track.artist.name;
if (!isVA && trackArtist && trackArtist == release.artist.name) trackArtist = undefined;
tracks.push({});
});
return tracks;
});
return true;
*/
else if (url.toLowerCase().includes('play.google.com/store/music/album/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, responseType: 'document', onload: function(response) {
prologue(response);
var search = new URLSearchParams(new URL(response.finalUrl).search);
var ID = search.get('id'), trackID, aggregateRating;
if (ID) identifiers.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;
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)) identifiers.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 + ')');
}
if ((ref = dom.querySelector('meta[itemprop="image"]')) != null) imgUrl = ref.content;
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; trackIdentifiers = {};
if ((ref = tr.querySelector('meta[itemprop="url"]')) != null) {
search = new URLSearchParams(new URL(ref.content).search);
let trackID = search.get('tid');
if (trackID) trackIdentifiers.TRACK_ID = trackID;
}
++trackNumber;
title = (ref = tr.querySelector('meta[itemprop="name"]')) != null ? ref.content : undefined;
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;
}
duration = durationFromMeta(tr);
addTrack();
});
} else volumes.forEach(function(volume) {
discNumber = undefined; discSubtitle = volume.textContent.trim();
guessDiscNumber();
volume.nextElementSibling.querySelectorAll('tbody > tr[class]').forEach(scanPlaylist);
});
resolve(tracks);
function scanPlaylist(tr) {
trackNumber = (ref = tr.querySelector('td:nth-of-type(1) > div')) != null ? parseInt(ref.textContent) : undefined;
title = (ref = tr.querySelector('td[itemprop="name"]')) != null ? ref.textContent.trim() : undefined;
duration = (ref = tr.querySelector('td:nth-of-type(3)')) != null ? timeStringToTime(ref.textContent) : undefined;
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({
artist: isVA ? VA : artist.join('; '),
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
catalog: catalogue,
media: media,
genre: genres.join('; '),
discnumber: discNumber,
totaldiscs: totalDiscs,
discsubtitle: discSubtitle,
tracknumber: trackNumber,
totaltracks: totalTracks,
title: title,
track_artist: trackArtist,
duration: duration,
url: identifiers.GOOGLE_ID ? undefined : response.finalUrl,
//description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
});
}
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('7digital.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, responseType: 'document', onload: function(response) {
prologue(response);
if ((ref = dom.querySelector('table.release-track-list')) != null) identifiers['7DIGITAL_ID'] = ref.dataset.releaseid;
artist = Array.from(dom.querySelectorAll('h2.release-info-artist > span[itemprop="byArtist"] > meta[itemprop="name"]')).map(node => node.content);
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if (isVA) artist = [];
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);
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);
if ((ref = dom.querySelector('span.release-packshot-image > img[itemprop="image"]')) != null) imgUrl = ref.src;
totalTracks = dom.querySelectorAll('table.release-track-list > tbody > tr.release-track').length;
dom.querySelectorAll('table.release-track-list').forEach(function(table) {
discSubtitle = discNumber = undefined;
if ((ref = table.querySelector('caption > h4.release-disc-info')) != null) {
discSubtitle = ref.textContent.trim();
guessDiscNumber();
}
table.querySelectorAll('tbody > tr.release-track').forEach(function(tr) {
trackIdentifiers = {};
if (tr.dataset.trackid) trackIdentifiers.TRACK_ID = tr.dataset.trackid;
tracks.push({
artist: isVA ? VA : artist.join('; '),
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
catalog: catalogue,
media: media,
genre: genres.join('; '),
discnumber: discNumber,
totaldiscs: totalDiscs,
discsubtitle: discSubtitle,
tracknumber: (ref = tr.querySelector('td.release-track-preview > em.release-track-preview-text')) != null ?
ref.textContent.trim() : undefined,
totaltracks: totalTracks,
title: (ref = tr.querySelector('td.release-track-name > meta[itemprop="name"]')) != null ?
ref.content : undefined,
duration: durationFromMeta(tr),
url: !identifiers['7DIGITAL_ID'] ? response.finalUrl : undefined,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
});
});
});
resolve(tracks);
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('e-onkyo.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, responseType: 'document', onload: function(response) {
prologue(response);
if (/\/album\/(\w+)\/?$/.test(response.finalUrl)) identifiers.EONKYO_ID = RegExp.$1;
artist = Array.from(dom.querySelectorAll('div.jacketDetailArea p.artistsName > a'))
.map(node => node.textContent.trim());
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if (isVA) artist = [];
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);
}
if ((ref = dom.querySelector('figure > a.colorbox')) != null) {
imgUrl = new URL(response.finalUrl).origin + ref.pathname;
}
trs = dom.querySelectorAll('dl.musicList > dd.musicBox');
resolve(Array.from(trs).map(tr => ({
//var trackId = tr.dataset.trackid;
//if (trackId) trackId = 'TRACK_ID=' + trackId;
//trackArtist = tr.children[5].textContent.trim();
//if (trackArtist == artist.join(', ')) trackArtist = undefined;
artist: isVA ? VA : artist.join('; '),
album: album,
album_year: albumYear,
release_date: releaseDate,
label: label,
catalog: catalogue,
encoding: 'lossless',
codec: 'FLAC',
bd: bd,
sr: sr * 1000 || undefined,
media: media,
//discnumber: discNumber,
//totaldiscs: totalDiscs,
//discsubtitle: discSubtitle,
tracknumber: (ref = tr.querySelector('div.musicListNo')) != null ? ref.textContent.trim() : undefined,
totaltracks: trs.length,
title: (ref = tr.querySelector('div.musicTtl > span')) != null ? ref.title : undefined,
duration: (ref = tr.querySelector('div.musicTime')) != null ? timeStringToTime(ref.textContent.trim()) : undefined,
url: !identifiers.EONKYO_ID ? response.finalUrl : undefined,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
})));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('store.acousticsounds.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, responseType: 'document', onload: function(response) {
prologue(response);
if (/\/(\d+)\/$/.test(response.finalUrl)) identifiers.ACOUSTICSOUNDS_ID = RegExp.$1;
artist = Array.from(dom.querySelectorAll('div > h1 > a')).map(node => node.textContent.trim());
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if (isVA) artist = [];
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);
if ((ref = dom.querySelector('div#detail > link[rel="image_src"]')) != null) {
imgUrl = ref.href.replace(/\/medium\//i, '/large/');
}
trs = dom.querySelectorAll('div#tracks > table > tbody > tr');
trackNumber = 0;
resolve(Array.from(trs).map(tr => ({
artist: isVA ? VA : artist.join('; '),
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
catalog: catalogue,
encoding: ['FLAC', 'DSD'].includes(format) ? 'lossless' : undefined,
codec: format,
bd: bd,
sr: sr,
media: media,
genre: genres.join('; '),
//discnumber: discNumber,
//totaldiscs: totalDiscs,
//discsubtitle: discSubtitle,
tracknumber: ++trackNumber,
totaltracks: trs.length,
title: (ref = tr.querySelector('td[nowrap]')) != null ? ref.textContent.trim() : undefined,
url: !identifiers.ACOUSTICSOUNDS_ID ? response.finalUrl : undefined,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
})));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('indies.eu/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, responseType: 'document', onload: function(response) {
prologue(response);
if (/\/alba\/(\d+)\//.test(response.finalUrl)) identifiers.INDIESSCOPE_ID = RegExp.$1;
ref = dom.querySelector(':root > body > div > div > div > h2');
if (ref != null) artist = Array.from(ref.childNodes).map(node => node.textContent.trim());
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if (isVA) artist = [];
if ((ref = dom.querySelector(':root > body > div > div > div > h1')) != null) album = ref.textContent.trim();
if ((ref = dom.querySelector('div.infoBox')) != null) {
let ndx = 0;
ref.childNodes.forEach(function(child) {
if (child.nodeName == 'BR') { ++ndx; return; }
switch (ndx) {
case 0:
if (child.nodeType == Node.TEXT_NODE) {
label = child.wholeText.trim();
if (/^(.*)\s+\/\s+(\d{4})$/.test(label)) {
label = RegExp.$1;
releaseDate = RegExp.$2;
}
}
break;
case 1:
if (child.nodeType == Node.ELEMENT_NODE) genres.push(child.textContent.trim());
break;
case 2:
if (child.nodeType == Node.ELEMENT_NODE) catalogue = child.textContent.trim();
break;
}
});
}
getDescFromNode('div.popis > section', response.finalUrl, true);
if ((ref = dom.querySelector('div.obrazekDetail > img')) != null) imgUrl = ref.src;
trs = dom.querySelectorAll('table.skladby > tbody > tr');
resolve(Array.from(trs).map(function(tr) {
title = undefined;
if ((ref = tr.querySelector('td.nazev')) != null) {
trackNumber = parseInt(ref.firstChild.wholeText);
title = ref.querySelector('strong').textContent.trim();
}
return {
artist: isVA ? VA : artist.join('; '),
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
catalog: catalogue,
codec: format,
media: media,
genre: genres.join('; '),
//discnumber: discNumber,
//totaldiscs: totalDiscs,
//discsubtitle: discSubtitle,
tracknumber: trackNumber,
totaltracks: trs.length,
title: title,
duration: (ref = tr.querySelector('td:nth-of-type(4)')) != null ? timeStringToTime(ref.textContent) : undefined,
identifiers: !identifiers.INDIESSCOPE_ID ? response.finalUrl : undefined,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
};
}));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('beatport.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, responseType: 'document', onload: function(response) {
prologue(response);
if (/\/release\/(?:\d\/)?(?:\S+-)?(\d+)\b/i.test(response.finalUrl)) identifiers.BEATPORT_ID = RegExp.$1;
artist = Array.from(dom.querySelectorAll('span > a[data-artist]')).map(node => node.textContent.trim());
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if (isVA) artist = [];
if ((ref = dom.querySelector('div > h1')) != null) album = ref.textContent.trim();
dom.querySelectorAll('ul > li > span.category').forEach(function(span) {
if (/^(?:Release\s+Date)/i.test(span.textContent)) releaseDate = span.nextElementSibling.textContent.trim();
if (/^(?:Label)/i.test(span.textContent)) label = span.nextElementSibling.textContent.trim();
if (/^(?:Catalog)/i.test(span.textContent)) catalogue = span.nextElementSibling.textContent.trim();
});
getDescFromNode('div.interior-expandable', response.finalUrl, true);
if ((ref = dom.querySelector('div > img.interior-release-chart-artwork')) != null) imgUrl = ref.src;
trs = dom.querySelectorAll('div.tracks > ul > li.track');
resolve(Array.from(trs).map(function(tr) {
title = undefined; trackIdentifiers = {};
if ((ref = tr.querySelector('span.buk-track-primary-title')) != null) {
title = ref.title || ref.textContent.trim();
if ((ref = tr.querySelector('span.buk-track-remixed')) != null) title += ' (' + ref.textContent.trim() + ')';
}
trackArtist = Array.from(tr.querySelectorAll('p.buk-track-artists > a')).map(a => a.textContent.trim());
if (!isVA && trackArtist.equalTo(artist)) trackArtist = [];
if ((ref = tr.querySelector('p.buk-track-bpm')) != null) trackIdentifiers.BPM = ref.textContent;
return {
artist: isVA ? VA : artist.join('; '),
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: (ref = tr.querySelector('p.buk-track-labels')) != null ? ref.textContent.trim() : label,
catalog: catalogue,
codec: format,
media: media,
genre: Array.from(tr.querySelectorAll('p.buk-track-genre > a')).map(a => a.textContent).join('; '),
//discnumber: discNumber,
//totaldiscs: totalDiscs,
//discsubtitle: discSubtitle,
tracknumber: (ref = tr.querySelector('div.buk-track-num')) != null ? ref.textContent.trim() : undefined,
totaltracks: trs.length,
title: title,
track_artist: joinArtists(trackArtist),
remixer: Array.from(tr.querySelectorAll('p.buk-track-remixers > a')).map(a => a.textContent.trim()).join('; '),
duration: (ref = tr.querySelector('p.buk-track-length')) != null ? timeStringToTime(ref.textContent) : undefined,
url: !identifiers.BEATPORT_ID ? response.finalUrl : undefined,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
};
}));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('traxsource.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, responseType: 'document', onload: function(response) {
prologue(response);
if (/\/title\/(\d+)(?=\/|$)/i.test(response.finalUrl)) identifiers.TRAXSOURCE_ID = RegExp.$1;
artist = Array.from(dom.querySelectorAll('h1.artists > a.com-artists')).map(node => node.textContent.trim());
if (artist.length <= 0 && (ref = dom.querySelector('h1.artists')) != null) artist = [ref.textContent.trim()];
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if (isVA) artist = [];
if ((ref = dom.querySelector('h1.title')) != null) album = ref.textContent.trim();
if ((ref = dom.querySelector('a.com-label')) != null) label = ref.textContent.trim();
if ((ref = dom.querySelector('div.cat-rdate')) != null) {
catalogue = ref.textContent.trim();
if (/\s*\|\s*(\S+)$/.test(catalogue)) {
catalogue = RegExp.leftContent;
releaseDate = normalizeDate(RegExp.$1);
}
}
getDescFromNode('div.desc', response.finalUrl, true);
if ((ref = dom.querySelector('meta[property="og:image"]')) != null) imgUrl = ref.content;
trs = dom.querySelectorAll('div.trklist > div.trk-row');
resolve(Array.from(trs).map(function(tr) {
trackIdentifiers = {};
title = (ref = tr.querySelector('div.title > a')) != null ? ref.textContent.trim() : undefined;
if (title && (ref = tr.querySelector('span.version')) != null ) {
if (ref.firstChild.nodeType == Node.TEXT_NODE
&& (i = ref.firstChild.wholeText.trim()).length > 0) title += ` (${i})`;
}
trackArtist = Array.from(tr.querySelectorAll('div.artists a.com-artists')).map(a => a.textContent.trim());
if (!isVA && trackArtist.equalTo(artist)) trackArtist = [];
return {
artist: isVA ? VA : artist.join('; '),
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
catalog: catalogue,
media: media,
genre: Array.from(tr.querySelectorAll('div.genre > a')).map(a => a.textContent.trim()).join('; '),
//discnumber: discNumber,
//totaldiscs: totalDiscs,
//discsubtitle: discSubtitle,
tracknumber: (ref = tr.querySelector('div.tnum')) != null ? ref.textContent.trim() : undefined,
totaltracks: trs.length,
title: title,
track_artist: joinArtists(trackArtist),
remixer: Array.from(tr.querySelectorAll('div.artists a.com-remixers')).map(a => a.textContent.trim()).join('; '),
duration: (ref = tr.querySelector('span.duration')) != null ? timeStringToTime(ref.textContent) : undefined,
url: !identifiers.TRAXSOURCE_ID ? response.finalUrl : undefined,
description: description,
identifiers: mergeIds(),
cover_url: imgUrl,
};
}));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (url.toLowerCase().includes('music.apple.com/')) return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET', url: url, responseType: 'document', onload: function(response) {
prologue(response);
if (/\/(\d+)$/.test(response.finalUrl)) identifiers.APPLE_ID = RegExp.$1;
artist = Array.from(dom.querySelectorAll('span.product-header__identity > a.link')).map(a => a.textContent.trim());
if (artist.length <= 0 && (ref = dom.querySelector('span.product-header__identity')) != null) {
artist = [ref.textContent.trim()];
}
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if ((ref = dom.querySelector('h1 > span.product-header__title')) != null) album = ref.textContent.trim();
genres = Array.from(dom.querySelectorAll('ul.inline-list > li:first-of-type > a')).map(a => a.textContent.trim());
if ((ref = dom.querySelector('time[datetime]')) != null) releaseDate = ref.dateTime;
if ((ref = dom.querySelector('li.link-list__item--copyright')) != null) {
label = ref.textContent.replace(/^.*[©℗]\s+\d{4}\s+/, '');
}
description = html2php(dom.querySelector('section.product-hero-desc__section'), response.finalUrl);
if (description && !description.includes('[quote]')) {
description = '[quote]' + description.collapseGaps() + '[/quote]';
}
discNumber = 0;
trs = dom.querySelectorAll('table > tbody > tr[id]');
resolve(Array.from(trs).map(function(tr) {
trackIdentifiers = {};
trackNumber = (ref = tr.querySelector('span.table__row__number')) != null ? parseInt(ref.textContent) : undefined;
if (trackNumber == 1) ++discNumber;
trackArtist = /*(ref = tr.querySelector('div.table__row__titles > div:last-of-type')) != null ?
ref.textContent.trim() : */undefined;
if (!isVA && trackArtist == joinArtists(artist)) trackArtist = undefined;
if ((ref = tr.querySelector('time.table__row__duration-counter')) != null
&& /^PT(?:(?:(\d+)H)?(\d+)M)?(\d+)S$/.test(ref.dateTime)) {
duration = (parseInt(RegExp.$1) * 60**2 || 0) + (parseInt(RegExp.$2) * 60 || 0) + (parseInt(RegExp.$3) || 0);
} else duration = undefined;
return {
artist: isVA ? VA : artist.join('; '),
album: album,
//album_year: extractYear(releaseDate),
release_date: releaseDate,
label: label,
media: media,
genre: genres.join('; '),
discnumber: discNumber,
tracknumber: trackNumber,
totaltracks: trs.length,
title: (ref = tr.querySelector('div.table__row__headline')) != null ? ref.textContent.trim() : undefined,
track_artist: trackArtist,
duration: duration,
description: description,
url: !identifiers.APPLE_ID ? response.finalUrl : undefined,
identifiers: mergeIds(),
};
}));
},
onerror: error => { reject(defaultErrorHandler(error)) },
onabort: abort => { reject(defaultAbortHandler(abort)) },
ontimeout: timeOut => { reject(defaultTimeoutHandler(timeOut)) },
}));
else if (mbrRlsParser.test(url)) { // Musicbrainz
var entities = [
'aliases',
'annotation',
'artist-credits',
'artists',
'collections',
'discids',
'genres',
'isrcs',
'labels',
'media',
'ratings',
'recordings',
'release-groups',
'tags',
'url-rels',
];
return queryMusicbrainzAPI('release/' + RegExp.$1, { inc: entities.join('+') }).then(function(release) {
if (release.error) return Promise.reject(release.error);
identifiers.MBR_ID = release.id;
if (release.barcode) identifiers.BARCODE = release.barcode;
if (release.asin) identifiers.ASIN = release.asin;
artist = Array.isArray(release['artist-credit']) ? release['artist-credit'].map(artist => artist.name) : [];
isVA = artist.length <= 0 || artist.length == 1 && vaParser.test(artist[0]);
if (Array.isArray(release.genres)) genres = release.genres.map(genre => genre.name);
if (Array.isArray(release.tags)) Array.prototype.push.apply(genres, release.tags.map(tag => tag.name));
if (genres.length <= 0) {
if (Array.isArray(release['release-group'].genres)) {
Array.prototype.push.apply(genres, release['release-group'].genres.map(tag => tag.name));
}
if (Array.isArray(release['release-group'].tags)) {
Array.prototype.push.apply(genres, release['release-group'].tags.map(tag => tag.name));
}
}
label = release['label-info'].map(label => label.label.name);
catalogue = release['label-info'].map(label => label['catalog-number']);
release.media.forEach(function(medium, ndx) {
medium.tracks.forEach(function(track, ndx) {
trackIdentifiers = { TRACK_ID: track.id };
trackArtist = Array.isArray(track['artist-credit']) ? track['artist-credit'].map(artist => artist.name) : [];
if (!isVA && trackArtist.length > 0 && trackArtist.equalTo(artist)) trackArtist = [];
tracks.push({
artist: isVA ? VA : artist.join(', '),
album: /*release['release-group'].title || */release.title,
album_year: extractYear(release['release-group']['first-release-date']),
release_date: release.date,
genre: genres.join('; '),
label: label.filter(label => label).join(' / '),
catalog: catalogue.filter(catno => catno).join(' / '),
media: medium.format,
discnumber: medium.position,
discsubtitle: medium.title,
totaldiscs: release.media.length,
tracknumber: track.number,
title: track.title,
track_artist: joinArtists(trackArtist),
duration: track.length / 1000,
//country: release.country,
description: release.annotation,
//url: 'https://www.deezer.com/album/' + release.id,
identifiers: mergeIds(),
});
});
});
return tracks;
});
}
if (!weak) clipBoard.value = '';
return Promise.reject(new URL(url).hostname + ' not supported');
function mergeIds() {
var r = Object.assign(identifiers, trackIdentifiers);
trackIdentifiers = {};
return r;
}
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');
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;
}
function guessDiscNumber() {
if (discParser.test(discSubtitle)) {
discSubtitle = undefined;
discNumber = parseInt(RegExp.$1);
}
}
function prologue(response) {
if (response.readyState != 4 || response.status != 200) throw defaultErrorHandler(response);
dom = domParser.parseFromString(response.responseText, 'text/html');
}
} // fetchOnline_Music
function parseLastFm(album) {
if (typeof album != 'object') return Promise.reject('invalid object')
var identifiers = {}, description = [];
if (album.id) identifiers.LASTFM_ID = album.id;
if (album.mbid) identifiers.MBR_ID = album.mbid;
if (album.wiki && album.wiki.summary) description.push(album.wiki.summary);
if (album.wiki && album.wiki.content) description.push(album.wiki.content);
var genres = album.tags.tag.map(tag => tag.name);
description = description.join('\n\n');
var imgUrl = album.image.filter(image => image.size == /*'extralarge'*/'mega');
if (imgUrl.length > 0) {
imgUrl = imgUrl[0]['#text'];
if (imgUrl) imgUrl = imgUrl.replace(/\/\d+x\d+\//, '/');
} else imgUrl = undefined;
return Promise.resolve(album.tracks.track.map((track, ndx) => ({
artist: album.artist,
album: album.name,
genre: genres.join('; ') || undefined,
title: track.name,
tracknumber: ndx + 1,
track_artist: track.artist.name != album.artist ? track.artist.name : undefined,
duration: parseFloat(track.duration) || undefined,
url: album.url,
description: description || undefined,
identifiers: identifiers,
cover_url: imgUrl,
})));
}
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('FATAL: valid URL accepted for this category', 'ua-critical');
return false;
}
sourceUrl = RegExp.$1;
var description, tags = new TagManager();
if (sourceUrl.toLowerCase().includes('://sanet')) {
GM_xmlhttpRequest({ method: 'GET', url: sourceUrl, onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw defaultErrorHandler(response);
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(response.finalUrl) + '[/url]';
}
writeDescription(description.collapseGaps());
if ((ref = dom.querySelector('section.descr > div.center > a.mfp-image')) != null) {
setCover(ref.href);
} else {
ref = dom.querySelector('section.descr > div.center > img[data-src]');
if (ref != null) setCover(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 || '';
}, onerror: defaultErrorHandler, onabort: defaultAbortHandler, ontimeout: defaultTimeoutHandler });
return true;
}
if (!weak) {
addMessage('FATAL: this domain not supported', 'ua-critical');
clipBoard.value = '';
}
return false;
} // fillFromText_Apps
function fillFromText_Ebooks(weak = false) {
if (messages != null) messages.parentNode.removeChild(messages);
if (!urlParser.test(clipBoard.value)) {
addMessage('FATAL: valid URL accepted for this category', 'ua-critical');
return false;
}
sourceUrl = RegExp.$1;
var description, tags = new TagManager();
if (url.toLowerCase().includes('martinus.cz') || sourceUrl.toLowerCase().includes('martinus.sk')) {
GM_xmlhttpRequest({ method: 'GET', url: sourceUrl, onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw defaultErrorHandler(response);
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)) {
sourceUrl = new URL('https://www.worldcat.org/isbn/' + detail.children[1].textContent.trim());
val = '[url=' + sourceUrl.href + ']' + detail.children[1].textContent.trim() + '[/url]';
findOCLC(sourceUrl);
// } 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;
});
sourceUrl = new URL(response.finalUrl);
description += '\n\n[b]More info:[/b]\n[url]' + sourceUrl.href + '[/url]';
writeDescription(description.collapseGaps());
if ((i = dom.querySelector('a.mj-product-preview > img')) != null) {
setCover(i.src.replace(/\?.*/, ''));
} else if ((i = dom.querySelector('head > meta[property="og:image"]')) != null) {
setCover(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();
}
}, onerror: defaultErrorHandler, onabort: defaultAbortHandler, ontimeout: defaultTimeoutHandler });
return true;
} else if (sourceUrl.toLowerCase().includes('goodreads.com')) {
GM_xmlhttpRequest({ method: 'GET', url: sourceUrl, onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw defaultErrorHandler(response);
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))) {
sourceUrl = new URL('https://www.worldcat.org/isbn/' + RegExp.$1);
val = '[url=' + sourceUrl.href + ']' + strip(detail.children[1].textContent) + '[/url]';
findOCLC(sourceUrl);
}
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) + '%';
}
sourceUrl = new URL(response.finalUrl);
// if ((ref = dom.querySelector('div#buyButtonContainer > ul > li > a.buttonBar')) != null) {
// let u = new URL(ref.href);
// description += '\n[url=' + sourceUrl.origin + u.pathname + '?' + u.search + ']Libraries[/url]';
// }
description += '\n\n[b]More info and reviews:[/b]\n[url]' + sourceUrl.origin + sourceUrl.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(description.collapseGaps());
if ((ref = dom.querySelector('div.editionCover > img')) != null) setCover(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();
}, onerror: defaultErrorHandler, onabort: defaultAbortHandler, ontimeout: defaultTimeoutHandler });
return true;
} else if (sourceUrl.toLowerCase().includes('databazeknih.cz')) {
if (!sourceUrl.toLowerCase().includes('show=alldesc')) {
if (!sourceUrl.includes('?')) { sourceUrl += '?show=alldesc' } else { sourceUrl += '&show=alldesc' }
}
GM_xmlhttpRequest({ method: 'GET', url: sourceUrl, onload: function(response) {
if (response.readyState != 4 || response.status != 200) throw defaultErrorHandler(response);
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) {
sourceUrl = new URL('https://www.worldcat.org/isbn/' + RegExp.$1.replace(/-/g, ''));
val = '[url=' + sourceUrl.href + ']' + detail.children[1].textContent.trim() + '[/url]';
findOCLC(sourceUrl);
}
description += '\n[b]' + lbl + '[/b] ' + val;
});
sourceUrl = new URL(response.finalUrl);
description += '\n\n[b]More info:[/b]\n[url]' + sourceUrl.origin + sourceUrl.pathname + '[/url]';
writeDescription(description.collapseGaps());
if ((ref = dom.querySelector('div#icover_mid > a')) != null) setCover(ref.href.replace(/\?.*/, ''));
if ((ref = dom.querySelector('div#lbImage')) != null && /\burl\("(.*)"\)/i.test(i.style.backgroundImage)) {
setCover(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();
}, onerror: defaultErrorHandler, onabort: defaultAbortHandler, ontimeout: defaultTimeoutHandler });
return true;
}
if (!weak) {
addMessage('FATAL: 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();
},
onerror: error => { console.error('GM_xmlhttpRequest: error') },
onabort: abort => { console.error('XHR: abort') },
ontimeout: timeout => { console.error('XHR: timeout') },
});
return true;
}
} // fillFromText_Ebooks
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 setCover(url) {
var image = document.getElementById('image') || document.querySelector('input[name="image"]');
return elementWritable(image) ? testImageUrl(url).then(function(url) {
image.value = url;
coverPreview(image, url);
if (prefs.auto_rehost_cover && !url.toLowerCase().startsWith(imghostOrigin)) {
/*if (rehostItBtn != null) rehostItBtn.click(); else */{
image.disabled = true;
rehost2PTPIMG([url])
.then(urls => { if (urls.length > 0) image.value = urls[0] })
.catch(reason => { alert(reason) })
.then(() => { image.disabled = false });
}
}
return url;
}) : Promise.reject('Image input not available');
}
function elementWritable(elem) {
return elem != null && !elem.disabled && (overwrite || !elem.value || isNWCD && elem.value == '---');
}
function defaultErrorHandler(error, showMessage) {
var str = 'XHR => ready state: ' + error.readyState + '; HTTP status code: ' + error.status;
if (error.error) str += ' (' + error.error + ')';
const e = str; //new Error(str);
console.error(e);
if (showMessage) addMessage(e, 'ua-critical');
return e;
}
function defaultAbortHandler(abort, showMessage) {
const e = 'XHR: abort';
console.error(e);
if (showMessage) addMessage(e, 'ua-critical');
return e;
}
function defaultTimeoutHandler(timeout, showMessage) {
const e = 'XHR: timeout';
console.error(e);
if (showMessage) addMessage(e, 'ua-critical');
return e;
}
function addArtistField() { exec(function() { AddArtistField() }) }
function removeArtistField() { exec(function() { RemoveArtistField() }) }
function queryItunesAPI(key, params) {
return queryGenericAPI('itunes.apple.com', key, params);
}
function queryDeezerAPI(key, params) {
return queryGenericAPI('api.deezer.com', key, params);
}
function queryDiscogsAPI(key, params) {
if (prefs.discogs_key && prefs.discogs_secret) {
var hdr = { Authorization: 'Discogs key=' + prefs.discogs_key + ', secret=' + prefs.discogs_secret };
} else if (discogs_token) hdr = { Authorization: 'Discogs token=' + discogs_token };
return queryGenericAPI('api.discogs.com', key, params, hdr);
}
function queryMusicbrainzAPI(key, params) {
return queryGenericAPI('musicbrainz.org', 'ws/2/' + key + '/', Object.assign({ fmt: 'json' }, params));
}
function querySpotifyAPI(key, params) {
return key ? setToken().then(credentials => queryGenericAPI('api.spotify.com', 'v1/' + key, params, {
'Authorization': credentials.token_type + ' ' + credentials.access_token,
})) : Promise.reject('No API expression');
function setToken() {
if (isTokenValid()) return Promise.resolve(spotifyCredentials);
if (!spotify_clientid || !spotify_clientsecret) return Promise.reject('Spotify credentials not configured');
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(spotify_clientid + ':' + 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: error => { reject(defaultErrorHandler(error, false)) },
onabort: abort => { reject('XHR: abort') },
ontimeout: timeout => { reject('XHR: timeout') },
});
});
}
function isTokenValid() {
return spotifyCredentials.token_type && spotifyCredentials.token_type
&& spotifyCredentials.access_token && spotifyCredentials.expires >= new Date().getTime() + 30;
}
}
function queryLastFmAPI(method, params) {
return lastfm_api_key ? queryGenericAPI('ws.audioscrobbler.com', '2.0/', Object.assign({
method: method,
api_key: lastfm_api_key,
format: 'json',
}, params || {})) : Promise.reject('Last.fm API key not configured');
}
function queryGenericAPI(domain, key, params, headers) {
if (!key) return Promise.reject(new Error('Keyword missing'));
var retryCount = 0;
return new Promise(function(resolve, reject) {
var url = 'https://' + domain + '/' + key;
var query = new URLSearchParams(params || undefined).toString();
if (query.length > 0) url += '?' + query;
if (typeof headers != 'object') headers = {};
headers.Accept = 'application/json';
queryInternal();
function queryInternal() {
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'json',
headers: headers,
onload: function(response) {
if (response.status == 503) return http503Handler(response);
if (response.readyState == 4 || response.status == 200) resolve(response.response);
else reject(defaultErrorHandler(response, false));
},
onerror: error => error.status == 503 ? http503Handler(error) : reject(defaultErrorHandler(error, false)),
onabort: abort => reject('XHR: abort'),
ontimeout: timeout => reject('XHR: timeout'),
});
}
function http503Handler(response) {
if (retryCount++ > 10) reject(defaultErrorHandler(response, false));
setTimeout(function() { queryInternal() }, 1000);
}
});
}
function getMusicbrainzCovers(mbrid) {
return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET',
url: 'https://coverartarchive.org/release/' + mbrid,
responseType: 'json',
onload: function(response) {
if (response.status == 404) return resolve(null);
if (response.readyState != 4 || response.status != 200) return reject(defaultErrorHandler(response));
resolve([response.response.release, response.response.images.filter(function(image) {
return image.front || Array.isArray(image.types) && image.types.includes('Front');
}).map(image => image.image)]);
},
onerror: error => reject(defaultErrorHandler(error)),
onabort: abort => reject(defaultAbortHandler(abort)),
ontimeout: timeout => reject(defaultTimeoutHandler(timeout)),
}));
}
} // fillFromText
function addMessage(text, cls) {
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[text instanceof HTML ? 'innerHTML' : 'textContent'] = text;
return elem.appendChild(div);
}
function setHandlers() {
if (prefs.cleanup_descriptions) ['form.create_form', 'form.edit_form', 'form#request_form'].forEach(function(sel) {
if ((ref = document.querySelector(sel)) != null) ref.addEventListener('submit', cleanupDescriptions);
});
if ((ref = document.getElementById('yadg_input')) != null) ref.ondrop = clear0;
if (!isNWCD) {
if ((ref = document.getElementById('image') || document.querySelector('input[name="image"]')) != null) {
ref.ondragover = voidDragHandler;
ref.ondblclick = imageClear;
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;
}
}
Array.from(document.getElementsByTagName('textarea')).forEach(function(textArea) {
if (textArea.className == 'ua-input') return;
textArea.ondragover = voidDragHandler;
textArea.ondrop = descDropHandler;
textArea.onpaste = descPasteHandler;
});
}
function html2php(node, url, tagChain = []) {
if (!node || typeof node != 'object') return null;
switch (node.nodeType) {
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('url=' + 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 (ctxt.fillStyle != '#000000' && /^#(?:[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(/=.*$/, ''))));
});
if (node.nodeName = 'A' && text[2].trim().length <= 0) {
text[2] = removeRedirect(node.href);
tags.splice(-1, 1, 'url');
}
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);
}
}
case Node.TEXT_NODE:
return node.wholeText.replace(/\s+/g, ' ');
case Node.DOCUMENT_NODE:
return html2php(node.body, url);
}
return '';
}
function coverPreview(anchor, src, size) {
if (!prefs.auto_preview_cover || anchor.parentNode.previousElementSibling == null) return;
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 (urlParser.test(src)) {
child.onload = function(evt) {
this.onload = null;
if (!this.naturalWidth || !this.naturalHeight) return; // invalid image
(size > 0 ? Promise.resolve(size) : getRemoteFileSize(src)).then(function(size) {
var warn = prefs.huge_image_warning && size > prefs.huge_image_warning * 2**20;
var html = warn ? '<strong style="color: #ff4c4c;">' + formattedSize(size) + '</strong>' : formattedSize(size);
div.innerHTML = this.naturalWidth + '×' + this.naturalHeight + ' (' + html + ')';
if (!warn) return;
addMessage('Warning: high cover size (' + formattedSize(size) + ')', 'ua-warning');
}.bind(this)).catch(reason => { div.textContent = this.naturalWidth + '×' + this.naturalHeight });
}
child.src = src;
} else div.textContent = child.src = '';
}
function getRemoteFileSize(url) {
return new Promise(function(resolve, reject) {
var imageSize, abort = GM_xmlhttpRequest({
method: 'GET', url: url, responseType: 'blob',
onreadystatechange: function(response) {
if (imageSize || response.readyState < 2
|| !/^Content-Length:\s*(\d+)\b/im.test(response.responseHeaders)
|| !(imageSize = parseInt(RegExp.$1))) return;
resolve(imageSize);
abort.abort();
},
onload: function(response) { // fail-safe
if (imageSize) return;
if (response.readyState == 4 && response.status == 200) resolve(response.responseText.length);
else reject(new Error('Image not accessible'));
},
});
});
}
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 cleanupDescriptions(evt) {
descriptionFields.forEach(function(ID) {
if ((ref = evt.target.querySelector('textarea#' + ID)) == null || ref.textLength <= 0) return;
var clean = ref.value
.replace(/[ \t]*Vinyl rip by \[color=\S+\]\[\/color\]\s*/im, '')
.replace(/\[u\]Lineage:\[\/u\]\n\n/i, '')
for (var i = 0; i < 3; ++i) clean = clean.replace(/\s*\[(\w+)(?:=([^\[\]]*))?\]\[\/\1\]/gm, '');
const drMatch = [
/(^| \| )DR(\d+)$\s+/m,
/(?:^| \| )DR(\d+)(?=$| \| )/gm,
];
var m = /\[hide=DR(\d+)?\]\[pre\]/i.exec(clean);
//if (m != null && drMatch[0].test(clean) && RegExp.$2 == m[1]) clean = clean.replace(drMatch[0], '$1');
if (m != null && drMatch[1].test(clean) && RegExp.$1 == m[1]) clean = clean.replace(drMatch[1], '');
ref.value = clean.replace(/(?:[ \t]*\r?\n){3,}/g, '\n\n').replace(/[ \t]+$/gm, '').trim();
});
return true;
}
function reInParenthesis(expr) { return new RegExp('\\s+\\([^\\(\\)]*'.concat(expr, '[^\\(\\)]*\\)$'), 'i') }
function reInBrackets(expr) { return new RegExp('\\s+\\[[^\\[\\]]*'.concat(expr, '[^\\[\\]]*\\]$'), 'i') }
function notMonospaced(str) {
return false; // TODO: reliable test for nonstandard with literals
}
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 formattedSize(size) {
return size < 1024**1 ? Math.round(size) + ' B'
: size < 1024**2 ? (Math.round(size * 10 / 2**10) / 10) + ' KiB'
: size < 1024**3 ? (Math.round(size * 100 / 2**20) / 100) + ' MiB'
: size < 1024**4 ? (Math.round(size * 100 / 2**30) / 100) + ' GiB'
: size < 1024**5 ? (Math.round(size * 100 / 2**40) / 100) + ' TiB'
: (Math.round(size * 100 / 2**50) / 100) + ' PiB';
}
function safeText(unsafeText) {
let div = document.createElement('div');
div.innerText = unsafeText || '';
return div.innerHTML;
}
function testImageUrl(url) {
if (!urlParser.test(url)) return Promise.reject('not an image');
if (['png', 'jpg', 'jpeg', 'gif', 'bmp'].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 = img.ontimeout = error => { reject(url.concat(' not valid image')) };
img.src = url;
});
}
function testImageUrls(urls) {
return Array.isArray(urls) ? Promise.all(urls.map(testImageUrl)) : Promise.reject('URLs not an array');
}
function uaInsert(evt) {
if (evt instanceof DragEvent && evt.target.textLength > 0) return false;
/*if (isFirefox || !evt.target.ctrlKey) */evt.target.value = '';
if (!(prefs.autfill_delay > 0)) return true;
autofill = true;
setTimeout(fillFromText, prefs.autfill_delay);
}
// Firefox accepts dropped playlist in malformed form, try to detect and correct it
function fixFirefoxDropBug(evt) {
if (evt.target == null || evt.target.textLength <= 0) return true;
var tl = (Math.sqrt(4 * evt.target.value.split('\n').length - 3) + 1) / 2;
if (tl < 2 || tl != Math.floor(tl) || evt.target.textLength % tl != 0) return true;
var l = evt.target.textLength / tl;
var s = evt.target.value.slice(0, l);
for (var i = 1; i < tl; ++i) if (evt.target.value.slice(i * l, (i + 1) * l) != s) return true;
evt.target.value = s;
return true;
}
function imageClear(evt) {
evt.target.value = '';
coverPreview(evt.target, null);
}
function imageDropHandler(evt) { return imageDataHandler(evt, evt.dataTransfer) }
function imagePasteHandler(evt) { return imageDataHandler(evt, evt.clipboardData) }
function imageDataHandler(evt, data) {
if (!data) return true;
if (data.files.length > 0 && data.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';
let size = data.files[0].size;
upload2PTPIMG([data.files[0]]).then(function(urls) {
evt.target.value = urls[0];
evt.target.style.backgroundColor = '#004000';
evt.target.hTimer = setTimeout(function() {
evt.target.style.backgroundColor = null;
delete evt.target.hTimer;
}, 10000);
coverPreview(evt.target, urls[0], size);
}).catch(function(error) {
evt.target.style.backgroundColor = null;
imageClear(evt);
alert(error);
}).then(function() { evt.target.disabled = false });
return false;
} else if (data.items.length > 0) {
testImageUrl((data.getData('text/uri-list') || data.getData('text/plain')).split(/\r?\n/)[0]).then(function(url) {
evt.preventDefault();
evt.stopPropagation();
evt.target.value = url;
coverPreview(evt.target, url);
if (prefs.auto_rehost_cover && !url.toLowerCase().startsWith(imghostOrigin)) {
/*if (rehostItBtn != null) rehostItBtn.click(); else */{
evt.target.disabled = true;
rehost2PTPIMG([url])
.then(function(urls) { if (urls.length > 0) evt.target.value = urls[0] })
.catch(e => { alert(e) })
.then(function() { evt.target.disabled = false });
return false;
}
}
}).catch(e => { console.warn(e) });
}
}
function descDropHandler(evt) {
if (evt.dataTransfer == null || evt.shiftKey) return true;
if (evt.dataTransfer.files.length > 0) {
evt.preventDefault();
evt.stopPropagation();
let images = [];
Array.from(evt.dataTransfer.files).forEach(function(file) {
switch (file.type) {
case '':
if (!['log'/*, 'nfo'*/].some(ext => file.name.toLowerCase().endsWith('.' + ext))) break;
case 'text/plain':
//case 'text/nfo': // malformed encoding
case 'text/log':
evt.target.disabled = true;
file.getText(file.name.toLowerCase().endsWith('.nfo') ? 'ibm850' : '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) var DR = parseInt(RegExp.$1);
var tag = isDR || file.name.toLowerCase().endsWith('.nfo') ? 'pre' : 'code';
var php = isDR ? '[hide=DR' + RegExp.$1 + '][' + tag + ']' + text + '[/' + tag + '][/hide]'
: '[hide=' + file.name + '][' + tag + ']' + text + '[/' + tag + '][/hide]';
if (evt.target.textLength <= 0) evt.target.value = php; else if (evt.ctrlKey) {
evt.target.value = evt.target.value.slice(0, evt.rangeOffset) +
php + evt.target.value.slice(evt.rangeOffset);
} else if (isDR && /\[hide=DR\d*\]\[pre\]\[\/pre\]/i.test(evt.target.value)) {
evt.target.value = RegExp.leftContext + php.slice(0, -7) + RegExp.rightContext;
} else if (isDR && /\[hide=DR(\d*)\]((?:\[pre\](foobar2000[\s\S]+?)^\[\/pre\]\s*)+)(?:\[pre\]\[\/pre\])?/im.test(evt.target.value)) {
php = '[hide=DR';
if (parseInt(RegExp.$1) == DR) php += RegExp.$1;
evt.target.value = RegExp.leftContext.concat(php, ']', RegExp.$2.trim(), '\n[pre]', text, '[/pre]', RegExp.rightContext);
} else if (!isDR && /\[hide\](?:\[code\]\[\/code\])?\[\/hide\]/i.test(evt.target.value)) {
evt.target.value = RegExp.leftContext + php + RegExp.rightContext;
} else if (!isDR && /(\[hide=[^\]]+\])(?:\[code\]\[\/code\])?(\[\/hide\])/i.test(evt.target.value)) {
evt.target.value = RegExp.leftContext.concat(RegExp.$1, '[code]', text, '[/code]', RegExp.$2, RegExp.rightContext);
} else evt.target.value += '\n\n'.concat(php);
}).catch(function(e) { alert(e) }).then(function() {
if (!evt.target.style.background) evt.target.disabled = false;
});
break;
case 'image/png':
case 'image/jpeg':
case 'image/gif':
case 'image/bmp':
//case 'image/webp':
//case 'image/svg+xml':
images.push(file);
break;
}
});
if (images.length > 0) {
evt.target.disabled = true;
evt.target.style.background = '#FF000040 no-repeat center center url(' + ulImgData +')';
//evt.target.style.background = '#FF000040 no-repeat center center url(https://svgshare.com/i/H16.svg)';
upload2PTPIMG(images).then(urlHandler.bind({ tag: 'img' })).catch(error => { alert(error) }).then(function() {
evt.target.style.background = null;
evt.target.disabled = false;
});
}
} else if (evt.dataTransfer.items.length > 0) {
evt.preventDefault();
evt.stopPropagation();
let content = evt.dataTransfer.getData('text/uri-list');
if (content) {
content = content.split(/\r?\n/);
testImageUrls(content).then(function(urls) {
if (prefs.auto_rehost_cover) {
evt.target.disabled = true;
rehost2PTPIMG(urls).then(urlHandler.bind({ tag: 'img' })).catch(e => { alert(e) }).then(function() {
evt.target.disabled = false;
});
} else urlHandler.bind({ tag: 'img' })(content);
}).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')) {
textHandler(html2php(domParser.parseFromString(content, 'text/html')).collapseGaps());
} else if (content = evt.dataTransfer.getData('text/plain')) {
textHandler(content);
}
}
function urlHandler(urls) {
const 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 (evt.ctrlKey) {
evt.target.value = evt.target.value.slice(0, evt.rangeOffset) +
php + evt.target.value.slice(evt.rangeOffset);
} 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 textHandler(php) {
if (evt.target.textLength <= 0) evt.target.value = php; else if (evt.ctrlKey) {
evt.target.value = evt.target.value.slice(0, evt.rangeOffset) + php + evt.target.value.slice(evt.rangeOffset);
} else evt.target.value += '\n\n'.concat(php);
}
}
function descPasteHandler(evt) {
if (evt.clipboardData == null || evt.clipboardData.items.length <= 0) return true;
var content = evt.clipboardData.getData('text/html');
if (!content) return true;
evt.preventDefault();
evt.stopPropagation();
content = html2php(domParser.parseFromString(content, 'text/html')).collapseGaps();
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';
evtSrc.hTimer = setTimeout(function() {
evtSrc.style.backgroundColor = null;
delete evtSrc.hTimer;
}, 10000);
coverPreview(image, results[0], evt.dataTransfer.files[0].size);
} else evtSrc.style.backgroundColor = null;
}).catch(function(error) {
evtSrc.style.backgroundColor = null;
alert(error);
}).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 voidDragHandler(evt) { evt.preventDefault() }
function upload2PTPIMG(files, elem) {
var frs = Array.from(files).filter(function(file) {
return file instanceof File && ['jpeg', 'png', 'gif', 'bmp'].some(ext => file.type == 'image/' + ext);
}).map(file => new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() { resolve({ file: file, data: reader.result }) };
reader.onerror = reader.onabort = reader.ontimeout = error => { reject('FileReader error (' + file.name + ')') };
reader.readAsBinaryString(file);
}));
return frs.length > 0 ? getPTPIMGapiKey().then(apiKey => Promise.all(frs).then(images => new Promise(function(resolve, reject) {
const boundary = '------NN-GGn-PTPIMG';
var data = '--' + boundary + '\r\n';
images.forEach(function(image, ndx) {
data += 'Content-Disposition: form-data; name="file-upload[' + ndx +
']"; filename="' + image.file.name.toASCII() + '"\r\n';
data += 'Content-Type: ' + image.file.type + '\r\n\r\n';
data += image.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: imghostOrigin + '/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 => imghostOrigin + '/' + item.code + '.' + item.ext));
} else {
reject(`Response error ${response.readyState}/${response.status} (${response.statusText})`);
}
},
onprogress: elem instanceof HTMLInputElement ?
progress => { elem.value = 'Uploading... (' + progress.position + '%)' } : undefined,
onerror: function(error) {
reject(new Error(`XHR readyState=${error.readyState}, status=${error.status} (${error.error})`));
},
onabort: abort => { reject('XHR: abort') },
ontimeout: timeout => { reject('XHR: timeout') },
});
}))) : Promise.reject('Nothing to upload');
}
function rehost2PTPIMG(urls) {
return testImageUrls(urls).then(urls => getPTPIMGapiKey().then(apiKey => 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.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: imghostOrigin + '/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 => imghostOrigin + '/' + item.code + '.' + item.ext));
} else {
reject(`Response error ${response.readyState}/${response.status} (${response.statusText})`);
}
},
onerror: function(error) {
reject(new Error(`XHR readyState=${error.readyState}, status=${error.status} (${error.error})`));
},
onabort: abort => { reject('XHR: abort') },
ontimeout: timeout => { reject('XHR: timeout') },
});
})));
}
function getPTPIMGapiKey() {
try {
var apiKey = prefs.ptpimg_api_key || JSON.parse(window.localStorage.ptpimg_it).api_key;
if (apiKey) return Promise.resolve(apiKey);
} catch(e) { console.warn(e) }
return new Promise((resolve, reject) => GM_xmlhttpRequest({
method: 'GET',
url: imghostOrigin,
responseType: 'document',
onload: function(response) {
if (response.readyState == 4 && response.status == 200) {
apiKey = domParser.parseFromString(response.responseText, 'text/html').getElementById('api_key');
if (apiKey != null && apiKey.value) {
GM_setValue('ptpimg_api_key', prefs.ptpimg_api_key = apiKey.value);
resolve(apiKey.value);
alert(`Your PTPIMG API key ${apiKey.value} was successfully saved`);
} else reject(`PTPIMG API key isn\'t configured.
Please login to ${imghostOrigin}/ and repeat the action
If you don\'t have PTPIMG account, to avoid this warning in
future consider to set auto_rehost_cover to 0 in preferences
(Tampermonkey menu -> right click to Upload Assistant -> Storage tab)`);
} else reject(new Error('XHR readyState=' + response.readyState +
', status=' + response.status + ' (' + response.error + ')'));
},
onerror: function(error) {
reject(new Error(`XHR readyState=${error.readyState}, status=${error.status} (${error.error})`));
},
onabort: abort => { reject('XHR: abort') },
ontimeout: timeout => { reject('XHR: timeout') },
}));
}
function dcFmtToGazelle(format) {
if (/^(?:CD|CDi|CDr|HDCD)\b/.test(format)) return 'CD';
if (/\b(?:File|AAC|AIFC|AIFF|ALAC|AMR|APE|DFF|DSD|FLAC|MP2|MP3|ogg-vorbis|Opus|SHN|WAV|WavPack|WMA|WMV)\b/.test(format)) return 'WEB';
if (/^(?:Vinyl|LP|\d+(?:\.\d+)?\s*")$/.test(format)) return 'Vinyl';
if (/\b(?:SACD|Hybrid)\b/.test(format)) return 'SACD';
if (/^(?:Blu[ \-]?ray)\b/i.test(format)) return 'Blu-Ray';
if (/^(?:DVD|HD\s+DVD)/.test(format)) return 'DVD';
if (/^(?:Cassette|Microcassette)$/i.test(format)) return 'Cassette';
if (/^(?:DAT)$/.test(format)) return 'DAT';
if (/^(?:Soundboard)$/i.test(format)) return 'Soundboard';
//if (/^(?:Memory\s+Stick)$/i.test(format)) return ??
return null;
}