您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Browse artist's releases in chronological order directly from release page
当前为
// ==UserScript== // @name [GMT] Artist Chronology // @version 1.01 // @description Browse artist's releases in chronological order directly from release page // @match https://*/torrents.php?id=* // @match https://*/torrents.php?page=*&id=* // @run-at document-end // @author Anakunda // @namespace https://greasyfork.org/users/321857 // @copyright 2024, Anakunda (https://greasyfork.org/users/321857) // @license GPL-3.0-or-later // @grant GM_getValue // @grant GM_setValue // @require https://openuserjs.org/src/libs/Anakunda/libLocks.min.js // @require https://openuserjs.org/src/libs/Anakunda/gazelleApiLib.min.js // ==/UserScript== 'use strict'; const anchor = document.body.querySelector('table#vote_matches') || document.body.querySelector('div.box.torrent_description'); if (anchor == null) return; const searchParams = new URLSearchParams(document.location.search); const groupId = parseInt(searchParams.get('id')); console.assert(groupId > 0); if (!(groupId > 0)) return; let chronologyArtistId = parseInt(searchParams.get('chronology_id')) || undefined; let releaseType = document.body.querySelector('div#content div.header > h2'); if (releaseType != null) releaseType = /\s+\[([^\[\]]+)\]$/.exec(releaseType.lastChild.textContent); if (releaseType != null) releaseType = { '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, }[releaseType[1]]; const getArtistIds = className => Array.from(document.body.querySelectorAll(`ul#artist_list > li.${className} > a`), a => parseInt((a = new URLSearchParams(a.search)).get('id'))).filter(artistId => artistId > 0); const artistIds = { DJ: getArtistIds('artists_dj'), main: getArtistIds('artist_main'), composer: getArtistIds('artists_composers'), remixer: getArtistIds('artists_remix'), producer: getArtistIds('artists_producer'), }; const minifyHTML = html => html.replace(/\s*(?:\r?\n)+\s*/g, ''); const forward = (scaleX = 1) => minifyHTML(` <svg height="20px" viewBox="0 0 82.66 82.66" transform="scale(${scaleX},1)" xmlns="http://www.w3.org/2000/svg"> <circle fill="#8888" cx="41" cy="41.33" r="41.04"/> <polygon fill="white" points="46.27,22.55 59.12,31.94 71.97,41.33 59.12,50.72 46.27,60.12 46.27,47.41 38.39,53.17 22.19,65.02 22.19,41.33 22.19,17.65 38.39,29.49 46.27,35.26 "/> </svg> `); const fastForward = (scaleX = 1) => minifyHTML(` <svg height="20px" viewBox="0 0 93.33 93.33" transform="scale(${scaleX},1)" xmlns="http://www.w3.org/2000/svg"> <circle fill="#8888" cx="46.67" cy="46.67" r="46.67"/> <path fill="white" d="M58.69 20.71l12.12 0 0 51.91 -12.12 0 0 -51.91zm-0.31 25.96l-17.93 12.97 -17.93 12.98 0 -25.95 0 -25.96 17.93 12.98 17.93 12.98z"/> </svg> `); const artistChronology = 'artistChronology' in sessionStorage ? JSON.parse(sessionStorage.getItem('artistChronology')) : { }; const box = Object.assign(document.createElement('div'), { innerHTML: minifyHTML(` <div class="box chronology"> <div class="head"> <a href="#">↑</a> <strong>Chronology</strong> <label style="float: right; margin-left: 2rem;">Lock to release type <input name="releasetype-lock" type="checkbox" style="margin-left: 0.5rem;"> </label> <a href="#" class="artist-lock" style="float: right; margin-left: 2rem;"></a> </div> <div class="body" style="padding: 0;"></div> </div> `) }); const [body, artistLock, lockState] = ['div.body', 'a.artist-lock','input[type="checkbox"][name="releasetype-lock"]'] .map(Element.prototype.querySelector.bind(box)); console.assert(body != null && artistLock != null && lockState != null); function loadChronology(releaseTypeLocked = true, lockedArtistId) { while (body.lastChild != null) body.removeChild(body.lastChild); Array.prototype.concat.apply([ ], Object.values(artistIds).map(artistIds => lockedArtistId > 0 ? artistIds.filter(artistId => artistId == lockedArtistId) : artistIds).filter(artistIds => artistIds.length <= GM_getValue('max_artists_load', 5))).filter((artistId, index, artistIds) => artistIds.indexOf(artistId) == index).forEach(function(artistId) { const artistRow = document.createElement('div'); artistRow.className = `chronology-artist-${artistId}`; (artistId in artistChronology ? Promise.resolve(artistChronology[artistId]) : queryAjaxAPI('artist', { id: artistId }).then(function(artist) { artistChronology[artistId] = { name: artist.name, torrentGroups: artist.torrentgroup.map(torrentGroup => ({ id: torrentGroup.groupId, name: torrentGroup.groupName, year: torrentGroup.groupYear, releaseType: torrentGroup.releaseType, })).sort((a, b) => (a.year || Infinity) - (b.year || Infinity)), timestamp: Date.now(), }; sessionStorage.setItem('artistChronology', JSON.stringify(artistChronology)); return artistChronology[artistId]; })).then(function(artistChronology) { function chronologyByRole(torrentGroups, role) { function addNavigation(className, index, icon) { if (torrentGroups[index]) roleNav.querySelectorAll('div.' + className).forEach(function(div) { if (div.classList.contains('link')) { const link = content => `<a href="/torrents.php?${new URLSearchParams({ id: torrentGroups[index].id, chronology_id: artistId, }).toString()}">${content}</a>`; if (!div.classList.contains('icon')) div.innerHTML = `${link(torrentGroups[index].name)}<br>(${torrentGroups[index].year})`; else if (icon) div.innerHTML = link(icon); } else { if (!div.classList.contains('icon')) div.innerHTML = `<span>${torrentGroups[index].name}</span><br>(${torrentGroups[index].year})`; else if (icon) div.innerHTML = icon; } }); } torrentGroups = torrentGroups.filter((tg1, index, torrentGroups) => torrentGroups.findIndex(tg2 => tg2.id == tg1.id) == index); const groupIndex = torrentGroups.findIndex(torrentGroup => torrentGroup.id == groupId); if (groupIndex < 0) return; const roleNav = Object.assign(document.createElement('div'), { className: `as-${role}`, innerHTML: minifyHTML(` <div style="text-align: center; padding: 5px 10px; background-color: #b0c4de66;"> <a href="/artist.php?id=${artistId}">${artistChronology.name}</a>'s chronology (as ${role}) [${groupIndex + 1}/${torrentGroups.length}] </div> <div style="display: flex; flex-flow: row nowrap; padding: 5px 10px; justify-content: space-between; column-gap: 1rem;"> <div class="first icon link"></div> <div class="previous link" style="text-align: center;"></div> <div class="previous icon link"></div> <div class="current" style="text-align: center;"></div> <div class="next icon link"></div> <div class="next link" style="text-align: center;"></div> <div class="last icon link"></div> </div> `), }); if (groupIndex > 0) { addNavigation('first', 0, fastForward(-1)); addNavigation('previous', groupIndex - 1, forward(-1)); } addNavigation('current', groupIndex); if (groupIndex < torrentGroups.length - 1) { addNavigation('next', groupIndex + 1, forward(1)); addNavigation('last', torrentGroups.length - 1, fastForward(1)); } artistRow.append(roleNav); } let torrentGroups = artistChronology.torrentGroups; if (releaseTypeLocked && releaseType > 0) torrentGroups = torrentGroups.filter(torrentGroup => torrentGroup.releaseType >= 1000 || torrentGroup.releaseType == releaseType); chronologyByRole(torrentGroups.filter(torrentGroup => torrentGroup.releaseType < 1000), 'main'); chronologyByRole(torrentGroups.filter(torrentGroup => torrentGroup.releaseType == 1021), 'producer'); chronologyByRole(torrentGroups.filter(torrentGroup => torrentGroup.releaseType == 1022), 'composer'); chronologyByRole(torrentGroups.filter(torrentGroup => torrentGroup.releaseType == 1023), 'remixer'); }, alert); body.append(artistRow); }); } if (chronologyArtistId > 0) Object.assign(artistLock, { innerHTML: '<span class="artist-lock-state">Release</span> artist Id', onclick: function(evt) { chronologyArtistId = chronologyArtistId > 0 ? undefined : parseInt(searchParams.get('chronology_id')); loadChronology(lockState.checked, chronologyArtistId); evt.currentTarget.querySelector('span.artist-lock-state').textContent = chronologyArtistId > 0 ? 'Release' : 'Lock to'; return false; }, }); else artistLock.remove(); Object.assign(lockState, { checked: 'chronologyLock' in sessionStorage ? Boolean(Number(sessionStorage.getItem('chronologyLock'))) : true, onchange: function(evt) { sessionStorage.setItem('chronologyLock', Number(evt.currentTarget.checked)); loadChronology(evt.currentTarget.checked, chronologyArtistId); }, }); loadChronology(lockState.checked, chronologyArtistId); anchor.before(box);