Last active
September 5, 2024 17:22
-
-
Save jerome-breton/986d1221ac062f77a35e51b976d131f1 to your computer and use it in GitHub Desktop.
Bookmarklet for removing played items from your Youtube Playlist
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
| /** Minified version with https://dotmaui.com/jsminify/ | |
| javascript:(()=>{let e=window.location,t=document;if("www.youtube.com"!=e.host&&"/playlist"!=e.pathname)return void alert("You must be in a Youtube playlist !");let n=t.createElement("div");n.style="position:fixed;z-index:2999;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.8);",t.body.appendChild(n);let o=new URLSearchParams(e.search),i=t.createElement("iframe");i.src=e.origin+e.pathname+"?"+o.toString(),i.style="display:none",i.sandbox="allow-same-origin allow-scripts allow-top-navigation allow-presentation";let r="("+(e=>{function t(e){return new Promise(t=>{let n;if(n=document.querySelector(e))return t(n);const o=new MutationObserver(i=>{(n=document.querySelector(e))&&(t(n),o.disconnect())});o.observe(document.body,{childList:!0,subtree:!0})})}function n(e,r,l){var a;if(e.length){let o=e.pop().closest("ytd-playlist-video-renderer");o.querySelector("#menu #button").click(),t("tp-yt-iron-dropdown:not([id=dropdown])").then(t=>{(a=t,new Promise(e=>{if("none"!=a.style.display)return e(a);const t=new MutationObserver(n=>{"none"!=a.style.display&&(e(a),t.disconnect())});t.observe(a,{attributes:!0})})).then(()=>{[].slice.call(document.querySelectorAll("ytd-menu-service-item-renderer"))[3].click(),function(e){return new Promise(t=>{if(!e.isConnected)return t(e);const n=new MutationObserver(o=>{e.isConnected||(t(e),n.disconnect())});n.observe(e.parentNode,{childList:!0})})}(o).then(()=>{let t=100*(r-e.length)/r;i.postMessage({w:t+"%",i:Math.round(t)+"% ("+(r-e.length)+"/"+r+")"},"*"),window.setTimeout(()=>{n(e,r,l)},500)})})})}else if(i.postMessage({i:"Checking, please wait..."},"*"),l)console.log("Reload !");else{let e=/([?&])rnd=.*?(&|$)/i;o.location.search.match(e)?o.location=o.location.href.replace(e,"$1rnd="+Date.now()+"$2"):o.location=o.location.href+"&rnd="+Date.now()}}let o=document,i=window.parent;t("ytd-playlist-video-list-renderer").then(()=>{window.setTimeout(()=>{let t=Array.from(document.querySelectorAll("ytd-thumbnail-overlay-resume-playback-renderer"));t.length?n(t,t.length,e):(i.postMessage({i:"Refreshing, please wait..."},"*"),i.location.reload(!0))},2e3)})})+")(false)";window.trustedTypes&&trustedTypes.createPolicy&&(r=trustedTypes.createPolicy("noEscapePolicy",{createScript:e=>e}).createScript(r)),i.onload=(()=>{i.contentWindow.eval(r)}),t.body.appendChild(i);let l=t.createElement("div");l.style="position:absolute;left:0;right:0;top:0;bottom:0;width:830px;height:4px;margin:auto;background:#999;padding:0",n.appendChild(l);let a=t.createElement("div");a.style="height:4px;background:red;transition:width 1s ease 0s;width:0;",l.appendChild(a);let s=t.createElement("div");s.style="position:absolute;width:100%;height:100%;text-align:center;font-size:13px;color:white;line-height:30px;top:0;",s.innerText="Loading, please wait...",l.appendChild(s),window.addEventListener("message",e=>{let t=e.data;t.hasOwnProperty("w")&&(a.style.width=t.w),t.hasOwnProperty("i")&&(s.innerText=t.i)},!1)})(); | |
| **/ | |
| javascript:(() => { | |
| let winloc = window.location; | |
| let doc = document; | |
| let debug = false; | |
| let viewframe = debug || false; | |
| if(debug) debugger; | |
| if(winloc.host != 'www.youtube.com' && winloc.pathname != '/playlist'){ | |
| alert('You must be in a Youtube playlist !'); | |
| return; | |
| } | |
| /* Code being eval'ed in iframe */ | |
| let loop = (debug) => { | |
| if(debug) debugger; | |
| let doc = document; | |
| let winpar = window.parent; | |
| function waitForElm(selector) { | |
| return new Promise(resolve => { | |
| let elm; | |
| if (elm = document.querySelector(selector)) { | |
| return resolve(elm); | |
| } | |
| const observer = new MutationObserver(mutations => { | |
| if (elm = document.querySelector(selector)) { | |
| resolve(elm); | |
| observer.disconnect(); | |
| } | |
| }); | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| }); | |
| } | |
| function waitForVisible(elm) { | |
| return new Promise(resolve => { | |
| if (elm.style.display != 'none') { | |
| return resolve(elm); | |
| } | |
| const observer = new MutationObserver(mutations => { | |
| if (elm.style.display != 'none') { | |
| resolve(elm); | |
| observer.disconnect(); | |
| } | |
| }); | |
| observer.observe(elm, { | |
| attributes: true | |
| }); | |
| }); | |
| } | |
| function waitForDisconnected(elm) { | |
| return new Promise(resolve => { | |
| if (!elm.isConnected) { | |
| return resolve(elm); | |
| } | |
| const observer = new MutationObserver(mutations => { | |
| if (!elm.isConnected) { | |
| resolve(elm); | |
| observer.disconnect(); | |
| } | |
| }); | |
| observer.observe(elm.parentNode, { | |
| childList : true | |
| }); | |
| }); | |
| } | |
| function removeNext(c, max, debug){ | |
| if (!c.length) { | |
| /* Job maybe done, reload iframe */ | |
| winpar.postMessage({ i: 'Checking, please wait...' }, '*'); | |
| if (debug) { | |
| console.log('Reload !') | |
| } else { | |
| let regex = new RegExp("([?&])rnd=.*?(&|$)", "i"); | |
| if (doc.location.search.match(regex)) { | |
| doc.location = doc.location.href.replace(regex, '$1rnd=' + Date.now() + '$2'); | |
| } else { | |
| doc.location = doc.location.href + "&rnd=" + Date.now(); | |
| } | |
| } | |
| return; | |
| } else { | |
| /* Click menu */ | |
| let v = c.pop().closest("ytd-playlist-video-renderer"); | |
| v.querySelector("#menu #button").click(); | |
| waitForElm("tp-yt-iron-dropdown:not([id=dropdown])").then((m) => { | |
| waitForVisible(m).then(() => { | |
| /* Click 4th option */ | |
| let d = [].slice.call(document.querySelectorAll('ytd-menu-service-item-renderer')); | |
| d[3].click() | |
| waitForDisconnected(v).then(() => { | |
| /* Update progress */ | |
| let pc = 100 * (max - c.length) / max; | |
| winpar.postMessage({ w: pc + '%', i: Math.round(pc) + '% (' + (max - c.length) + '/' + max + ')' }, '*'); | |
| window.setTimeout(() => { | |
| removeNext(c, max, debug); | |
| }, 500); | |
| }); | |
| }); | |
| }); | |
| } | |
| } | |
| waitForElm('ytd-playlist-video-list-renderer').then(() => { | |
| window.setTimeout(() => { | |
| /* Prevent real deletion and refreshing */ | |
| if(debug) debugger; | |
| /* Get played */ | |
| let c = Array.from(document.querySelectorAll('ytd-thumbnail-overlay-resume-playback-renderer')); | |
| if(!c.length){ | |
| /* Job done, reload parent ! */ | |
| winpar.postMessage({i: 'Refreshing, please wait...'}, '*'); | |
| winpar.location.reload(true); | |
| } else { | |
| removeNext(c, c.length, debug); | |
| } | |
| }, 2000); | |
| }); | |
| }; | |
| /* Background color mask */ | |
| let ov = doc.createElement('div'); | |
| ov.style = 'position:fixed;z-index:2999;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.8);'; | |
| doc.body.appendChild(ov); | |
| /* Iframe */ | |
| let urlParams = new URLSearchParams(winloc.search); | |
| let ifr = doc.createElement('iframe'); | |
| ifr.src = winloc.origin + winloc.pathname + '?' + urlParams.toString(); | |
| ifr.style = viewframe ? 'display:block;z-index:4000;position:relative;top:0;left:0;width:100%;height:50vh' : 'display:none'; | |
| ifr.sandbox = "allow-same-origin allow-scripts allow-top-navigation allow-presentation" | |
| /* Inject loop function in iframe */ | |
| let loopString = '(' + loop.toString() + ')(' + (debug?'true':'false') + ')'; | |
| if (window.trustedTypes && trustedTypes.createPolicy) { | |
| loopString = trustedTypes.createPolicy('noEscapePolicy', { createScript: string => string }).createScript(loopString); | |
| } | |
| ifr.onload = () => { ifr.contentWindow.eval(loopString); }; | |
| doc.body.appendChild(ifr); | |
| /* Progress bar background */ | |
| let bar = doc.createElement('div'); | |
| bar.style = 'position:absolute;left:0;right:0;top:0;bottom:0;width:830px;height:4px;margin:auto;background:#999;padding:0'; | |
| ov.appendChild(bar); | |
| /* and front */ | |
| let pbar = doc.createElement('div'); | |
| pbar.style = 'height:4px;background:red;transition:width 1s ease 0s;width:0;'; | |
| bar.appendChild(pbar); | |
| /* and text */ | |
| let bt = doc.createElement('div'); | |
| bt.style = 'position:absolute;width:100%;height:100%;text-align:center;font-size:13px;color:white;line-height:30px;top:0;'; | |
| bt.innerText = 'Loading, please wait...'; | |
| bar.appendChild(bt); | |
| window.addEventListener("message", (event) => { | |
| let data = event.data; | |
| if(data.hasOwnProperty('w')){ | |
| pbar.style.width = data.w; | |
| } | |
| if(data.hasOwnProperty('i')){ | |
| bt.innerText = data.i; | |
| } | |
| }, false); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment