您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Accurate filling of new upload/request and group/request edit forms based on foobar2000's playlist selection or web link, offline and online release integrity check, tracklist format customization, featured artists extraction, classical works formatting, cover art fetching from store, checking for previous upload, form enhancements and more
当前为
// ==UserScript== // @name [RED/OPS] Upload Assistant // @namespace https://greasyfork.org/users/321857-anakunda // @version 1.353.0 // @description Accurate filling of new upload/request and group/request edit forms based on foobar2000's playlist selection or web link, offline and online release integrity check, tracklist format customization, featured artists extraction, classical works formatting, cover art fetching from store, checking for previous upload, form enhancements and more // @author Anakunda // @copyright 2019-21, Anakunda (https://greasyfork.org/users/321857-anakunda) // @license GPL-3.0-or-later // @icon data:image/png;base64,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 // @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_openInTab // @grant GM_registerMenuCommand // @grant GM_setClipboard // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js // @require https://openuserjs.org/src/libs/Anakunda/bencode-min.js // @require https://openuserjs.org/src/libs/Anakunda/xhrLib.js // @require https://openuserjs.org/src/libs/Anakunda/progressBars.js // @require https://openuserjs.org/src/libs/Anakunda/imageHostUploader.js // @require https://openuserjs.org/src/libs/Anakunda/QobuzLib.js // @require https://openuserjs.org/src/libs/Anakunda/langCodes.js // @require https://openuserjs.org/src/libs/Anakunda/libStringDistance.js // ==/UserScript== // expression for 'Automatically Fill Values' in foobaru200: // %album artist%%album%%releasedate%%genre%%label%%discnumber%%discsubtitle%%totaldiscs%%tracknumber%%totaltracks%%artist%%title%%composer%%performer%%conductor%%media%%url%%comment%%releasetype%%upc%%isrc%%explicit% Array.prototype.includesCaseless = function(str) { if (typeof str != 'string') return false; str = str.toLowerCase(); return this.some(elem => typeof elem == 'string' && elem.toLowerCase() == str); }; 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.equalCaselessTo = function(arr) { function adjust(elem) { return typeof elem == 'string' ? elem.toLowerCase() : elem } return Array.isArray(arr) && arr.length == this.length && arr.map(adjust).sort().toString() == this.map(adjust).sort().toString(); }; Array.prototype.distinctValues = function() { return this.filter((elem, index, arrRef) => arrRef.indexOf(elem) == index); }; Array.prototype.flatten = function() { return this.reduce(function(flat, toFlatten) { return flat.concat(Array.isArray(toFlatten) ? toFlatten.flatten() : toFlatten); }, [ ]); }; String.prototype.toASCII = function() { return this.normalize("NFKD").replace(/[\x00-\x1F\u0080-\uFFFF]/g, ''); }; String.prototype.flatten = function() { return this.replace(/\n/g, '\x1C').replace(/\r/g, '\x1D'); }; String.prototype.collapseGaps = function() { return this.replace(/(?:[ \t\xA0]*\r?\n){3,}/g, '\n\n').replace(/\[(\w+)\]\[\/\1\]/ig,'').trim(); }; function queryQobuzAPI(endPoint, params) { if (!endPoint) return Promise.reject('No API endpoint'); const qobuzApi = (function() { try { let qobuzApis = JSON.parse(window.sessionStorage.qobuzApis); if (qobuzApis.length > 0) return Promise.resolve(qobuzApis[qobuzApis.length - 1]); } catch(e) { } return globalXHR('https://play.qobuz.com/login').then(function({document}) { let script = document.body.querySelector('script[src]:last-of-type'); if (script == null) return Promise.reject('invalid document structure'); let url = new URL(script.src); url.hostname = 'play.qobuz.com'; return globalXHR(url, { responseType: 'text' }); }).then(function({responseText}) { let qobuzApis = responseText.match(/\b(?:n\.qobuzapi)=(\{.*?\})/g) .map(s => eval('(' + /\b(?:n\.qobuzapi)=(\{.*?\})/.exec(s)[1] + ')')); if (qobuzApis.length <= 0) return Promise.reject('invalid format (bundle.js)'); window.sessionStorage.qobuzApis = JSON.stringify(qobuzApis); return qobuzApis[qobuzApis.length - 1]; }); })(); function getUser() { try { let userInfo = JSON.parse(window.localStorage.qobuzUserInfo); if (!userInfo.user_auth_token) throw 'User info invalid'; return Promise.resolve(userInfo); } catch(e) { let userId = GM_getValue('userid'), password = GM_getValue('password'); if (!userId || !password) return Promise.reject('insufficient user credentials'); return qobuzApi.then(qobuzApi => globalXHR(qobuzApi.base_url + qobuzApi.base_method + 'user/login', { responseType: 'json', headers: { 'X-App-Id': qobuzApi.app_id } }, new URLSearchParams({ username: userId, password: password }))).then(function({response}) { window.localStorage.qobuzUserInfo = JSON.stringify(response); if (!response.user_auth_token) throw 'User info invalid'; return response; }); } } return getUser().then(user => qobuzApi.then(function(qobuzApi) { let url = new URL(qobuzApi.base_url + qobuzApi.base_method + endPoint); if (params && typeof params == 'object') url.search = new URLSearchParams(params); return globalXHR(url, { responseType: 'json', headers: { 'X-App-Id': qobuzApi.app_id, 'X-User-Auth-Token': user.user_auth_token }, }).then(({response}) => response); })); } function copyTracks(evt) { getTracks().then(function(tracks) { GM_setClipboard(tracks.map(track => track.map(field => field !== undefined ? field : '') .join('\x1E')).join('\n'), 'text'); let img = document.createElement('img'); img.src = 'data:image/png;base64,' + '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'; img.style.height = '18px'; console.debug(evt); evt.target.textContent = ''; evt.target.append(img); }, reason => { alert(reason) }); //return false; } function getTracks() { const discParser = /^(?:CD|DIS[CK]\s+|VOLUME\s+|DISCO\s+|DISQUE\s+)(\d+)(?:\s+of\s+(\d+))?$/i; const vaParser = /^(?:Various(?:\s+Artists)?|Varios(?:\s+Artistas)?|V\/?A|\<various\s+artists\>|Různí(?:\s+interpreti)?)$/i; const VA = 'Various Artists'; const multiArtistParsers = [ /\s*[\,\;\u3001](?!\s*(?:[JjSs]r)\b)(?:\s*(?:[Aa]nd|\&)\s+)?\s*/, /\s+[\/\|\×|meets]\s+/i, ]; const ampersandParsers = [ /\s+(?:meets|vs\.?|X)\s+(?!\s*(?:[\&\/\+\,\;]|and))/i, /\s*[;\/\|\×]\s*(?!\s*(?:\s*[\&\/\+\,\;]|and))/i, /(?:\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 featArtistParsers = [ ///\s+(?:meets)\s+(.+?)\s*$/i, /* 0 */ /\s+(?:[Ww](?:ith|\.?\/)|[Aa]vec)\s+(?!his\b|her\b|Friends$|Strings$)(.+?)\s*$/, /* 1 */ /(?:\s+[\-\−\—\–\_])?\s+(?:[Ff]eaturing\s+|(?:[Ff]eat|[Ff]t|FT)\.\s*|[Ff]\.?\/\s+)([^\(\)\[\]\{\}]+?)(?=\s*(?:[\(\[\{].*)?$)/, /* 2 */ /\s+\[\s*f(?:eat(?:\.|uring)|t\.|\.?\/)\s+([^\[\]]+?)\s*\]/i, /* 3 */ /\s+\(\s*f(?:eat(?:\.|uring)|t\.|\.?\/)\s+([^\(\)]+?)\s*\)/i, /* 4 */ /\s+\[\s*(?:(?:en\s+)?duo\s+)?avec\s+([^\[\]]+?)\s*\]/i, /* 5 */ /\s+\(\s*(?:(?:en\s+)?duo\s+)?avec\s+([^\(\)]+?)\s*\)/i, /* 6 */ /\s+\[\s*(?:with|[Ww]\.?\/)\s+(?![Hh]is\b|[Hh]er\b|Friends$|Strings$)([^\[\]]+?)\s*\]/, /* 7 */ /\s+\(\s*(?:with|[Ww]\.?\/)\s+(?![Hh]is\b|[Hh]er\b|Friends$|Strings$)([^\(\)]+?)\s*\)/, ]; const featTest = /\b(?:feat(?:uring|\.)|ft\.)/i; const pseudoArtistParsers = [ /* 0 */ vaParser, /* 1 */ /^(?:#??N[\/\-]?A|[JS]r\.?)$/i, /* 2 */ /^(?:auditorium|[Oo]becenstvo|[Pp]ublikum)$/, /* 3 */ /^(?:(Special\s+)??Guests?|Friends|(?:Studio\s+)?Orchestra)$/i, /* 4 */ /^(?:Various\s+Composers)$/i, /* 5 */ /^(?:[Aa]nonym)/, /* 6 */ /^(?:traditional|trad\.|lidová)$/i, /* 7 */ /\b(?:traditional|trad\.|lidová)$/, /* 8 */ /^(?:tradiční|lidová)\s+/, /* 9 */ /^(?:[Ll]iturgical\b|[Ll]iturgick[áý])/, ]; const tailingBracketStripper = /(?:\s+(?:\([^\(\)]+\)|\[[^\[\]]+\]|\{[^\{\}]+\}))+\s*$/; const error = new Error('Failed to parse Qobus release page'); function normalizeDate(str, countryCode = undefined) { if (typeof str != 'string') return null; let match; function formatOutput(yearIndex, montHindex, dayIndex) { let year = parseInt(match[yearIndex]), month = parseInt(match[montHindex]), day = parseInt(match[dayIndex]); if (year < 30) year += 2000; else if (year < 100) year += 1900; if (year < 1000 || year > 9999 || month < 1 || month > 12 || day < 0 || day > 31) return null; return year.toString() + '-' + month.toString().padStart(2, '0') + '-' + day.toString().padStart(2, '0'); } if ((match = /\b(\d{4})-(\d{1,2})-(\d{1,2})\b/.exec(str)) != null) return formatOutput(1, 2, 3); // US, SE if ((match = /\b(\d{4})\/(\d{1,2})\/(\d{1,2})\b/.exec(str)) != null) return formatOutput(1, 2, 3); if ((match = /\b(\d{1,2})\/(\d{1,2})\/(\d{2})\b/.exec(str)) != null && (parseInt(match[1]) > 12 || /\b(?:be|it|au|nz)\b/i.test(countryCode))) return formatOutput(3, 2, 1); // BE, IT, AU, NZ if ((match = /\b(\d{1,2})\/(\d{1,2})\/(\d{2})\b/.exec(str)) != null) return formatOutput(3, 1, 2); // US, MO if ((match = /\b(\d{1,2})\/(\d{1,2})\/(\d{4})\b/.exec(str)) != null) return formatOutput(3, 2, 1); // UK, IE, FR, ES, FI, DK if ((match = /\b(\d{1,2})-(\d{1,2})-((?:\d{2}|\d{4}))\b/.exec(str)) != null) return formatOutput(3, 2, 1); // NL if ((match = /\b(\d{1,2})\. *(\d{1,2})\. *(\d{4})\b/.exec(str)) != null) return formatOutput(3, 2, 1); // CZ, DE if ((match = /\b(\d{1,2})\. *(\d{1,2})\. *(\d{2})\b/.exec(str)) != null) return formatOutput(3, 2, 1); // AT, CH, DE, LU if ((match = /\b(\d{4})\. *(\d{1,2})\. *(\d{1,2})\b/.exec(str)) != null) return formatOutput(1, 2, 3); // 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); let d = new Date(expr); return parseInt(isNaN(d) ? expr : d.getFullYear()); } 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)); } function realArtistName(artist) { return ![ pseudoArtistParsers[0], pseudoArtistParsers[1], pseudoArtistParsers[4], ].some(rx => rx.test(artist)); } function splitArtists(str, parsers = multiArtistParsers) { var result = [str]; parsers.forEach(function(parser) { for (let i = result.length; i > 0; --i) { let j = result[i - 1].split(parser).map(strip); if (j.length > 1 && j.every(twoOrMore) && !j.some(artist => pseudoArtistParsers.some(rx => rx.test(artist)))) result.splice(i - 1, 1, ...j); } }); return result; } function splitAmpersands(artists) { if (typeof artists == 'string') var result = splitArtists(artists); else if (Array.isArray(artists)) result = Array.from(artists); else return []; ampersandParsers.forEach(function(ampersandParser) { for (let i = result.length; i > 0; --i) { let j = result[i - 1].split(ampersandParser).map(strip); if (j.length <= 1 || !j.every(looksLikeTrueName)) continue; result.splice(i - 1, 1, ...j.filter(function(artist) { return !result.includesCaseless(artist) && !pseudoArtistParsers.some(rx => rx.test(artist)); })); } }); return result; } function strip(art) { return [ ///\s+(?:aka|AKA)\.?\s+(.*)$/g, tailingBracketStripper, ].reduce((acc, rx, ndx) => ndx != 1 || rx.test(acc)/* && !notMonospaced(RegExp.$1)*/ ? acc.replace(rx, '') : acc, art); } 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()); } function guessDiscNumber() { if (discParser.test(discSubtitle)) { discSubtitle = undefined; discNumber = parseInt(RegExp.$1); } } let ref, artist, album, releaseDate, totalDiscs, totalTracks, isVA, label, composer, discSubtitle, discNumber, title, url, tracks = [ ], genres = [ ], featArtists = [ ], description, releaseType, trackArtist, personnel, domParser = new DOMParser, QOBUZ_ID = document.location.pathname.replace(/^.*\//, ''); function getTrackArtists(performers, defaultPerformer) { let result = [ ]; for (let ndx = 0; ndx <= qobuzArtistLabels.length; ++ndx) result[ndx] = [ ]; if (performers) for (let it of performers.split(' - ').map(x => x.split(', '))) { // ========================================== EXPERIMENTAL ========================================== if (it.length > 2) { let ndx = it.findIndex((s, ndx) => ndx > 0 && qobuzArtistLabels.some(artistLabels => artistLabels.includes(s))); if (ndx > 1) it.splice(0, ndx, it.slice(0, ndx).join(', ')); //else if (ndx < 0) it = [it.join(', ')]; } // ================================================================================================== if (it.length > 1) for (let ndx of it.slice(1).map(roleNormalizer).map(role => qobuzArtistLabels.findIndex(roles => roles.map(roleNormalizer).includes(role)))) result[ndx >= 0 ? ndx : 13].pushUniqueCaseless(it[0]); else { result[qobuzArtistLabels.length].pushUniqueCaseless(it[0]); if (prefs.diag_mode) console.warn('Qobuz rolesless performer:', it[0]); } } //Array.prototype.push.apply(result[0], result[1]); for (let ndx of [1, 2, 3, 4, 5, qobuzArtistLabels.length]) if (result[0].length <= 0 && result[ndx].length > 0) result[0] = result[ndx]; //result[0] = result[0].filter(artist => ![9].some(index => result[index].includes(artist))); featArtistParsers.slice(1, 6).forEach(function(rx, index) { let matches = rx.exec(title); if (matches == null) return; Array.prototype.pushUniqueCaseless.apply(result[6], splitAmpersands(matches[2])); if (index < 5) title = title.replace(rx, ''); }); if (defaultPerformer) for (let ndx of [0, 7]) if (result[ndx].length <= 0) result[ndx] = [defaultPerformer]; result[6] = result[6].filter(artist => ![0, 9].some(index => result[index].includes(artist))); // for (let ndx = 0; ndx < result.length; ++ndx) if (ndx != 8) result[ndx] = result[ndx] // .filter(trackArtist => ![0, 1, 4].some(ndx => pseudoArtistParsers[ndx].test(trackArtist))); for (let ndx = 0; ndx < result.length; ++ndx) if (ndx != 8) result[ndx] = result[ndx].filter(realArtistName); //console.debug('\tFiltered:', personnel[0], personnel[6]); return result; } return queryQobuzAPI('album/get', { album_id: QOBUZ_ID }).then(function(response) { if (response.tracks_count > response.tracks.limit) throw 'Tracklist length exceeding batch limit'; switch (response.release_type || response.product_type) { //case 'album': releaseType = 'Album'; break; case 'single': releaseType = 'Single'; break; case 'ep': case 'epmini': releaseType = 'EP'; break; } isVA = vaParser.test(response.artist.name); artist = [ ]; for (let ndx = 0; ndx <= qobuzArtistLabels.length; ++ndx) artist[ndx] = [ ]; if (response.artists) for (let _artist of response.artists) for (let ndx of _artist.roles.map(roleNormalizer).map(role => qobuzArtistLabels.findIndex(roles => roles.map(roleNormalizer).includes(role)))) artist[ndx >= 0 ? ndx : 13].pushUniqueCaseless(_artist.name); for (let ndx of [1, 2, 3, 4, 5, qobuzArtistLabels.length]) if (artist[0].length <= 0 && artist[ndx].length > 0) artist[0] = artist[ndx]; //artist[0] = artist[0].filter(_artist => ![9].some(index => artist[index].includes(_artist))); if (artist[0].length <= 0) artist[0] = response.artists.map(artist => artist.name); artist[6] = artist[6].filter(_artist => ![0, 9].some(index => artist[index].includes(_artist))); // for (let ndx = 0; ndx < artist.length; ++ndx) if (ndx != 8) artist[ndx] = artist[ndx] // .filter(albumArtist => ![0, 1, 4].some(ndx => pseudoArtistParsers[ndx].test(albumArtist))); artist = joinArtists(response.artists.map(artist => artist.name)); // !! album = response.title; if (response.version) { let version = ' (' + response.version + ')'; if (!album.toLowerCase().endsWith(version.toLowerCase())) album += version; } featArtistParsers.slice(1, 6).forEach(function(rx, index) { var matches = rx.exec(album); if (matches == null) return; Array.prototype.pushUniqueCaseless.apply(featArtists, splitAmpersands(matches[1])); if (index < 5) album = album.replace(rx, ''); }); if ((featArtists = featArtists.filter(realArtistName)).length > 0 && !featTest.test(artist)) artist += ' feat. ' + joinArtists(featArtists); if (response.description) description = domParser.parseFromString(response.description, 'text/html') .body.textContent.trim().flatten(); response.tracks.items.forEach(function(track, index) { title = track.title; if (track.version) title += ' (' + track.version + ')'; personnel = getTrackArtists(track.performers, track.performer && track.performer.name); trackArtist = joinArtists(personnel[0]); //if (trackArtist && personnel[9].length > 0) trackArtist += ' under ' + joinArtists(personnel[9]); if (trackArtist && personnel[6].length > 0) trackArtist += ' feat. ' + joinArtists(personnel[6]); tracks.push([ /* 00 */ isVA ? VA : artist, /* 01 */ album, /* 02 */ response.release_date_original, /* 03 */ response.genre.name, /* 04 */ response.label.name, /* 05 */ response.media_count > 1 ? track.media_number || 1 : undefined, /* 06 */ track.work || undefined, /* 07 */ response.media_count > 1 ? response.media_count : undefined, /* 08 */ track.track_number || index + 1, /* 09 */ response.tracks_count || response.tracks.total, /* 10 */ trackArtist, /* 11 */ title, /* 12 */ personnel[8].length > 0 ? personnel[8].join(', ') : track.composer.name, /* 13 */ [personnel[0], personnel[qobuzArtistLabels.length], personnel.slice(1, 8)] .flatten().distinctValues().join(', ') || trackArtist || !isVA && artist, /* 14 */ joinArtists(personnel[9]), // conductors //joinArtists(personnel[10]), //joinArtists(personnel[11]), /* 15 */ 'Digital Media', // WEB /* 16 */ response.url, /* 17 */ description, /* 18 */ releaseType || undefined, /* 19 */ response.upc || undefined, /* 20 */ track.isrc || undefined, /* 21 */ track.parental_warning ? 1 : undefined, ]); }); return finalizeTracks(); }).catch(function(reason) { console.info('Qobuz API method failed for the reason', reason); if ((ref = document.querySelector('section.album-item[data-gtm]')) != null) try { let gtm = JSON.parse(ref.dataset.gtm); //if (gtm.shop.category) genres.push(gtm.shop.category); if (gtm.shop.subCategory) var subCategory = gtm.shop.subCategory.replace(/-/g, ' '); //if (gtm.type) var releaseType = gtm.type; } catch(e) { console.warn(e) } if ((ref = document.querySelector('div.album-meta > h2.album-meta__artist')) != null) artist = ref.title || ref.textContent.trim(); isVA = vaParser.test(artist); album = (ref = document.querySelector('div.album-meta > h1.album-meta__title')) != null ? ref.title || ref.textContent.trim() : undefined; featArtistParsers.slice(1, 6).forEach(function(rx, index) { var matches = rx.exec(album); if (matches == null) return; Array.prototype.pushUniqueCaseless.apply(featArtists, splitAmpersands(matches[1])); if (index < 5) album = album.replace(rx, ''); }); if ((featArtists = featArtists.filter(realArtistName)).length > 0 && !featTest.test(artist)) artist += ' feat. ' + joinArtists(featArtists); releaseDate = (ref = document.querySelector('div.album-meta > ul > li:first-of-type')) != null ? normalizeDate(ref.textContent) : undefined; let mainArtist = (ref = document.querySelector('div.album-meta > ul > li:nth-of-type(2) > a')) != null ? ref.title || ref.textContent.trim() : undefined; if (mainArtist && featArtists.length > 0 && !featTest.test(mainArtist)) mainArtist += ' feat. ' + joinArtists(featArtists); //ref = document.querySelector('p.album-about__copyright'); //if (ref != null) albumYear = extractYear(ref.textContent); document.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.firstElementChild.textContent.replace(/\s+/g, ' ').trim(); else if (['Composer', 'Compositeur', 'Komponist', 'Compositore', 'Compositor'].some(matchLabel)) { composer = it.firstElementChild.textContent.trim(); //if (pseudoArtistParsers.some(rx => rx.test(composer))) composer = undefined; } else if (['Genre', 'Genere', 'Género'].some(g => it.textContent.startsWith(g)) && it.childElementCount > 0 && genres.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(); } }); description = Array.from(document.querySelectorAll('section#description > p')) .map(p => p.textContent.trim()).filter(Boolean).join('\n\n').flatten(); url = (ref = document.querySelector('meta[property="og:url"]')) != null ? ref.content : document.URL; addTracks(document); if (totalTracks <= 50) return Promise.resolve(finalizeTracks()); let params = new URLSearchParams({ albumId: QOBUZ_ID, offset: 50, limit: 999, store: /\/(\w{2}-\w{2})\/album\//i.test(document.location.pathname) ? RegExp.$1 : 'fr-fr', }); return localXHR('/v4/ajax/album/load-tracks?' + params).then(dom => { addTracks(dom) }, function(reason) { console.error('localXHR() failed:', reason); }).then(() => finalizeTracks()); function addTracks(dom) { Array.prototype.push.apply(tracks, Array.from(dom.querySelectorAll('div.player__item > div.player__tracks > div.track > div.track__items')).map(function(div, index) { let TRACK_ID = div.parentNode.dataset.track; title = (ref = [ 'div.track__item--name > span', 'div.track__item--name--track > span', 'span.track__item--name', ].reduce((acc, sel) => acc || div.querySelector(sel), null)) != null ? ref.textContent.trim().replace(/\s+/g, ' ') : undefined; ref = div.parentNode.querySelector('p.track__info:first-of-type'); personnel = div.querySelector('div.track__item--performer > span') || div.querySelector('div.track__item--name[itemprop="performer"] > span'); personnel = getTrackArtists(ref != null && ref.textContent.trim(), personnel != null && personnel.textContent.trim()); if (personnel[0].length > 0) trackArtist = joinArtists(personnel[0]); if (!trackArtist) if (!isVA) trackArtist = artist; else console.warn('Qobuz: track main artist missing for track', index + 1, div); //if (trackArtist && personnel[9].length > 0) trackArtist += ' under ' + joinArtists(personnel[9]); if (trackArtist && personnel[6].length > 0) trackArtist += ' feat. ' + joinArtists(personnel[6]); let trackGenres = [ ]; if (div.parentNode.dataset.gtm) try { let gtm = JSON.parse(div.parentNode.dataset.gtm); //if (gtm.product.id) QOBUZ_ID = gtm.product.id; if (gtm.product.subCategory) trackGenres.pushUniqueCaseless(gtm.product.subCategory.replace(/-/g, ' ')); if (gtm.product.type) releaseType = gtm.product.type; } catch(e) { console.warn(e) } trackGenres = trackGenres.map(function(genre) { genre = qbGenreToEnglish(genre.replace(/-/g, ' ')) return genre.split(/\s+/).map(word => word[0].toUpperCase() + word.slice(1).toLowerCase()).join(' '); }); if ((ref = div.parentNode.parentNode.parentNode.querySelector('p.player__work:first-child')) != null) { discSubtitle = ref.textContent.replace(/\s+/g, ' ').trim(); guessDiscNumber(); } return [ /* 00 */ isVA ? VA : artist, /* 01 */ album, /* 02 */ releaseDate, /* 03 */ genres.map(qbGenreToEnglish).join(', '), /* 04 */ label, /* 05 */ totalDiscs > 1 ? discNumber || 1 : undefined, /* 06 */ discSubtitle, /* 07 */ totalDiscs > 1 ? totalDiscs : undefined, /* 08 */ (ref = div.querySelector('div.track__item--number > span') || div.querySelector('span[itemprop="position"]')) != null ? parseInt(ref.textContent) : undefined, /* 09 */ totalTracks, /* 10 */ trackArtist, /* 11 */ title, /* 12 */ personnel[8].length > 0 ? personnel[8].join(', ') : composer, /* 13 */ [personnel[0], personnel[qobuzArtistLabels.length], personnel.slice(1, 8)] .flatten().distinctValues().join(', ') || trackArtist || !isVA && artist, /* 14 */ joinArtists(personnel[9]), // conductors //joinArtists(personnel[10]), //joinArtists(personnel[11]), /* 15 */ 'Digital Media', // WEB /* 16 */ url, /* 17 */ description, /* 18 */ releaseType && releaseType.toLowerCase() != 'album' ? releaseType : undefined, ]; })); } }); function finalizeTracks() { if (!isVA && tracks.every(track => track[10] && track[10] == tracks[0][10])) tracks.forEach(track => { track[0] = track[10] }); return tracks; } } let button = document.querySelector('button.player-share__button'); if (button != null) { button.onclick = copyTracks; button.classList.remove('pct-share'); button.style = 'font: 700 small "Segoe UI", Tahome, sans-serif; padding: 3px; background-color: lightgray; width: 12em;'; button.textContent = 'Copy album metadata'; } if (typeof GM_registerMenuCommand == 'function' && typeof GM_setClipboard == 'function') GM_registerMenuCommand('Store foobar2000\'s parsing string to clipboard', function() { GM_setClipboard([ /* 00 */ 'album artist', 'album', 'releasedate', 'genre', 'label', 'discnumber', 'discsubtitle', 'totaldiscs', /* 08 */ 'tracknumber', 'totaltracks', 'artist', 'title', 'composer', 'performer', 'conductor', 'media', 'url', /* 17 */ 'comment', 'releasetype', 'upc', 'isrc', 'explicit', ].map(tagName => '%' + tagName + '%').join('\x1E'), 'text'); });