Adds a shuffle play button to "Likes" and playlists
// ==UserScript==
// @name soundcloud shuffle likes
// @version 1.7
// @description Adds a shuffle play button to "Likes" and playlists
// @author bhackel
// @match https://soundcloud.com/*
// @grant none
// @run-at document-end
// @license MIT
// @noframes
// @namespace https://greasyfork.org/en/users/324178-bhackel
// ==/UserScript==
(function() {
'use strict';
/* Injects Button into the page once it has loaded,
then tries to re-add it if it disappears due to page change
*/
function insertButtonLoop() {
let url = window.location.href;
url = url.split('?')[0];
let btnShuffle = document.querySelector('.bhackel-shuffle-likes');
// Check if button does not exist already, and that user is on likes or a playlist
if (!btnShuffle && (url.includes("/likes") || url.includes("/sets/") || url.includes("/discover/"))) {
btnShuffle = document.createElement('Button');
btnShuffle.innerHTML = 'Shuffle Play';
btnShuffle.onclick = function(){ setupLoad(this); };
btnShuffle.scrolling = false;
btnShuffle.interval = 0;
// Case for likes
if (url.includes("you/likes")) {
btnShuffle.className = 'bhackel-shuffle-likes sc-button sc-button-large';
btnShuffle.pageType = "Likes";
// Check if top bar has loaded
let collectionTop = document.querySelector('.collectionSection__top');
if (collectionTop) {
// Insert the button above the grid of tracks
collectionTop.insertBefore(btnShuffle, collectionTop.children[2]);
} else {
setTimeout(insertButtonLoop, 1000);
}
}
// Case for generic user likes
else if (url.includes("/likes") && !url.includes("you/likes")) {
btnShuffle.className = 'bhackel-shuffle-likes sc-button sc-button-medium';
btnShuffle.pageType = "GenericLikes";
// Check if top bar has loaded
let titleBar = document.querySelector(".userNetworkTabs");
if (titleBar) {
// Insert the button above the list of tracks
titleBar.appendChild(btnShuffle);
} else {
setTimeout(insertButtonLoop, 1000);
}
}
// Case for a playlist
else if (url.includes("/sets/") && !url.includes("/discover/")) {
btnShuffle.className = 'bhackel-shuffle-likes sc-button sc-button-medium';
btnShuffle.pageType = "Playlist";
// Check if action bar has loaded
let soundActions = document.querySelector('.soundActions');
if (soundActions) {
// Insert the button after other action buttons
soundActions.children[0].appendChild(btnShuffle);
} else {
setTimeout(insertButtonLoop, 1000);
}
}
// Case for discover playlists
else if (url.includes("/discover/sets/")) {
btnShuffle.className = 'bhackel-shuffle-likes sc-button sc-button-medium';
btnShuffle.pageType = "Discover";
// Check if action bar has loaded
let playlistControls = document.querySelector('.systemPlaylistDetails__controls');
if (playlistControls) {
// Insert the button after other action buttons
playlistControls.appendChild(btnShuffle);
} else {
setTimeout(insertButtonLoop, 1000);
}
}
}
// Perform another check in 3 seconds, in the case button has been removed
setTimeout(insertButtonLoop, 3000);
}
/* Changes the text of the button, resets the queue to have the user's
likes, then starts the scrolling loop. Or it stops the loop from running.
*/
function setupLoad(btn) {
// Check whether the loop is running or not
if (btn.scrolling === false) {
btn.innerHTML = 'Click to Stop Loading';
btn.scrolling = true;
// The list of tracks visible on screen, which changes for a playlist or likes
let tracks;
if (btn.pageType === "Likes") {
tracks = document.querySelector('.lazyLoadingList__list');
} else if (btn.pageType === "GenericLikes") {
tracks = document.querySelector('.lazyLoadingList__list');
} else if (btn.pageType === "Playlist") {
tracks = document.querySelector('.trackList__list');
} else if (btn.pageType === "Discover") {
tracks = document.querySelector('.systemPlaylistTrackList__list');
}
if (tracks.childElementCount > 2) {
// Reset the queue to the beginning of the list of tracks
let firstTrack = tracks.children[0];
let secondTrack = tracks.children[1];
let firstPlayButton = firstTrack.querySelector(".playButton");
let secondPlayButton = secondTrack.querySelector(".playButton");
// Reset by playing 2, playing 1, then pausing playback
secondPlayButton.click();
setTimeout(function(){ firstPlayButton.click(); }, 150);
setTimeout(function(){
let playButton = document.querySelector('.playControl');
if (playButton.classList.contains('playing')) {
playButton.click();
}
}, 500);
// Add the first track to the queue so it gets shuffled
tracks.getElementsByClassName("sc-button-more")[0].click()
document.getElementsByClassName("moreActions__button addToNextUp")[0].click()
// Open the queue to load it
toggleQueue('open');
// Setup the scrolling loop - Needs time before running so the queue loads
btn.timeout = setTimeout(function(){
btn.interval = setInterval(function() { scrollQueue(btn); }, 500);
}, 3000);
} else {
// The list has two or less tracks - cannot shuffle play
btn.innerHTML = 'Error: Too Few Tracks';
}
} else {
clearInterval(btn.interval);
clearTimeout(btn.timeout);
btn.interval = 0;
btn.scrolling = false;
btn.innerHTML = 'Shuffle Play';
}
}
/* Scrolls the queue down, ensuring that the queue is open by opening it
*/
function scrollQueue(btn) {
let queue = document.querySelector('.queue');
// Check if the queue is open
if (queue.classList.contains('m-visible')) {
// Scroll the queue to the bottom, loading new tracks below
let scrollableQueue = document.querySelector('.queue__scrollableInner');
let queueContainer = document.querySelector('.queue__itemsHeight');
let scrollToHeight = parseInt(queueContainer.style.height);
scrollableQueue.scroll(0,scrollToHeight);
// Check if all tracks are loaded, then play
let autoplayDiv = document.querySelector('.queue__fallback');
if (autoplayDiv) {
clearInterval(btn.interval);
btn.scrolling = false;
btn.interval = 0;
play(btn);
}
} else {
// Open the queue if it is closed
toggleQueue('open');
}
}
/* Shuffles the queue, skips the first track, then plays it
*/
function play(btn) {
btn.innerHTML = 'Shuffle Play';
let playButton = document.querySelector('.playControl');
let shuffleButton = document.querySelector('.shuffleControl');
let skipButton = document.querySelector(".skipControl__next");
// Re-Shuffle tracks if shuffle is enabled, and enable shuffle if it is disabled
if (shuffleButton.classList.contains('m-shuffling')) {
shuffleButton.click();
shuffleButton.click();
} else if (!shuffleButton.classList.contains('m-shuffling')) {
shuffleButton.click();
}
// Skip the duplicate first track that was added previously
// This also begins playback
skipButton.click();
// Close the queue if it is open
toggleQueue('close');
// Add focus back to the play/pause button so keybinds work
playButton.focus()
}
/* Opens or closes the song queue
*/
function toggleQueue(changeToState) {
let queue = document.querySelector('.queue');
let isQueueOpen = queue.classList.contains('m-visible');
// Toggle queue if the queue is open and it should be closed, or if it's closed and should be open
if ((isQueueOpen && changeToState === 'close') || (!isQueueOpen && changeToState === 'open')) {
let queueTrigger = document.querySelector('.playbackSoundBadge__queueCircle');
queueTrigger.click();
}
}
insertButtonLoop();
})();