Supraphon Online: Fetch album info to clipboard

Zkopíruje playlist do schránky tak aby jej bylo možno v hudebním přehrávači aplikovat na tagy

Verzia zo dňa 20.10.2019. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Supraphon Online: Fetch album info to clipboard
// @namespace    https://greatest.deepsurf.us/cs/users/321857-anakunda
// @version      1.1
// @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ě
// @iconURL      https://www.supraphonline.cz/favicon.ico
// @match        http*://*.supraphonline.cz/album/*
// @grant        RegExp
// @grant        GM_getValue
// @grant        GM_getValue
// @grant        GM_setClipboard
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @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('div.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 + ')');
			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 => it.textContent.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(it.nextElementSibling.textContent.trim());
			  } 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());
}