Last active
July 31, 2025 21:18
-
-
Save Slluxx/af1afbf96dbb0e42c2bac1949b4dce17 to your computer and use it in GitHub Desktop.
Spotify Desk2Mobile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Spotify Desk2Mobile | |
// @namespace https://github.com/Slluxx/Desk2Mobile | |
// @downloadURL https://gist.github.com/Slluxx/af1afbf96dbb0e42c2bac1949b4dce17/raw/spotifydesk2mobile.user.js | |
// @updateURL https://gist.github.com/Slluxx/af1afbf96dbb0e42c2bac1949b4dce17/raw/spotifydesk2mobile.user.js | |
// @version 2025-07-31.3 | |
// @description Turns the desktop-mode website back into mobile to restore a fully working webplayer. | |
// @author SLX | |
// @match https://open.spotify.com/* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=spotify.com | |
// @grant none | |
// ==/UserScript== | |
/* | |
// Spotify’s web player doesn’t play nice with Android - it pushes you toward | |
// the app and makes the browser version nearly unusable. Even switching to desktop mode is a mess. | |
// This simple Tampermonkey/Greasemonkey script fixes that by transforming the desktop view back | |
// into a mobile-friendly layout without losing any features. You’ll need a browser that supports | |
// extensions (like Firefox), and Tampermonkey or Greasemonkey installed. | |
// DISCORD: https://discord.gg/DDnGpsh6 | |
*/ | |
(function () { | |
'use strict'; | |
function isMobile() { | |
const regex = /Mobi|Mobile|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; | |
return regex.test(navigator.userAgent); | |
} | |
const toggleFullscreen = async () => { | |
if (!document.fullscreenElement) { | |
try { | |
await document.documentElement.requestFullscreen(); | |
} catch (err) { | |
console.error(err); | |
} | |
let elm = container.querySelector('#fullscreenmsg'); | |
elm.classList.add("hidden"); | |
} else { | |
document.exitFullscreen().catch(console.error); | |
} | |
}; | |
let wakeLock = null; | |
const toggleOled = async () => { | |
const overlay = document.querySelector('#oledOverlay'); | |
if (overlay.classList.contains("hidden") == false) { | |
overlay.classList.add("hidden"); | |
if (wakeLock) { | |
try { | |
await wakeLock.release(); | |
wakeLock = null; | |
console.log("Wake lock released"); | |
} catch (err) { | |
console.error("Wake lock release failed:", err); | |
} | |
} | |
} else { | |
overlay.classList.remove("hidden"); | |
try { | |
wakeLock = await navigator.wakeLock.request("screen"); | |
console.log("Wake lock acquired"); | |
} catch (err) { | |
console.error("Wake lock request failed:", err); | |
} | |
} | |
}; | |
// Inject styles | |
const style = document.createElement('style'); | |
style.textContent = ` | |
#oledOverlay { | |
position: absolute; | |
width: 100vw; | |
height: 100vh; | |
background-color: black; | |
z-index: 99999; | |
} | |
/* Non-Desktop-Alert */ | |
.nonDesktopAlert { | |
background-color: #000000; | |
position: fixed; | |
width: 100vw; | |
height: 100vh; | |
z-index: 1; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
left: 0px; | |
right: 0px; | |
top: 0px; | |
bottom: 0px; | |
flex-direction: column; | |
box-shadow: inset 0px 0px 105px teal; | |
} | |
.nonDesktopAlert > p { | |
width: 70vw; | |
color: white; | |
font-size: larger; | |
} | |
.hidden { | |
display: none !important; | |
} | |
#fullscreenmsg { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100vw; | |
height: 100vh; | |
background: rgba(0, 0, 0, 0.95); | |
color: white; | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
font-size: 6vw; | |
z-index: 99999; | |
text-align: center; | |
padding: 5vw; | |
box-sizing: border-box; | |
box-shadow: inset 0px 0px 105px teal; | |
} | |
#fullscreenmsg button { | |
margin: 4vw; | |
padding: 3vw 6vw; | |
font-size: 5vw; | |
border: none; | |
border-radius: 1.5vw; | |
background-color: teal; | |
color: white; | |
cursor: pointer; | |
transition: background 0.3s; | |
} | |
#fullscreenmsg button:hover { | |
background-color: darkcyan; | |
} | |
/* destop to phone adjustments */ | |
/* slight margin to look better */ | |
#global-nav-bar { | |
margin-bottom: 0.5rem; | |
margin-top: 0.5rem; | |
} | |
/* hide spotify logo */ | |
#global-nav-bar > div:nth-of-type(1) > a { | |
display: none!important; | |
} | |
/* hide upgrade button */ | |
[data-testid="upgrade-button"] { | |
display: none!important; | |
} | |
/* hide download button */ | |
[href="/download"] { | |
display: none!important; | |
} | |
/* hide friend activity button */ | |
[data-testid="friend-activity-button"] { | |
display: none!important; | |
} | |
/* | |
context menu is shared by multiple actions. | |
it removes the "open in app" button aswell as a spacer div to change the view. | |
hacky workaround. | |
*/ | |
#context-menu > ul > :last-child { | |
display: none!important; | |
} | |
/* hide account dropdown items */ | |
[data-testid="user-widget-menu"] > ul > li:nth-child(8), | |
[data-testid="user-widget-menu"] > ul > li:nth-child(9), | |
[data-testid="user-widget-menu"] > ul > li:nth-child(10) { | |
display: none!important; | |
} | |
body { | |
min-width: unset!important; | |
} | |
#main > div { | |
--panel-gap: 0px!important; | |
} | |
/* hide left sidebar */ | |
#Desktop_LeftSidebar_Id { | |
display: none; | |
} | |
/* hide right sidebar */ | |
#main > div > div:nth-of-type(2) > div:nth-of-type(7) { | |
display: none!important; | |
} | |
/* remove min width of playbar */ | |
[data-testid="now-playing-bar"] { | |
min-width: unset!important; | |
} | |
/* left side player details */ | |
[data-testid="now-playing-bar"] > div > div:nth-of-type(1) { | |
display: none; | |
} | |
/* right side player buttons */ | |
[data-testid="now-playing-bar"] > div > div:nth-of-type(3) { | |
display: none; | |
} | |
/* fix width of playing bar*/ | |
[data-testid="now-playing-bar"] > div > div:nth-of-type(2) { | |
max-width: unset!important; | |
width: 90%!important; | |
} | |
/* fix space around playing bar*/ | |
[data-testid="now-playing-bar"] > div { | |
justify-content: space-around!important; | |
} | |
/* remove footer */ | |
[data-testid="footer-div"] { | |
display: none; | |
} | |
/* create better spacing for songs | |
[data-testid="tracklist-row"] { | |
grid-template-columns: [index] var(--tracklist-index-column-width,5px) [first] minmax(5px,var(--col1,4fr)) [last] minmax(50px,var(--col2,1fr))!important; | |
} */ | |
/* remove image cover if it exists */ | |
[data-testid="tracklist-row"] > div:nth-child(2) > :not(:last-child) { | |
display: none; | |
} | |
/* remove add/remove quick button */ | |
[data-testid="tracklist-row"] > div:last-child > :first-child { | |
display: none; | |
} | |
`; | |
document.head.appendChild(style); | |
// Create container and set innerHTML | |
const container = document.createElement('div'); | |
container.id = "spotyclean" | |
container.innerHTML = ` | |
<div id="oledOverlay" class="hidden"></div> | |
<div id="nonDesktopAlert" class="nonDesktopAlert hidden"><p>To use Spotify Desk2Mobile, you have to enable desktop mode for this website.</p><p>Once you enable desktop mode, make sure to zoom out all the way!</p></div> | |
<div id="fullscreenmsg" class="hidden"> | |
<p>Enable fullscreen? <br/> (You should)</p> | |
<div> | |
<button id="fs-yes">Yes</button> | |
<button id="fs-no">No</button> | |
</div> | |
</div> | |
`; | |
document.body.appendChild(container); | |
// if on mobile, we need to display the alert and stop everything | |
if (isMobile()) { | |
const nonDesktopAlert = container.querySelector('#nonDesktopAlert'); | |
nonDesktopAlert.classList.remove("hidden"); | |
return; | |
} | |
// if not in fullscreen, we need to display a message | |
if (!document.fullscreenElement) { | |
const fsmsg = container.querySelector('#fullscreenmsg'); | |
fsmsg.classList.remove("hidden"); | |
} | |
/* Click and unclick the searchbar to extend it */ | |
const observer = new MutationObserver((mutations, obs) => { | |
const form = document.querySelector('[data-encore-id="formInputIcon"]'); | |
if (form) { | |
form.click(); | |
if (document.activeElement) { | |
document.activeElement.blur(); | |
} | |
obs.disconnect(); | |
} | |
}); | |
observer.observe(document.body, { | |
childList: true, | |
subtree: true | |
}); | |
/* Wait for user-widget-menu and add out own buttons*/ | |
const observer2 = new MutationObserver((mutations, obs) => { | |
const menu = document.querySelector('[data-testid="user-widget-menu"]'); | |
if (menu && menu.firstChild && menu.firstChild.childNodes.length > 1 && menu.firstChild.firstChild.id != "fsbtn") { | |
const ul = menu.firstChild | |
const spacer = ul.querySelector(":scope > div").cloneNode(true) | |
const li = ul.firstChild | |
const newLiFullscreen = li.cloneNode(true); | |
const newLiOled = li.cloneNode(true); | |
const newLiUpdate = li.cloneNode(true); | |
newLiFullscreen.firstChild.lastChild.innerHTML = "" | |
newLiOled.firstChild.lastChild.innerHTML = "" | |
newLiFullscreen.firstChild.firstChild.innerHTML = "Toggle fullscreen" | |
newLiOled.firstChild.firstChild.innerHTML = "Wakelock + OLED" | |
newLiUpdate.firstChild.firstChild.innerHTML = "Update Spotify D2M" | |
newLiFullscreen.id = "fsbtn"; | |
newLiFullscreen.addEventListener('click', toggleFullscreen); | |
newLiOled.addEventListener('click', toggleOled); | |
newLiUpdate.addEventListener('click', function(){ | |
window.open("https://gist.github.com/Slluxx/af1afbf96dbb0e42c2bac1949b4dce17/raw/spotifydesk2mobile.user.js", "_blank"); | |
}); | |
ul.insertBefore(spacer.cloneNode(true), ul.firstChild); | |
ul.insertBefore(newLiUpdate, ul.firstChild); | |
ul.insertBefore(spacer, ul.firstChild); | |
ul.insertBefore(newLiOled, ul.firstChild); | |
ul.insertBefore(newLiFullscreen, ul.firstChild); | |
console.log("buttons added"); | |
} | |
}); | |
observer2.observe(document.body, { | |
childList: true, | |
subtree: true | |
}); | |
document.getElementById("fs-yes").addEventListener("click", function () { | |
const fsmsg = container.querySelector('#fullscreenmsg'); | |
fsmsg.classList.add("hidden"); | |
try { | |
document.documentElement.requestFullscreen(); | |
} catch (err) { | |
console.error(err); | |
} | |
}) | |
document.getElementById("fs-no").addEventListener("click", function () { | |
const fsmsg = container.querySelector('#fullscreenmsg'); | |
fsmsg.classList.add("hidden"); | |
}) | |
const overlay = container.querySelector('#oledOverlay'); | |
overlay.onclick = toggleOled; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment