您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A discography player for bandcamp.com and manage your played albums
当前为
// ==UserScript== // @name Bandcamp script (Deluxe Edition) // @description A discography player for bandcamp.com and manage your played albums // @namespace https://openuserjs.org/users/cuzi // @copyright 2019, cuzi (https://openuserjs.org/users/cuzi) // @license MIT // @version 0.1 // @require https://unpkg.com/[email protected]/dist/index.min.js // @grant GM.xmlHttpRequest // @grant GM.setValue // @grant GM.getValue // @include https://bandcamp.com/* // @include https://*.bandcamp.com/* // ==/UserScript== // ==OpenUserJS== // @author cuzi // ==/OpenUserJS== /* globals JSON5, GM */ // TODO show volume bar if other script is not installed https://greasyfork.org/en/scripts/38012-bandcamp-volume-bar/code // TODO test preorder albums and albums that are not streamable // TODO do mp3 urls expire? // TODO export list of listened albums // TODO Mark as played automatically when played var player, audio, currentDuration, timeline, playhead var onPlayHead = false function humanDuration (duration) { let hours = parseInt(duration / 3600) if (!hours) { hours = '' } else { hours += ':' } duration %= 3600 let minutes = parseInt(duration / 60) minutes = (minutes < 10 ? '0' : '') + minutes duration %= 60 let seconds = parseInt(duration) if (duration - seconds >= 0.5) { seconds++ } seconds = (seconds < 10 ? '0' : '') + seconds return `${hours}${minutes}:${seconds}` } function timeSince (date) { // From https://stackoverflow.com/a/3177838/10367381 const seconds = Math.floor((new Date() - date) / 1000) let interval = Math.floor(seconds / 31536000) if (interval > 1) { return interval + ' years' } interval = Math.floor(seconds / 2592000) if (interval > 1) { return interval + ' months' } interval = Math.floor(seconds / 86400) if (interval > 1) { return interval + ' days' } interval = Math.floor(seconds / 3600) if (interval > 1) { return interval + ' hours' } interval = Math.floor(seconds / 60) if (interval > 1) { return interval + ' minutes' } return Math.floor(seconds) + ' seconds' } function removeViaQuerySelector (parent, selector) { if (typeof selector === 'undefined') { selector = parent parent = document } for (let el = parent.querySelector(selector); el; el = parent.querySelector(selector)) { el.remove() } } function firstChildWithText (parent) { for (let i = 0; i < parent.childNodes.length; i++) { const node = parent.childNodes[i] if (node.nodeType === window.Node.TEXT_NODE && node.nodeValue.trim()) { return node } else if (node.childNodes.length) { const r = firstChildWithText(node) if (r) { return r } } } return false } const _dateOptions = { year: 'numeric', month: 'short', day: 'numeric' } const _dateOptionsWithoutYear = { month: 'short', day: 'numeric' } function dateFormater (date) { if (date.getFullYear() === (new Date()).getFullYear()) { return date.toLocaleDateString(undefined, _dateOptionsWithoutYear) } else { return date.toLocaleDateString(undefined, _dateOptions) } } function dateFormaterRelease (date) { return date.toLocaleDateString(undefined, _dateOptionsWithoutYear) + ', ' + date.getFullYear() } function findUserProfileUrl () { if (document.querySelector('#collection-main a')) { return document.querySelector('#collection-main a').href } return 'https://bandcamp.com/login' } function findPreviousAlbumCover (currentUrl) { const currentKey = albumKey(currentUrl) const as = document.querySelectorAll('.music-grid .music-grid-item a[href^="/album/"],.music-grid .music-grid-item a[href^="/track/"]') let last = false let found = false for (let i = 0; i < as.length; i++) { if (last && albumKey(as[i].href) === currentKey) { found = last break } last = as[i] } if (found) { return playAlbumFromCover.apply(found, null) } return false } function findNextAlbumCover (currentUrl) { const currentKey = albumKey(currentUrl) const as = document.querySelectorAll('.music-grid .music-grid-item a[href^="/album/"],.music-grid .music-grid-item a[href^="/track/"]') let isNext = false for (let i = 0; i < as.length; i++) { if (isNext) { return playAlbumFromCover.apply(as[i], null) } if (albumKey(as[i].href) === currentKey) { isNext = true } } return false } function musicPlayerNextSong (next) { const current = player.querySelector('.playlist .playing') if (!next) { next = current.nextElementSibling while (next) { if ('file' in next.dataset) { break } next = next.nextElementSibling } } if (next) { current.className = current.className.replace('playing', '') next.className += ' playing' musicPlayerPlaySong(next) } else { // End of playlist reached if (findNextAlbumCover(current.dataset.albumUrl) === false) { window.alert('End of playlist reached') } } } var ivSlideInNextSong function musicPlayerPlaySong (next) { currentDuration = next.dataset.duration player.querySelector('.durationDisplay .current').innerHTML = '-' player.querySelector('.durationDisplay .total').innerHTML = humanDuration(currentDuration) audio.src = next.dataset.file const key = albumKey(next.dataset.albumUrl) // Meta const currentlyPlaying = document.querySelector('.currentlyPlaying') const nextInRow = player.querySelector('.nextInRow') nextInRow.querySelector('.cover').href = next.dataset.albumUrl nextInRow.querySelector('.cover img').src = next.dataset.albumCover nextInRow.querySelector('.info .link').href = next.dataset.albumUrl nextInRow.querySelector('.info .title').innerHTML = next.dataset.title nextInRow.querySelector('.info .artist').innerHTML = next.dataset.artist nextInRow.querySelector('.info .album').innerHTML = next.dataset.album // Favicon musicPlayerFavicon(next.dataset.albumCover.replace(/_\d.jpg$/, '_3.jpg')) // Wishlist const collectWishlist = player.querySelector('.collect-wishlist') collectWishlist.dataset.albumUrl = next.dataset.albumUrl player.querySelectorAll('.collect-wishlist>*').forEach(function (e) { e.style.display = 'none' }) if (next.dataset.isPurchased === 'true') { player.querySelector('.collect-wishlist .wishlist-own').style.display = 'inline-block' collectWishlist.dataset.wishlist = 'own' } else if (next.dataset.inWishlist === 'true') { player.querySelector('.collect-wishlist .wishlist-collected').style.display = 'inline-block' collectWishlist.dataset.wishlist = 'collected' } else { player.querySelector('.collect-wishlist .wishlist-add').style.display = 'inline-block' collectWishlist.dataset.wishlist = 'add' } // Played/Listened const collectListened = player.querySelector('.collect-listened') collectListened.dataset.albumUrl = next.dataset.albumUrl player.querySelectorAll('.collect-listened>*').forEach(function (e) { e.style.display = 'none' }) GM.getValue('myalbums', '{}').then(function (str) { const myalbums = JSON.parse(str) if (key in myalbums && 'listened' in myalbums[key] && myalbums[key].listened) { player.querySelector('.collect-listened .listened').style.display = 'inline-block' const date = new Date(myalbums[key].listened) const since = timeSince(date) player.querySelector('.collect-listened .listened').title = since + ' ago\nClick to mark as NOT played' collectListened.dataset.listened = myalbums[key].listened } else { player.querySelector('.collect-listened .mark-listened').style.display = 'inline-block' collectListened.dataset.listened = false } }) // Animate currentlyPlaying.style.marginLeft = -parseInt(currentlyPlaying.clientWidth + 1) + 'px' nextInRow.style.width = '99%' clearTimeout(ivSlideInNextSong) ivSlideInNextSong = window.setTimeout(function () { currentlyPlaying.remove() const clone = nextInRow.cloneNode(true) clone.style.width = '0%' clone.className = 'nextInRow' nextInRow.className = 'currentlyPlaying' nextInRow.parentNode.appendChild(clone) }, 7 * 1000) window.setTimeout(() => player.querySelector('.playlist .playing').scrollIntoView({ block: 'nearest' }), 200) } function musicPlayerPlay () { if (audio.paused) { audio.play() musicPlayerCookieChannelSendStop() } else { audio.pause() } } function musicPlayerStop () { if (!audio.paused) { audio.pause() } } function musicPlayerPrev () { musicPlayerShowBusy() const current = player.querySelector('.playlist .playing') let prev = current.previousElementSibling while (prev) { if ('file' in prev.dataset) { break } prev = prev.previousElementSibling } if (prev) { musicPlayerNextSong(prev) } } function musicPlayerNext () { musicPlayerShowBusy() musicPlayerNextSong() } function musicPlayerPrevAlbum () { audio.pause() window.setTimeout(function () { musicPlayerShowBusy() findPreviousAlbumCover(player.querySelector('.playlist .playing').dataset.albumUrl) }, 10) } function musicPlayerNextAlbum () { audio.pause() window.setTimeout(function () { musicPlayerShowBusy() findNextAlbumCover(player.querySelector('.playlist .playing').dataset.albumUrl) }, 10) } function musicPlayerOnTimelineClick (ev) { musicPlayerMovePlayHead(ev) const timelineWidth = timeline.offsetWidth - playhead.offsetWidth const clickPercent = (ev.clientX - timeline.getBoundingClientRect().left) / timelineWidth audio.currentTime = currentDuration * clickPercent } function musicPlayerOnTimeUpdate (ev) { const playpause = player.querySelector('.playpause') const timelineWidth = timeline.offsetWidth - playhead.offsetWidth const playPercent = timelineWidth * (audio.currentTime / currentDuration) playhead.style.marginLeft = playPercent + 'px' if (audio.currentTime === currentDuration) { playpause.querySelector('.play').style.display = 'none' playpause.querySelector('.busy').style.display = '' playpause.querySelector('.pause').style.display = 'none' } else if (audio.paused) { playpause.querySelector('.play').style.display = '' playpause.querySelector('.busy').style.display = 'none' playpause.querySelector('.pause').style.display = 'none' if (document.title.startsWith('\u25B6\uFE0E ')) { document.title = document.title.substring(3) } } else { playpause.querySelector('.play').style.display = 'none' playpause.querySelector('.busy').style.display = 'none' playpause.querySelector('.pause').style.display = '' if (!document.title.startsWith('\u25B6\uFE0E ')) { document.title = '\u25B6\uFE0E ' + document.title } } player.querySelector('.durationDisplay .current').innerHTML = humanDuration(audio.currentTime) } function musicPlayerShowBusy (ev) { const playpause = player.querySelector('.playpause') playpause.querySelector('.play').style.display = 'none' playpause.querySelector('.busy').style.display = '' playpause.querySelector('.pause').style.display = 'none' } function musicPlayerMovePlayHead (event) { const newMargLeft = event.clientX - timeline.getBoundingClientRect().left const timelineWidth = timeline.offsetWidth - playhead.offsetWidth if (newMargLeft >= 0 && newMargLeft <= timelineWidth) { playhead.style.marginLeft = newMargLeft + 'px' } if (newMargLeft < 0) { playhead.style.marginLeft = '0px' } if (newMargLeft > timelineWidth) { playhead.style.marginLeft = timelineWidth + 'px' } } function musicPlayerOnPlayheadMouseDown () { onPlayHead = true window.addEventListener('mousemove', musicPlayerMovePlayHead, true) audio.removeEventListener('timeupdate', musicPlayerOnTimeUpdate, false) } function musicPlayerOnPlayheadMouseUp (event) { if (onPlayHead) { musicPlayerMovePlayHead(event) window.removeEventListener('mousemove', musicPlayerMovePlayHead, true) // change current time const timelineWidth = timeline.offsetWidth - playhead.offsetWidth const clickPercent = (event.clientX - timeline.getBoundingClientRect().left) / timelineWidth audio.currentTime = currentDuration * clickPercent audio.addEventListener('timeupdate', musicPlayerOnTimeUpdate, false) } onPlayHead = false } function musicPlayerOnVolumeClick (ev) { const volSlider = player.querySelector('.vol-slider') const sliderWidth = volSlider.offsetWidth const percent = (ev.clientX - volSlider.getBoundingClientRect().left) / sliderWidth audio.volume = percent > 0.9 ? 1.0 : percent } function musicPlayerOnVolumeWheel (ev) { ev.preventDefault() const direction = Math.min(Math.max(-1.0, ev.deltaY), 1.0) audio.volume = Math.min(Math.max(0.0, audio.volume - 0.05 * direction), 1.0) } function musicPlayerOnMuteClick (ev) { if (audio.volume < 0.01) { if ('lastvolume' in audio.dataset && audio.dataset.lastvolume) { audio.volume = audio.dataset.lastvolume } else { audio.volume = 1.0 } } else { audio.dataset.lastvolume = audio.volume audio.volume = 0.0 } } function musicPlayerOnVolumeChanged (ev) { const icons = ['\uD83D\uDD07', '\uD83D\uDD08', '\uD83D\uDD09', '\uD83D\uDD0A'] const percent = audio.volume const volSlider = player.querySelector('.vol-slider') volSlider.querySelector('.vol-amt').style.width = parseInt(100 * percent) + '%' const volIconWrapper = player.querySelector('.vol-icon-wrapper') volIconWrapper.title = 'Mute (' + parseInt(percent * 100) + '%)' if (percent < 0.05) { volIconWrapper.innerHTML = icons[0] } else if (percent < 0.3) { volIconWrapper.innerHTML = icons[1] } else if (percent < 0.8) { volIconWrapper.innerHTML = icons[2] } else { volIconWrapper.innerHTML = icons[3] } } function musicPlayerOnEnded (ev) { musicPlayerNextSong() window.setTimeout(() => player.querySelector('.playlist .playing').scrollIntoView({ block: 'nearest' }), 200) } function musicPlayerOnPlaylistClick (ev) { musicPlayerNextSong(this) } function musicPlayerFavicon (url) { removeViaQuerySelector(document.head, 'link[rel*=icon]') const link = document.createElement('link') link.type = 'image/x-icon' link.rel = 'shortcut icon' link.href = url document.head.appendChild(link) } function musicPlayerCollectWishlistClick (ev) { ev.preventDefault() if (player.querySelector('.collect-wishlist').dataset === 'own') { return } const url = player.querySelector('.collect-wishlist').dataset.albumUrl player.querySelectorAll('.collect-wishlist>*').forEach(function (e) { e.style.display = 'none' }) window.open(url + '#collect-wishlist') } async function musicPlayerCollectListenedClick (ev) { ev.preventDefault() const collectListened = player.querySelector('.collect-listened') const listened = collectListened.dataset.listened !== 'false' const url = collectListened.dataset.albumUrl setTimeout(function () { player.querySelectorAll('.collect-listened>*').forEach(function (e) { e.style.display = 'none' }) player.querySelector('.collect-listened .listened-saving').style.display = 'inline-block' player.querySelector('.collect-listened').style.cursor = 'wait' }, 0) let albumData = await myAlbumsGetAlbum(url) if (!albumData) { albumData = await myAlbumsNewFromUrl(url, {}) } if (listened) { albumData.listened = false } else { albumData.listened = (new Date()).toJSON() } await myAlbumsUpdateAlbum(albumData) player.querySelectorAll('.collect-listened>*').forEach(function (e) { e.style.display = 'none' }) if (listened) { player.querySelector('.collect-listened .mark-listened').style.display = 'inline-block' } else { player.querySelector('.collect-listened .listened').style.display = 'inline-block' } player.querySelector('.collect-listened').style.cursor = '' makeAlbumLinksGreat() } function musicPlayerCookieChannel (onStopEventCb) { window.addEventListener('message', function onMessage (event) { // Receive messages from the cookie channel event handler if (event.origin === document.location.protocol + '//' + document.location.hostname && event.data && typeof (event.data) === 'object' && 'discographyplayerCookiechannelPlaylist' in event.data && event.data.discographyplayerCookiechannelPlaylist.length >= 2 && event.data.discographyplayerCookiechannelPlaylist[1] === 'stop') { onStopEventCb(event.data.discographyplayerCookiechannelPlaylist) } }) var script = document.createElement('script') script.innerHTML = ` var channel = new Cookie.CommChannel('playlist') channel.send('stop') channel.subscribe(function(a,b) { window.postMessage({'discographyplayerCookiechannelPlaylist': b}, document.location.href) }) channel.startListening() window.addEventListener('message', function onMessage (event) { // Receive messages from the user script if (event.origin === document.location.protocol + '//' + document.location.hostname && event.data && typeof(event.data) === 'object' && 'discographyplayerCookiechannelPlaylist' in event.data && event.data.discographyplayerCookiechannelPlaylist === 'sendstop') { channel.send('stop') } }) ` document.head.appendChild(script) } function musicPlayerCookieChannelSendStop (onStopEventCb) { window.postMessage({ discographyplayerCookiechannelPlaylist: 'sendstop' }, document.location.href) } function musicPlayerCreate () { if (player) { return } musicPlayerCookieChannel(musicPlayerStop) const img1px = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOsmLZvJgAFwQJn5VVZ5QAAAABJRU5ErkJggg==' const listenedListUrl = findUserProfileUrl() + '#listened-tab' player = document.createElement('div') document.body.appendChild(player) player.id = 'discographyplayer' player.innerHTML = ` <div class="col col25 nowPlaying"> <div class="currentlyPlaying"> <a class="cover" target="_blank" href="#"> <img src="${img1px}"> </a> <div class="info"> <a class="link" target="_blank" href="#"> <div class="title">◧◩◨▧■□▩</div> <div class="artist">by <span>◩▧◧□ ◩◨▧ ■◩▩</span></div> <div>from <span class="album">◨■■▩ ▧◨□</span></div> </a> </div> </div> <div class="nextInRow"> <a class="cover" target="_blank" href="#"> <img src="${img1px}"> </a> <div class="info"> <a class="link" target="_blank" href="#"> <div class="title">◧◩◨▧■□▩</div> <div>by <span class="artist">◩▧◧□ ◩◨▧ ■◩▩</span></div> <div>from <span class="album">◨■■▩ ▧◨□</span></div> </a> </div> </div> </div> <div class="col col25"> <audio autoplay="autoplay" preload="auto"></audio> <div class="audioplayer"> <div id="timeline"> <div id="playhead"></div> </div> <div class="controls"> <div class="prevalbum" title="Previous album"> <div class="arrowbutton prevalbum-icon"></div> </div> <div class="prev" title="Previous song"> <div class="arrowbutton prev-icon"></div> </div> <div class="playpause" title="Play/Pause"> <div class="play" style="display: none;"></div> <div class="busy" style="display: none;"></div> <div class="pause" style=""></div> </div> <div class="next" title="Next song"> <div class="arrowbutton next-icon"></div> </div> <div class="nextalbum" title="Next album"> <div class="arrowbutton nextalbum-icon"></div> </div> </div> <div class="durationDisplay"><span class="current">-</span>/<span class="total">-</span></div> <br class="cll"> </div> </div> <div class="col col35"> <ol class="playlist"></ol> </div> <div class="col col15"> <div class="vol"> <div class="vol-icon-wrapper" title="Mute"> 🔊 </div> <div class="vol-slider"> <div class="vol-amt" style="width: 100%;"></div> <div class="vol-bg"></div> </div> </div> <div class="collect"> <div class="collect-wishlist"> <a class="wishlist-default" href="https://bandcamp.com/wishlist">Wishlist</a> <span class="wishlist-add" title="Add this album to your wishlist"> <span class="bc-ui2 icon add-item-icon"></span> <span class="add-item-label">Add to wishlist</span> </span> <span class="wishlist-collected" title="Remove this album from your wishlist"> <span class="bc-ui2 icon collected-item-icon"></span> <span>In Wishlist</span> </span> <span class="wishlist-own" title="You own this album"> <span class="bc-ui2 icon own-item-icon"></span> <span>You own this</span> </span> <span class="wishlist-saving"> Saving.... </span> </div> <div class="collect-listened"> <a class="listened-default" href="${listenedListUrl}"> Played albums </a> <span class="listened" title="Mark album as NOT played"> <span class="listened-symbol">✔</span> <span class="listened-label">Played</span> </span> <span class="mark-listened" title="Mark album as played"> <span class="mark-listened-symbol">✔</span> <span class="mark-listened-label">Mark as played</span> </span> <span class="listened-saving"> Saving... </span> </div> </div> <br class="cll"> </div>` document.head.appendChild(document.createElement('style')).innerHTML = ` .cll{ clear:left; } #discographyplayer{ z-index:1010; position:fixed; bottom:0px; height:83px; width:100%; padding-top:3px; background:white; color:#505958; border-top: 1px solid rgba(0,0,0,0.15); font-family:"Helvetica Neue", Helvetica, Arial, sans-serif } #discographyplayer .nowPlaying .info,#discographyplayer .nowPlaying .cover { display: inline-block; vertical-align: top; } #discographyplayer .nowPlaying img { width: 60px; height: 60px; margin-top: 4px; margin-left: 4px; margin-bottom: 4px; } #discographyplayer .nowPlaying .info { line-height: 18px; margin-left: 8px; margin-top: 8px; max-width: calc(100% - 76px); } #discographyplayer .currentlyPlaying{ display:inline-block; vertical-align: top; overflow: hidden; transition: margin-left 3s ease-in-out; width:99%; } #discographyplayer .nextInRow { display:inline-block; vertical-align: top; width:0%; overflow: hidden; transition: width 6s ease-in-out; } #discographyplayer .durationDisplay{ margin-top:24px } #discographyplayer .controls{ margin-top: 10px; width: auto; float:left; } #discographyplayer .controls > *{ display:inline-block; cursor: pointer; border: 1px solid #d9d9d9; padding: 11px; margin-right: 14px; height: 18px; width: 17px; } #discographyplayer .playpause .play { width: 0; height: 0; border-top: 9px inset transparent; border-bottom: 9px inset transparent; border-left: 15px solid rgb(34, 34, 34); cursor: pointer; margin-left: 2px; } #discographyplayer .playpause .pause { border: 0; border-left: 5px solid #2d2d2d; border-right: 5px solid #2d2d2d; height: 18px; width: 4px; margin-right: 2px; margin-left: 1px; } #discographyplayer .playpause .busy { background-image: url(/img/playerbusy-noborder.gif); background-position: 50% 50%; background-repeat: no-repeat; border: none; height: 30px; margin: 0px 0px 0px -3px; width: 25px; overflow: hidden; background-size: contain; } #discographyplayer .arrowbutton { border: 0; height: 13px; width: 20px; margin-top: 4px; background: url(/img/nextprev.png) 0px 0px / 40px 12px no-repeat transparent; background-position-x: 0px; cursor: pointer; } #discographyplayer .arrowbutton.next-icon { background-position: 100% 0px; } #discographyplayer .arrowbutton.prev-icon { } #discographyplayer .arrowbutton.prevalbum-icon { border-right: 3px solid #2d2d2d; } #discographyplayer .arrowbutton.nextalbum-icon { background-position: 100% 0px; border-left: 3px solid #2d2d2d; } #timeline{ width: 100%; background: rgba(50,50,50,0.4); margin-top:5px; border-left:1px solid black; border-right:1px solid black; } #playhead{ width:10px; height:10px; border-radius: 50%; background:rgba(50,50,50,1.0); cursor:pointer; } #discographyplayer .playlist{ width:100%; display:inline-block; max-height:80px; overflow:auto; list-style:none; padding: 0px 5px 0px 5px; scrollbar-color: rgba(50,50,50,0.4) white; } #discographyplayer .playlist .playlistentry { cursor:pointer; margin:1px 0px } #discographyplayer .playlist .playlistentry .duration { float:right } #discographyplayer .playlist .playing{ background:#619aa950 } #discographyplayer .playlist .playlistheading{ background:rgba(50,50,50,0.4); margin:3px 0px } #discographyplayer .playlist .playlistheading a:link,#discographyplayer .playlist .playlistheading a:hover,#discographyplayer .playlist .playlistheading a:visited{ color:#EEE } #discographyplayer .vol{ float:left; position: relative; width: 100px; margin-left: 1em; margin-top: 1em; } #discographyplayer .vol-icon-wrapper{ font-size: 20px; cursor: pointer; width:27px; } #discographyplayer .vol-slider { width: 60px; height: 10px; position: relative; cursor: pointer; } #discographyplayer .vol > * { display: inline-block; vertical-align: middle; } #discographyplayer .vol-bg { background: rgba(50, 50, 50, 0.4); width: 100%; margin-top: 4px; height: 3px; position: absolute; } #discographyplayer .vol-amt { margin-top: 4px; height: 3px; position: absolute; background: rgba(50, 50, 50, 1); } #discographyplayer .vol-control-outer { height: 100%; position: relative; margin-left: -3px; margin-right: 5px; } #discographyplayer .collect{ float:left; margin-left: 1em; } #discographyplayer .collect-wishlist { cursor:default; margin-top:0.5em; } #discographyplayer .collect-wishlist .wishlist-add { cursor:pointer; } #discographyplayer .collect-listened { cursor:pointer; margin-top:0.5em; margin-left: 2px; } #discographyplayer .collect .icon{ height: 13px; width: 14px; display: inline-block; position: relative; top: 2px; } #discographyplayer .collect .add-item-icon{ background-position: 0px -73px; } #discographyplayer .collect .collected-item-icon{ background-position: -28px -73px; } #discographyplayer .collect .own-item-icon{ background-position: -42px -73px; } #discographyplayer .collect .wishlist-add,#discographyplayer .collect .wishlist-collected,#discographyplayer .collect .wishlist-own,#discographyplayer .collect .wishlist-saving{ display:none; } #discographyplayer .collect .wishlist-add:hover .add-item-icon{ background-position: -56px -73px; } #discographyplayer .collect .wishlist-add:hover .add-item-label{ text-decoration:underline; } #discographyplayer .collect .listened,#discographyplayer .collect .mark-listened, #discographyplayer .collect .listened-saving{ display:none; } #discographyplayer .collect .listened .listened-symbol{ color:rgb(0,220,50); text-shadow:1px 0px #DDD,-1px 0px #DDD,0px -1px #DDD,0px 1px #DDD } #discographyplayer .collect .mark-listened .mark-listened-symbol{ color:#FFF; text-shadow:1px 0px #959595,-1px 0px #959595,0px -1px #959595,0px 1px #959595 } #discographyplayer .collect .mark-listened:hover .mark-listened-symbol{ text-shadow:1px 0px #0AF,-1px 0px #0AF,0px -1px #0AF,0px 1px #0AF } #discographyplayer .collect .mark-listened:hover .mark-listened-label { text-decoration:underline; } #discographyplayer .col { float: left; min-height: 1px; position: relative; } #discographyplayer .col25 { width: 25%; } #discographyplayer .col35 { width: 35%; } #discographyplayer .col30 { width: 30%; } #discographyplayer .col15 { width: 14%; } #discographyplayer .col20 { width: 20%; } ` audio = player.querySelector('audio') playhead = player.querySelector('#playhead') timeline = player.querySelector('#timeline') audio.addEventListener('ended', musicPlayerOnEnded) audio.addEventListener('timeupdate', musicPlayerOnTimeUpdate) audio.addEventListener('volumechange', musicPlayerOnVolumeChanged) audio.addEventListener('canplaythrough', function () { currentDuration = audio.duration player.querySelector('.durationDisplay .total').innerHTML = humanDuration(currentDuration) }) timeline.addEventListener('click', musicPlayerOnTimelineClick, false) playhead.addEventListener('mousedown', musicPlayerOnPlayheadMouseDown, false) window.addEventListener('mouseup', musicPlayerOnPlayheadMouseUp, false) player.querySelector('.prevalbum').addEventListener('click', musicPlayerPrevAlbum) player.querySelector('.prev').addEventListener('click', musicPlayerPrev) player.querySelector('.playpause').addEventListener('click', musicPlayerPlay) player.querySelector('.next').addEventListener('click', musicPlayerNext) player.querySelector('.nextalbum').addEventListener('click', musicPlayerNextAlbum) player.querySelector('.vol-slider').addEventListener('click', musicPlayerOnVolumeClick) player.querySelector('.vol').addEventListener('wheel', musicPlayerOnVolumeWheel, false) player.querySelector('.vol-icon-wrapper').addEventListener('click', musicPlayerOnMuteClick) player.querySelector('.collect-wishlist').addEventListener('click', musicPlayerCollectWishlistClick) player.querySelector('.collect-listened').addEventListener('click', musicPlayerCollectListenedClick) } function addHeadingToPlaylist (title, url) { musicPlayerCreate() let content = document.createTextNode('💽 ' + title) if (url) { const a = document.createElement('a') a.href = url a.target = '_blank' a.appendChild(content) content = a } const li = document.createElement('li') li.appendChild(content) li.className = 'playlistheading' player.querySelector('.playlist').appendChild(li) } function addToPlaylist (startPlayback, data) { musicPlayerCreate() const li = document.createElement('li') li.appendChild(document.createTextNode((data.trackNumber > 9 ? '' : '0') + data.trackNumber + '. ' + data.artist + ' - ' + data.title)) const span = document.createElement('span') span.className = 'duration' span.appendChild(document.createTextNode(humanDuration(data.duration))) li.appendChild(span) li.value = data.trackNumber li.dataset.file = data.file li.dataset.title = data.title li.dataset.trackNumber = data.trackNumber li.dataset.duration = data.duration li.dataset.artist = data.artist li.dataset.album = data.album li.dataset.albumUrl = data.albumUrl li.dataset.albumCover = data.albumCover li.dataset.inWishlist = data.inWishlist li.dataset.isPurchased = data.isPurchased li.addEventListener('click', musicPlayerOnPlaylistClick) li.className = 'playlistentry' player.querySelector('.playlist').appendChild(li) if (startPlayback) { player.querySelectorAll('.playlist .playing').forEach(function (el) { el.className = el.className.replace('playing', '') }) li.className += ' playing' musicPlayerPlaySong(li) window.setTimeout(() => player.querySelector('.playlist .playing').scrollIntoView({ block: 'nearest' }), 200) } } function addAlbumToPlaylist (TralbumData, startPlaybackIndex) { let i = 0 const artist = TralbumData.artist const album = TralbumData.current.title const albumUrl = document.location.protocol + '//' + albumKey(TralbumData.url) const albumCover = `https://f4.bcbits.com/img/a${TralbumData.art_id}_2.jpg` addHeadingToPlaylist(album, 'url' in TralbumData ? TralbumData.url : false) let streamable = 0 for (var key in TralbumData.trackinfo) { const track = TralbumData.trackinfo[key] if (!track.file) { continue } const trackNumber = track.track_num const file = track.file[Object.keys(track.file)[0]] const title = track.title const duration = track.duration const inWishlist = 'tralbum_collect_info' in TralbumData && 'is_collected' in TralbumData.tralbum_collect_info && TralbumData.tralbum_collect_info.is_collected const isPurchased = 'tralbum_collect_info' in TralbumData && 'is_purchased' in TralbumData.tralbum_collect_info && TralbumData.tralbum_collect_info.is_purchased addToPlaylist(startPlaybackIndex === i++, { file: file, title: title, trackNumber: trackNumber, duration: duration, artist: artist, album: album, albumUrl: albumUrl, albumCover: albumCover, inWishlist: inWishlist, isPurchased: isPurchased }) streamable++ } if (streamable === 0) { const li = document.createElement('li') li.appendChild(document.createTextNode('\uD83D\uDE22 Album is not streamable')) player.querySelector('.playlist').appendChild(li) } } function getTralbumData (url, cb) { return new Promise(function (resolve, reject) { GM.xmlHttpRequest({ method: 'GET', url: url, onload: function (response) { const TralbumData = JSON5.parse(response.responseText.split('var TralbumData =')[1].split('\n};\n')[0].replace(/"\s+\+\s+"/, '') + '\n}') correctTralbumData(TralbumData) resolve(TralbumData) }, onerror: function (response) { console.log('getTralbumData(' + url + ') Error: ' + response.status + '\nResponse:\n' + response.responseText) reject(response) } }) }) } function correctTralbumData (TralbumData) { // Corrections for single tracks if (TralbumData.current.type === 'track' && TralbumData.current.title.toLowerCase().indexOf('single') === -1) { TralbumData.current.title += ' - Single' } for (let i = 0; i < TralbumData.trackinfo.length; i++) { if (TralbumData.trackinfo[i].track_num === null) { TralbumData.trackinfo[i].track_num = i + 1 } } return TralbumData } function albumKey (url) { if (url.startsWith('/')) { url = document.location.hostname + url } if (url.indexOf('://') !== -1) { url = url.split('://')[1] } if (url.indexOf('#') !== -1) { url = url.split('#')[0] } if (url.indexOf('?') !== -1) { url = url.split('?')[0] } return url } async function storeTralbumData (TralbumData) { const cache = JSON.parse(await GM.getValue('tralbumdata', '{}')) for (const prop in cache) { // Delete cached values, that are older than 2 hours if ((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > 2 * 60 * 60 * 1000) { delete cache[prop] } } TralbumData.time = (new Date()).toJSON() cache[albumKey(TralbumData.url)] = TralbumData await GM.setValue('hovercache', JSON.stringify(cache)) } async function cachedTralbumData (url) { const key = albumKey(url) const cache = JSON.parse(await GM.getValue('tralbumdata', '{}')) for (const prop in cache) { // Delete cached values, that are older than 2 hours if ((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > 2 * 60 * 60 * 1000) { delete cache[prop] continue } if (prop === key) { return cache[prop] } } return false } function playAlbumFromCover (ev) { let parent = this for (let j = 0; parent.tagName !== 'A' && j < 20; j++) { parent = parent.parentNode } const url = parent.href parent.className += ' discographyplayer_currentalbum' // Check if already in playlist if (player) { const lis = player.querySelectorAll('.playlist .playlistentry') for (let i = 0; i < lis.length; i++) { if (lis[i].dataset.albumUrl === url) { lis[i].click() return } } } // Load data cachedTralbumData(url).then(function (TralbumData) { if (TralbumData) { addAlbumToPlaylist(TralbumData, 0) } else { playAlbumFromUrl(url) } }) } function playAlbumFromUrl (url) { getTralbumData(url).then(function (TralbumData) { storeTralbumData(TralbumData) addAlbumToPlaylist(TralbumData, 0) }).catch(function (e) { window.alert('Could not load album data from url:\n' + url + '\n' + e) }) } async function myAlbumsGetAlbum (url) { const key = albumKey(url) const data = JSON.parse(await GM.getValue('myalbums', '{}')) if (key in data) { return data[key] } else { return false } } async function myAlbumsUpdateAlbum (albumData) { const key = albumKey(albumData.url) const data = JSON.parse(await GM.getValue('myalbums', '{}')) if (key in data) { data[key] = Object.assign(data[key], albumData) } else { data[key] = albumData } await GM.setValue('myalbums', JSON.stringify(data)) } async function myAlbumsNewFromUrl (url, fallback) { // Get data from cache or load from url url = albumKey(url) const albumData = fallback || {} let TralbumData = await cachedTralbumData(url) if (!TralbumData) { try { TralbumData = await getTralbumData(document.location.protocol + '//' + url) } catch (e) { console.log('myAlbumsNewFromUrl() Could not load album data from url:\n' + url) } if (TralbumData) { storeTralbumData(TralbumData) } } if (TralbumData) { albumData.artist = TralbumData.artist albumData.title = TralbumData.current.title albumData.albumCover = `https://f4.bcbits.com/img/a${TralbumData.art_id}_2.jpg` albumData.releaseDate = TralbumData.current.release_date } albumData.url = url albumData.listened = false return albumData } function makeAlbumCoversGreat () { document.head.appendChild(document.createElement('style')).innerHTML = ` .music-grid-item .art-play { position: absolute; width: 74px; height: 54px; left: 50%; top: 50%; margin-left: -36px; margin-top: -27px; opacity: 0; transition: opacity 0.2s; } .music-grid-item .art-play-bg { position: absolute; width: 100%; height: 100%; left: 0; top: 0; background: #000; border-radius: 4px; } .music-grid-item .art-play-icon { position: absolute; width: 0; height: 0; left: 28px; top: 17px; border-width: 10px 0 10px 17px; border-color: transparent transparent transparent #fff; border-style: dashed dashed dashed solid; } .music-grid-item:hover .art-play { opacity: 0.6; } ` const onclick = function onclick (ev) { ev.preventDefault() playAlbumFromCover.apply(this, ev) } const artPlay = document.createElement('div') artPlay.className = 'art-play' artPlay.innerHTML = '<div class="art-play-bg"></div><div class="art-play-icon"></div>' // Albums and single tracks const imgs = document.querySelectorAll('.music-grid .music-grid-item a[href^="/album/"] img,.music-grid .music-grid-item a[href^="/track/"] img') for (let i = 0; i < imgs.length; i++) { imgs[i].addEventListener('click', onclick) // Add play overlay const clone = artPlay.cloneNode(true) clone.addEventListener('click', onclick) imgs[i].parentNode.appendChild(clone) } } async function makeAlbumLinksGreat () { const myalbums = JSON.parse(await GM.getValue('myalbums', '{}')) if (!(makeAlbumLinksGreat in document.head.dataset)) { document.head.dataset.makeAlbumLinksGreat = true document.head.appendChild(document.createElement('style')).innerHTML = ` .bdp_check_onlinkhover_container { z-index:1002; position:absolute; display:none } .bdp_check_onlinkhover_container_shown { display:block; background-color:rgba(255,255,255,0.9); padding:0px 2px 0px 0px; border-radius:5px } .bdp_check_onlinkhover_container:hover { position:absolute; transition: all 300ms linear; background-color:rgba(255,255,255,0.9); padding:0px 10px 0px 7px; border-radius:5px } .bdp_check_onchecked_container { z-index:-1; position:absolute; opacity:0.0; margin-top:-2px} a:hover .bdp_check_onchecked_container { z-index:1002; position:absolute; transition: opacity 300ms linear; opacity:1.0} .bdp_check_onlinkhover_symbol {color:rgba(0,0,50,0.7)} .bdp_check_onlinkhover_text {color:rgba(0,0,50,0.7)} .bdp_check_onlinkhover_container:hover .bdp_check_onlinkhover_symbol { color:rgba(0,0,100,1.0) } .bdp_check_onlinkhover_container:hover .bdp_check_onlinkhover_text { color:rgba(0,100,0,1.0)} .bdp_check_onchecked_symbol { color:rgba(0,100,0,0.8) } .bdp_check_onchecked_text { color:rgba(150,200,150,0.8) } a:hover .bdp_check_onchecked_symbol { text-shadow: 1px 1px #fff; color:rgba(0,50,0,1.0); transition: all 300ms linear } a:hover .bdp_check_onchecked_text { text-shadow: 1px 1px #000; color:rgba(200,255,200,0.8); transition: all 300ms linear } ` } /* <div class="bdp_check_container bdp_check_onlinkhover_container"><span class="bdp_check_onlinkhover_symbol">\u2610</span> <span class="bdp_check_onlinkhover_text">Check</span></div> <div class="bdp_check_container bdp_check_onlinkhover_container"><span class="bdp_check_onlinkhover_symbol">\u1f5f9</span> <span class="bdp_check_onlinkhover_text">Check</span></div> <span class="bdp_check_onchecked_symbol">\u2611</span> TITLE <div class="bdp_check_container bdp_check_onchecked_container"><span class="bdp_check_onchecked_text">Played</span></div> */ const onClickSetListened = async function (ev) { ev.preventDefault() let parent = this for (let j = 0; parent.tagName !== 'A' && j < 20; j++) { parent = parent.parentNode } setTimeout(function () { parent.style.cursor = 'wait'; parent.querySelector('.bdp_check_container').innerHTML = 'Saving...' }, 0) const url = parent.href let albumData = await myAlbumsGetAlbum(url) if (!albumData) { albumData = await myAlbumsNewFromUrl(url, { title: this.dataset.textContent }) } albumData.listened = (new Date()).toJSON() await myAlbumsUpdateAlbum(albumData) makeAlbumLinksGreat() parent.style.cursor = '' } const onClickRemoveListened = async function (ev) { ev.preventDefault() let parent = this for (let j = 0; parent.tagName !== 'A' && j < 20; j++) { parent = parent.parentNode } setTimeout(function () { parent.style.cursor = 'wait'; parent.querySelector('.bdp_check_container').innerHTML = 'Saving...' }, 0) const url = parent.href const albumData = await myAlbumsGetAlbum(url) if (albumData) { albumData.listened = false await myAlbumsUpdateAlbum(albumData) } makeAlbumLinksGreat() parent.style.cursor = '' } const mouseOverLink = function (ev) { if (this.querySelector('.bdp_check_onlinkhover_container')) { this.querySelector('.bdp_check_onlinkhover_container').className += ' bdp_check_onlinkhover_container_shown' } } const mouseOutLink = function (ev) { const a = this setTimeout(function () { const div = a.querySelector('.bdp_check_onlinkhover_container') if (div) { div.className = div.className.replace(' bdp_check_onlinkhover_container_shown', '') } }, 1000) } const mouseOverDivCheck = function (ev) { this.querySelector('.bdp_check_onlinkhover_symbol').innerText = '\uD83D\uDDF9' } const mouseOutDivCheck = function (ev) { this.querySelector('.bdp_check_onlinkhover_symbol').innerText = '\u2610' } const divCheck = document.createElement('div') divCheck.setAttribute('class', 'bdp_check_container bdp_check_onlinkhover_container') divCheck.setAttribute('title', 'Mark as played') divCheck.innerHTML = '<span class="bdp_check_onlinkhover_symbol">\u2610</span> <span class="bdp_check_onlinkhover_text">Check</span>' const divChecked = document.createElement('div') divChecked.setAttribute('class', 'bdp_check_container bdp_check_onchecked_container') divChecked.innerHTML = '<span class="bdp_check_onchecked_text">Played</span>' const spanChecked = document.createElement('span') spanChecked.appendChild(document.createTextNode('\u2611 ')) spanChecked.setAttribute('class', 'bdp_check_onchecked_symbol') const a = document.querySelectorAll('a[href*="/album/"],.music-grid .music-grid-item a[href^="/track/"]') let lastKey = '' for (let i = 0; i < a.length; i++) { const key = albumKey(a[i].href) if (key === lastKey) { // Skip multiple consequent links to same album continue } const textContent = a[i].textContent.trim() if (!textContent) { // Skip album covers only continue } let div if (a[i].dataset.textContent) { removeViaQuerySelector(a[i], '.bdp_check_onlinkhover_container') removeViaQuerySelector(a[i], '.bdp_check_onchecked_container') removeViaQuerySelector(a[i], '.bdp_check_onchecked_symbol') } else { a[i].dataset.textContent = textContent a[i].addEventListener('mouseover', mouseOverLink) a[i].addEventListener('mouseout', mouseOutLink) } if (key in myalbums && 'listened' in myalbums[key] && myalbums[key].listened) { div = divChecked.cloneNode(true) div.addEventListener('click', onClickRemoveListened) const date = new Date(myalbums[key].listened) const since = timeSince(date) const dateStr = dateFormater(date) div.title = since + ' ago\nClick to mark as NOT played' div.querySelector('.bdp_check_onchecked_text').appendChild(document.createTextNode(' ' + dateStr)) const span = spanChecked.cloneNode(true) span.title = since + ' ago\nClick to mark as NOT played' span.addEventListener('click', onClickRemoveListened) const firstText = firstChildWithText(a[i]) || a[i].firstChild firstText.parentNode.insertBefore(span, firstText) } else { div = divCheck.cloneNode(true) div.addEventListener('mouseover', mouseOverDivCheck) div.addEventListener('mouseout', mouseOutDivCheck) div.addEventListener('click', onClickSetListened) } a[i].appendChild(div) lastKey = key } } function removeTheTimeHasComeToOpenThyHeartWallet () { if ('theTimeHasComeToOpenThyHeartWallet' in document.head.dataset) { return } document.head.dataset.theTimeHasComeToOpenThyHeartWallet = true document.head.appendChild(document.createElement('script')).innerHTML = ` TralbumData.play_cap_data.streaming_limit = 100 TralbumData.play_cap_data.streaming_limits_enabled = false for(let i = 0; i < TralbumData.trackinfo.length; i++) { TralbumData.trackinfo[i].is_capped = false TralbumData.trackinfo[i].play_count = 1 } /* // Alternative would be create new player TralbumLimits.onPlayerInit = () => true TralbumLimits.updatePlayCounts = () => true Player.init(TralbumData, AlbumPage.onPlayerInit); */ Player.update(TralbumData) // Hide popup (not really needed, but won't hurt) window.setInterval(function() { if(document.getElementById('play-limits-dialog-cancel-btn')) { document.getElementById('play-limits-dialog-cancel-btn').click() window.setTimeout(function() { removeViaQuerySelector(document, '.ui-dialog.ui-widget') removeViaQuerySelector(document, '.ui-widget-overlay') }, 100) } }, 3000) ` } async function addListenedButtonToCollectControls () { const lastLi = document.querySelector('.share-panel-wrapper-desktop ul li') if (!lastLi) { window.setTimeout(addListenedButtonToCollectControls, 300) return } const myalbums = JSON.parse(await GM.getValue('myalbums', '{}')) const key = albumKey(document.location.href) const listened = key in myalbums && 'listened' in myalbums[key] && myalbums[key].listened const onClickSetListened = async function (ev) { ev.preventDefault() let parent = this for (let j = 0; parent.tagName !== 'LI' && j < 20; j++) { parent = parent.parentNode } setTimeout(function () { parent.style.cursor = 'wait'; parent.innerHTML = 'Saving...' }, 0) const url = document.location.href let albumData = await myAlbumsGetAlbum(url) if (!albumData) { albumData = await myAlbumsNewFromUrl(url, { title: this.dataset.textContent }) } albumData.listened = (new Date()).toJSON() await myAlbumsUpdateAlbum(albumData) addListenedButtonToCollectControls() } const onClickRemoveListened = async function (ev) { ev.preventDefault() let parent = this for (let j = 0; parent.tagName !== 'LI' && j < 20; j++) { parent = parent.parentNode } setTimeout(function () { parent.style.cursor = 'wait'; parent.innerHTML = 'Saving...' }, 0) const url = document.location.href const albumData = await myAlbumsGetAlbum(url) if (albumData) { albumData.listened = false await myAlbumsUpdateAlbum(albumData) } addListenedButtonToCollectControls() } removeViaQuerySelector('#discographyplayer_sharepanel') const li = lastLi.parentNode.appendChild(document.createElement('li')) const button = li.appendChild(document.createElement('span')) const icon = button.appendChild(document.createElement('span')) const a = button.appendChild(document.createElement('a')) li.setAttribute('id', 'discographyplayer_sharepanel') a.addEventListener('click', (ev) => ev.preventDefault()) if (listened) { const date = new Date(listened) const since = timeSince(date) button.title = since + '\nClick to mark as NOT played' button.addEventListener('click', onClickRemoveListened) icon.style.color = 'rgb(0,220,50)' icon.style.textShadow = '1px 0px #DDD,-1px 0px #DDD,0px -1px #DDD,0px 1px #DDD' icon.style.paddingRight = '5px' icon.appendChild(document.createTextNode('\u2714')) a.appendChild(document.createTextNode('Played')) li.appendChild(document.createTextNode(' - ')) const link = li.appendChild(document.createElement('span')) const viewLink = link.appendChild(document.createElement('a')) viewLink.href = findUserProfileUrl() + '#listened-tab' viewLink.title = 'View list of played albums' viewLink.appendChild(document.createTextNode('view')) } else { button.title = 'Click to mark as played' button.addEventListener('click', onClickSetListened) try { icon.style.color = window.getComputedStyle(document.getElementById('pgBd')).backgroundColor icon.style.textShadow = '1px 0px #959595,-1px 0px #959595,0px -1px #959595,0px 1px #959595' icon.style.paddingRight = '5px' } catch (e) { icon.style.color = '#959595' icon.style.fontWeight = 700 } icon.appendChild(document.createTextNode('\u2714')) a.appendChild(document.createTextNode('Unplayed')) } } function makeListenedListTabLink () { const grid = document.getElementById('grids').appendChild(document.createElement('div')) grid.className = 'grid' grid.id = 'listened-grid' const inner = grid.appendChild(document.createElement('div')) inner.className = 'inner' inner.innerHTML = 'Loading...' const li = document.querySelector('ol#grid-tabs').appendChild(document.createElement('li')) li.dataset.tab = 'listened' li.setAttribute('data-grid-id', 'listened-grid') const span = li.appendChild(document.createElement('span')) span.className = 'tab-title' span.appendChild(document.createTextNode('played')) const count = span.appendChild(document.createElement('span')) count.className = 'count' GM.getValue('myalbums', '{}').then(function (str) { let n = 0 const myalbums = JSON.parse(str) for (const key in myalbums) { if (myalbums[key].listened) { n++ } } count.appendChild(document.createTextNode(n)) }) li.addEventListener('click', showListenedListTab) return li } async function showListenedListTab () { if (document.getElementById('owner-controls')) document.getElementById('owner-controls').style.display = 'none' if (document.getElementById('wishlist-controls')) document.getElementById('wishlist-controls').style.display = 'none' const grid = document.getElementById('listened-grid') if (grid.querySelector('.collection-items')) { return } grid.innerHTML = '' const collectionItems = grid.appendChild(document.createElement('div')) collectionItems.className = 'collection-items' const collectionGrid = collectionItems.appendChild(document.createElement('ol')) collectionGrid.className = 'collection-grid' const myalbums = JSON.parse(await GM.getValue('myalbums', '{}')) for (const key in myalbums) { const albumData = myalbums[key] if (!albumData.listened) { continue } const artist = albumData.artist || 'Unkown artist' const title = albumData.title || 'Unkown title' const albumCover = albumData.albumCover || 'https://bandcamp.com/img/0.gif' const url = key const date = new Date(albumData.listened) const since = timeSince(date) const dateStr = dateFormater(date) let releaseDate if ('releaseDate' in albumData) { releaseDate = dateFormaterRelease(new Date(albumData.releaseDate)) } else { releaseDate = 'Unknown' } const li = collectionGrid.appendChild(document.createElement('li')) li.className = 'collection-item-container' li.innerHTML = ` <div class="collection-item-gallery-container"> <span class="bc-ui2 collect-item-icon-alt"></span> <div class="collection-item-art-container"> <img class="collection-item-art" alt="" src="${albumCover}"> </div> <div class="collection-title-details"> <a target="_blank" href="${url}" class="item-link"> <div class="collection-item-title">${title}</div> <div class="collection-item-artist">by ${artist}</div> </a> </div> <div class="collection-item-fav-track"> <span title="${since} ago" class="favoriteTrackLabel">played</span> <div title="${since} ago"> <span class="fav-track-link">${dateStr}</span> </div> <span class="favoriteTrackLabel">released</span> <div> <span class="fav-track-link">${releaseDate}</span> </div> </div> </div> ` } } function clickAddToWishlist () { const wishButton = document.querySelector('#collect-item>*') if (!wishButton) { window.setTimeout(clickAddToWishlist, 300) return } wishButton.click() if (document.querySelector('#collection-main a')) { // if logged in, the click should be successful, so try to close the window window.setTimeout(window.close, 1000) } } if (document.querySelector('.music-grid .music-grid-item a[href^="/album/"] img')) { // Discography page makeAlbumCoversGreat() } if (document.querySelector('.inline_player')) { // Album page with player removeTheTimeHasComeToOpenThyHeartWallet() } if (document.querySelector('.share-panel-wrapper-desktop')) { // Album page with Share,Embed,Wishlist links addListenedButtonToCollectControls() if (document.location.hash === '#collect-wishlist') { clickAddToWishlist() } } if (document.querySelector('ol#grid-tabs li') && document.querySelector('.fan-bio-pic-upload-container')) { const listenedTabLink = makeListenedListTabLink() if (document.location.hash === '#listened-tab') { window.setTimeout(function () { document.querySelector('#grid-tabs .active').classList.remove('active') document.querySelector('#grids .grid.active').classList.remove('active') listenedTabLink.classList.add('active') listenedTabLink.click() }, 500) } } makeAlbumLinksGreat()