Skip to content

Instantly share code, notes, and snippets.

@simonwep
Last active June 6, 2026 22:17
Show Gist options
  • Select an option

  • Save simonwep/24f8cdcd6d32d86e929004013bd660ae to your computer and use it in GitHub Desktop.

Select an option

Save simonwep/24f8cdcd6d32d86e929004013bd660ae to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Spotify ad skipper
// @version 1.0
// @namespace http://tampermonkey.net/
// @description Detects and skips ads on spotify
// @match https://*.spotify.com/*
// @grant none
// @run-at document-start
// @downloadURL https://gist.githubusercontent.com/Simonwep/24f8cdcd6d32d86e929004013bd660ae/raw
// @updateURL https://gist.githubusercontent.com/Simonwep/24f8cdcd6d32d86e929004013bd660ae/raw
// ==/UserScript==
!async function () {
async function queryAsync(query) {
return new Promise(resolve => {
const interval = setInterval(() => {
const element = document.querySelector(query);
if (element) {
clearInterval(interval);
return resolve(element);
}
}, 250);
});
}
/**
* Inject a middleware function in a object or instance
* @param ctx Object or instance
* @param fn Function name
* @param middleware Middleware function
* @param transform Transform function result
*/
function inject({ctx, fn, middleware, transform}) {
const original = ctx[fn];
ctx[fn] = function () {
if (!middleware || middleware.call(this, ...arguments) !== false) {
const result = original.call(this, ...arguments);
return transform ? transform.call(this, result, ...arguments) : result;
}
};
}
const nowPlayingBar = await queryAsync('.now-playing-bar');
const playButton = await queryAsync('button[title=Play], button[title=Pause]');
let audio;
inject({
ctx: document,
fn: 'createElement',
transform(result, type) {
if (type === 'audio') {
audio = result;
}
return result;
}
});
let playInterval;
new MutationObserver(() => {
const link = document.querySelector('.now-playing > a');
if (link) {
if (!audio) {
return console.error('Audio-element not found!');
}
if (!playButton) {
return console.error('Play-button not found!');
}
// console.log('Ad found', audio, playButton, nowPlayingBar);
audio.src = '';
playButton.click();
if (!playInterval) {
playInterval = setInterval(() => {
if (!document.querySelector('.now-playing > a') && playButton.title === 'Pause') {
clearInterval(playInterval);
playInterval = null;
} else {
playButton.click();
}
}, 500);
}
}
}).observe(nowPlayingBar, {
characterData: true,
childList: true,
attributes: true,
subtree: true
});
// Hide upgrade-button and captcha-errors, we don't what to see that.
const style = document.createElement('style');
style.innerHTML = `
[aria-label="Upgrade to Premium"],
body > div:not(#main) {
display: none !important;
}
`;
document.body.appendChild(style);
}();
@simonwep

simonwep commented Sep 5, 2019

Copy link
Copy Markdown
Author

@hannsen Oh, that's nice!

@hannsen

hannsen commented Sep 5, 2019

Copy link
Copy Markdown

Arg, my bad, this doesnt work with gist, as they change the url after you change them.

This url works though: https://gist.githubusercontent.com/Simonwep/24f8cdcd6d32d86e929004013bd660ae/raw

@developerfromjokela

Copy link
Copy Markdown

Where should I put this script?

@simonwep

simonwep commented Feb 8, 2020

Copy link
Copy Markdown
Author

@RonanFelipe

Copy link
Copy Markdown

I'm getting a error. When there is an ad about to start, the music keep in play pause the whole ad duration. The only thing I could do to stop was refresh the page.

@Weridox

Weridox commented Apr 18, 2020

Copy link
Copy Markdown

It stopped working after few weeks :(

@rikarikrika

Copy link
Copy Markdown

it's stop working after few days, how to resolve this? ty

@marcbelmont

Copy link
Copy Markdown

@rikapinka @Weridox

I've updated the script. It now detects ads and mutes until the ad is finished. It's not as clean as previous technique but it does the job for me.
https://gist.github.com/marcbelmont/1ea63270867a4e8786dd5f172d8d4489

@gigawasian

Copy link
Copy Markdown

doesnt work anymore

@gigawasian

Copy link
Copy Markdown

@marcbelmont does it skip ads?

@marcbelmont

marcbelmont commented May 9, 2020

Copy link
Copy Markdown

@marcbelmont does it skip ads?

@dayoshiguy It mutes the ads. Good enough for me.

@Weridox

Weridox commented May 9, 2020

Copy link
Copy Markdown

@marcbelmont
Thank you for your work. I use updated script. Works fine for me.

@doconghaph

Copy link
Copy Markdown

tysm !!

@sinhpn92

Copy link
Copy Markdown

How to use this script? I did run in the console, but it isn't still working.

@MohamedElashri

Copy link
Copy Markdown

How to use this script? I did run in the console, but it isn't still working.

You should use something like violentmonkey to install this script.

@mindplay-dk

Copy link
Copy Markdown

This handy inject function, is that part of a library somewhere?

@Ju2t-us

Ju2t-us commented Oct 10, 2022

Copy link
Copy Markdown

Works perfectly! I don't have much BAT but here's a tip @simonwep

@prawndumplings

prawndumplings commented Jun 10, 2023

Copy link
Copy Markdown

Great work, thankyou.

@codenyte

Copy link
Copy Markdown

Why don't you publish this on Greasy Fork?

@byTT05

byTT05 commented Feb 8, 2025

Copy link
Copy Markdown

Thank you bro. Congrats for the freedom.!!!

@simonwep

Copy link
Copy Markdown
Author

Caution

Please be aware of comments here promoting potential malware / cracked apps - I'll delete these as soon as possible but I don't want to report it to avoid a DMCA takedown notice.

@andrewf76

Copy link
Copy Markdown

The script stopped working for me over the last few weeks. After updating the query selectors I'm now getting Audio-element not found!.

Selectors I updated
const nowPlayingBar = await queryAsync('[data-testid="now-playing-bar"]');
const playButton = await queryAsync('button[data-testid="control-button-playpause"]');
const link = document.querySelector('[data-testid="context-item-info-ad-subtitle"]');

@addavriance

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment