// ==UserScript==
// @name Supraphonline: kopírovat metadata do schránky
// @name-cz Supraphonline: kopírovat metadata do schránky
// @name-en Supraphonline: copy metadata to clipboard
// @namespace https://greasyfork.org/cs/users/321857-anakunda
// @version 1.2
// @description Zkopíruje playlist do schránky tak aby jej bylo možno v hudebním přehrávači aplikovat na tagy
// @author Já, Osobně
// @copyright 2020, Anakunda (https://greasyfork.org/cs/users/321857-anakunda)
// @license GPL-3.0-or-later
// @iconURL https://www.supraphonline.cz/favicon.ico
// @match http*://*.supraphonline.cz/album/*
// @grant GM_getValue
// @grant GM_getValue
// @grant GM_setClipboard
// @grant GM_deleteValue
// @grant GM_xmlhttpRequest
// ==/UserScript==
// Výraz pro 'Automatically Fill Values' funkci ve foobaru2000:
// %album artist%%album%%date%%releasedate%%genre%%label%%catalog%%discnumber%%totaldiscs%%discsubtitle%%tracknumber%%totaltracks%%artist%%title%%performer%%composer%%media%%comment%%url%
'use strict';
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;
};
var hTimer = setInterval(function() {
var ref = document.querySelector('form.table-action');
if (ref == null) return;
clearInterval(hTimer);
var child = document.createElement('button');
child.id = 'copy-info-to-clipboard';
child.textContent = 'Kopírovat do schránky';
child.type = 'button';
child.name = 'copy-info-to-clipboard';
child.className = 'btn btn-danger topframe_login';
child.style.marginRight = '10px';
child.onclick = fetchAlbum;
ref.prepend(child);
}, 1000);
function fetchAlbum(evt) {
var original_text = evt.target.textContent;
evt.target.disabled = true;
evt.target.textContent = 'Pracuji...';
var isVA = false, ndx, artists = [], tracks = [], totalTime, discNumber, discSubtitle;
var domParser = new DOMParser();
var release = {
artists: [],
composers: [],
performers: [],
totalDiscs: 1,
};
document.querySelectorAll('h2.album-artist > a').forEach(it => { release.artists.pushUnique(it.title) });
if (release.artists.length == 0 && (ref = document.querySelector('h2.album-artist[title]')) != null) {
if (/^(?:Různí(?:\s+interpreti)?|Various(?:\s+Artists)?|VA)$/i.test(ref.title)) isVA = true;
if (isVA) release.artists = ['Various Artists']; //else albumArtist = [ref.title];
}
var ref = document.querySelector('span[itemprop="byArtist"] > meta[itemprop="name"]');
if (ref != null && /^(?:Různí\s+interpreti)$/i.test(ref.content)) isVA = true;
if ((ref = document.querySelector('h1[itemprop="name"]')) != null) release.album = ref.firstChild.data.trim();
if ((ref = document.querySelector('meta[itemprop="numTracks"]')) != null) release.totalTracks = parseInt(ref.content);
if ((ref = document.querySelector('meta[itemprop="genre"]')) != null) release.genre = ref.content;
if ((ref = document.querySelector('li.album-version > div.selected > div')) != null) {
if (/\b(?:CD)\b/.test(ref.textContent)) release.media = 'CD';
if (/\b(?:LP)\b/.test(ref.textContent)) release.media = 'Vinyl';
if (/\b(?:MP3)\b/.test(ref.textContent)) release.media = 'WEB-DL';
if (/\b(?:FLAC)\b/.test(ref.textContent)) release.media = 'WEB-DL';
if (/\b(?:Hi[\s\-]*Res)\b/.test(ref.textContent)) release.media = 'WEB-DL';
}
document.querySelectorAll('ul.summary > li').forEach(function(it) {
if (it.children.length < 1) return;
if (it.children[0].textContent.startsWith('Nosič')) release.media = it.lastChild.textContent.trim();
if (it.children[0].textContent.startsWith('Datum vydání')) release.releaseDate = normalizeDate(it.lastChild.textContent);
//if (it.children[0].textContent.startsWith('Žánr')) release.genre = it.lastChild.textContent.trim();
if (it.children[0].textContent.startsWith('Vydavatel')) release.label = it.lastChild.textContent.trim();
if (it.children[0].textContent.startsWith('Katalogové číslo')) release.catalogue = it.lastChild.textContent.trim();
if (it.children[0].textContent.startsWith('Celková stopáž')) totalTime = timeStringToTime(it.lastChild.textContent.trim());
if (/^(?:\([PC]\)|℗|©)$/i.test(it.children[0].textContent)) release.albumYear = extract_year(it.lastChild.data);
});
[
[/^(?:Orchestrální\s+hudba)$/i, 'Orchestral Music'],
[/^(?:Komorní\s+hudba)$/i, 'Chamber Music'],
[/^(?:Vokální)$/i, 'Classical, Vocal'],
[/^(?:Klasická\s+hudba)$/i, 'Classical'],
[/^(?:Melodram)$/i, 'Classical, Melodram'],
[/^(?:Symfonie)$/i, 'Symphony'],
[/^(?:Vánoční\s+hudba)$/i, 'Christmas Music'],
[/^(?:Alternativní)$/i, 'Alternative'],
[/^(?:Dechová\s+hudba)$/i, 'Brass Music'],
[/^(?:Elektronika)$/i, 'Electronic'],
[/^(?:Folklor)$/i, 'Folclore, World Music'],
[/^(?:Instrumentální\s+hudba)$/i, 'Instrumental'],
[/^(?:Latinské\s+rytmy)$/i, 'Latin'],
[/^(?:Meditační\s+hudba)$/i, 'Meditative'],
[/^(?:Pro\s+děti)$/i, 'Children'],
].forEach(it => { if (it[0].test(release.genre)) release.genre = it[1] });
const creators = [
'autoři',
'interpreti',
'tělesa',
'digitalizace',
];
for (var i = 0; i < 4; ++i) artists[i] = {};
document.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;
ref = it.querySelector('span');
let key = ref != null ? ref.textContent.replace(/\s*:.*$/, '').toLowerCase() : undefined;
// [
// [/^(?:zpěv)$/, 'vocal'],
// [/^(?:hudba)$/, 'music'],
// [/^(?:původní\s+text)$/, 'original lyrics'],
// [/^(?:text)$/, 'lyrics'],
// [/^(?:autor)$/, 'author'],
// [/^(?:účinkuje)$/, 'participating'],
// [/^(?:nahrál)$/, 'recorded'],
// [/^(?:sbormistr)$/, 'choirmaster'],
// [/^(?:řídí|dirigent)$/, 'conductor'],
// ].forEach(it => { if (it[0].test(key)) key = it[1] });
if (!(artists[ndx][key] instanceof Array)) artists[ndx][key] = [];
artists[ndx][key].pushUnique(it.querySelector('a').textContent.trim());
}
});
release.description = Array.from(document.querySelectorAll('div[itemprop="description"] p'))
.map(it => it.textContent.trim()).join(' ').replace(/[\r\n]+/g, ' ');
for (i = 1; i < 3; ++i) Object.keys(artists[i]).forEach(it => { release.performers.pushUnique(...artists[i][it]) });
Object.keys(artists[0]).forEach(it => { release.composers.pushUnique(...artists[0][it]) });
if (release.artists.length == 0 && !isVA) release.artists = release.performers;
var promises = [];
document.querySelectorAll('table.table-tracklist > tbody > tr').forEach(function(it) {
if (it.classList.contains('cd-header')) {
discNumber = /\b\d+\b/.test(it.querySelector(':scope h3').firstChild.data.trim()) ?
parseInt(RegExp.lastMatch) : undefined;
if (discNumber > release.totalDiscs) release.totalDiscs = discNumber;
}
if (it.classList.contains('song-header')) discSubtitle = it.children[0].title.trim() || undefined;
if (it.classList.contains('track') && it.id) {
let track = {
id: parseInt(it.id.replace(/^track-/i, '')),
artists: [],
performers: [],
composers: [],
conductors: [],
producers: [],
discNumber: discNumber,
discSubtitle: discSubtitle,
};
if (/^\s*(\d+)\.?\s*$/.test(it.children[0].firstChild.textContent)) track.trackNumber = parseInt(RegExp.$1);
ref = it.querySelectorAll('meta[itemprop="name"]');
if (ref.length > 0) track.title = ref[0].content;
if (/^PT(\d+)H(\d+)M(\d+)S$/i.test(it.querySelector('meta[itemprop="duration"]').content)) {
track.duration = parseInt(RegExp.$1 || 0) * 60**2 + parseInt(RegExp.$2 || 0) * 60 + parseInt(RegExp.$3 || 0);
}
tracks.push(track);
ref = it.querySelector('td > a.trackdetail');
if (ref != null) promises.push(new Promise(function(resolve, reject) {
GM_xmlhttpRequest({
method: 'GET',
url: ref.href,
responseType: 'text/html',
context: track,
onload: function(response) {
if (response.readyState != 4 || response.status != 200) {
reject('Response error ' + response.status + ' (' + response.statusText + ')');
return;
}
let dom = domParser.parseFromString(response.responseText, "text/html");
if (dom == null) reject('Parse error');
var track = response.context;
var tr_det = dom.querySelector('div[data-swap="trackdetail-' + track.id + '"] > div > div.row');
if (tr_det == null) reject('No trackdetail for track-' + track.id);
tr_det.querySelectorAll('div:nth-of-type(1) > ul > li > span').forEach(function(it) {
if (it.textContent.startsWith('Nahrávka dokončena')) track.recordDate = it.nextSibling.data.trim();
if (it.textContent.startsWith('Místo nahrání')) track.recordPlace = it.nextSibling.data.trim();
if (it.textContent.startsWith('Rok prvního vydání')) track.albumYear = parseInt(it.nextSibling.data);
if (it.textContent.startsWith('(P)')) track.copyright = it.nextSibling.data.trim();
if (it.textContent.startsWith('Žánr')) track.genre = it.nextSibling.data.trim();
});
tr_det.querySelectorAll('div:nth-of-type(2) > ul > li > span').forEach(function(it) {
function oneOf(arr) { return arr.some(k => elem[0].startsWith(k)) }
var elem = [it.textContent.trim().replace(/:.*/, ''), it.nextElementSibling.textContent.trim()];
if (oneOf(['hudba', 'text', 'hudba+text', 'původní text', 'český text', 'libreto'])) {
track.composers.push(elem);
} else if (oneOf(['nahrál'])) {
} else if (oneOf(['dirigent', 'řídí'])) {
track.conductors.push(elem);
} else if (oneOf(['produkce'])) {
track.producers.push(elem[1]);
} else {
track.performers.push(elem);
}
});
// TODO: vyfiltrovat track.artists z track.performers
resolve(true);
},
onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
ontimeout: function() { reject('Timeout') },
});
}));
}
});
Promise.all(promises).then(function() {
var clipBoard = '';
tracks.sort(function(a, b) {
var k = a.discNumber - b.discNumber;
return isNaN(k) || k == 0 ? a.trackNumber - b.trackNumber : k;
}).forEach(function(track) {
clipBoard += [
joinArtists(release.artists),
release.album,
release.albumYear || track.albumYear,
release.releaseDate,
track.genre || release.genre,
release.label,
release.catalogue,
track.discNumber,
release.totalDiscs > 1 ? release.totalDiscs : undefined,
track.discSubtitle,
track.trackNumber,
release.totalTracks,
joinArtists(track.artists.length > 0 ? track.artists : release.artists),
track.title,
track.performers.concat(track.conductors).map(desc).join(', ') || release.performers.join(', '),
track.composers.map(desc).join(', ') || release.composers.join(', '),
release.media,
release.description,
document.location.origin + document.location.pathname,
].join('\x1E') + '\n';
});
GM_setClipboard(clipBoard, 'text');
}).catch(e => { alert(e) }).then(function() {
evt.target.disabled = false;
evt.target.textContent = original_text;
});
}
function desc(el) {
var res = el[1];
if (el[0] && el[0] != 'hudební tělesa') res += ' (' + el[0] + ')';
return res;
}
function joinArtists(list) {
return list.length < 3 ? list.join(' a ') : list.slice(0, -1).join(', ') + ' a ' + list[list.length - 1];
}
function normalizeDate(str) {
if (typeof str != 'string') return null;
if (/\b(d{4})-(\d+)-(\d+)\b/.test(str)) {
var D = parseInt(RegExp.$3);
var M = parseInt(RegExp.$2);
var Y = parseInt(RegExp.$1);
} else if (/\b(\d{1,2})\/(\d{1,2})\/(\d{2}|\d{4})\b/.test(str)) {
D = parseInt(RegExp.$2);
M = parseInt(RegExp.$1);
Y = parseInt(RegExp.$3);
} else if (/\b(\d{1,2})\.\s?(\d{1,2})\.\s?(\d{2}|\d{4})\b/.test(str)) {
D = parseInt(RegExp.$1);
M = parseInt(RegExp.$2);
Y = parseInt(RegExp.$3);
}
if (Y < 100) Y += 2000;
return D > 0 && M > 0 && Y > 0 ?
Y.toString().padStart(4, '0') + '-' + M.toString().padStart(2, '0') + '-' + D.toString().padStart(2, '0')
: extract_year(str);
}
function timeStringToTime(str) {
if (!/(-\s*)?\b(\d+(?::\d{2})*(?:\.\d+)?)\b/.test(str)) return null;
var t = 0, a = RegExp.$2.split(':');
while (a.length > 0) t = t * 60 + parseFloat(a.shift());
return RegExp.$1 ? -t : t;
}
function extract_year(expr) {
if (typeof expr == '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());
}