您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a button to remove videos from playlists just like on mobile
当前为
// ==UserScript== // @name Youtube Mobile-like Playlist Remove Video Button // @license MIT // @namespace rtonne // @match https://www.youtube.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @grant none // @version 1.4 // @author Rtonne // @description Adds a button to remove videos from playlists just like on mobile // @run-at document-end // ==/UserScript== let currentUrl = null; const urlRegex = /^https:\/\/www.youtube.com\/playlist\?list=.*$/; // Using observer to run script whenever the body changes // because youtube doesn't reload when changing page const observer = new MutationObserver(async () => { try { let newUrl = window.location.href; // Because youtube doesn't reload on changing url // we have to allow the whole website and check here if we are in a playlist if (!urlRegex.test(newUrl)) { return; } const elements = await waitForElements( document, "ytd-playlist-video-renderer" ); // If the url is different we are in a different playlist // Or if the playlist length is different, we loaded more of the same playlist if ( currentUrl === newUrl && elements.length === document.querySelectorAll(".rtonne-youtube-playlist-delete-button") .length ) { return; } currentUrl = newUrl; // If the list cannot be sorted, we assume we can't remove from it either if ( !document.querySelector( "#header-container > #filter-menu > yt-sort-filter-sub-menu-renderer" ) ) { return; } elements.forEach((element) => { // Youtube reuses elements, so we check if element already has a button if (element.querySelector(".rtonne-youtube-playlist-delete-button")) return; // =========== // Now we create the button and add it to each video // =========== const elementStyle = document.defaultView.getComputedStyle(element); const button = document.createElement("button"); button.className = "rtonne-youtube-playlist-delete-button"; button.innerHTML = getYoutubeTrashSvg(); button.style.height = elementStyle.height; button.style.padding = "0"; button.style.borderRadius = `0 ${elementStyle.borderTopRightRadius} ${elementStyle.borderBottomRightRadius} 0`; button.style.borderWidth = "0"; button.style.fill = "var(--yt-spec-text-primary)"; button.onmouseover = () => { button.style.backgroundColor = "var(--yt-spec-static-brand-red)"; }; button.onmouseleave = () => { button.style.backgroundColor = "var(--yt-spec-additive-background)"; }; button.onmouseleave(); element.onmouseover = () => { button.style.width = "var(--yt-icon-width)"; }; element.onmouseleave = () => { button.style.width = "0"; }; element.onmouseleave(); element.appendChild(button); button.onclick = async () => { // Click the 3 dot menu button on the video element.querySelector('button.yt-icon-button').click(); const popup = ( await waitForElements( document, "tp-yt-iron-dropdown.ytd-popup-container:has(> div > ytd-menu-popup-renderer)" ) )[0]; // Set the popup opacity to 0 to hide it popup.style.opacity = "0"; const removeMenuItem = ( await waitForElements( popup, `ytd-menu-service-item-renderer:has(path[d="${getSvgPathD()}"])` ) )[0]; // Click the remove video from playlist button in the popup removeMenuItem.click(); // Set the opacity back to default popup.style.opacity = null; }; }); } catch (err) { console.log(err); } }); observer.observe(document.body, { childList: true, subtree: true, }); // I couldn't check if we changed from an editable list to a non-editable list // in the other observer, so I have this one to just do that and remove the buttons const sortObserver = new MutationObserver(() => { if (!urlRegex.test(window.location.href)) { return; } if ( !document.querySelector( "#header-container > #filter-menu > yt-sort-filter-sub-menu-renderer" ) ) { document .querySelectorAll(".rtonne-youtube-playlist-delete-button") .forEach((element) => element.remove()); } }); sortObserver.observe(document.body, { childList: true, subtree: true, }); function getYoutubeTrashSvg() { return `<div style="height: 24px;"> <svg enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24" focusable="false" style="pointer-events: none; display: block; width: 100%; height: 100%;"> <path d="${getSvgPathD()}"></path> </svg></div>`; } // This function is separate to find the menu's remove button in the observer function getSvgPathD() { return "M11 17H9V8h2v9zm4-9h-2v9h2V8zm4-4v1h-1v16H6V5H5V4h4V3h6v1h4zm-2 1H7v15h10V5z"; } // https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists // This function is required because youtube uses too much JS // and elements take a while to appear sometimes function waitForElements(node, selector) { return new Promise((resolve) => { if (node.querySelector(selector)) { return resolve(node.querySelectorAll(selector)); } const observer = new MutationObserver(() => { if (node.querySelector(selector)) { observer.disconnect(); resolve(node.querySelectorAll(selector)); } }); observer.observe(document.body, { childList: true, subtree: true, }); }); }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址