您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
As the name implies, sorts youtube watch later by duration
当前为
// Changelog 20/6: // Added autoscroll cause apparently it doesn't do so on my instance of Firefox // Added buttons this time // Moved the code to be GreaseMonkey/TamperMonkey compatible /* jshint esversion: 8 */ // ==UserScript== // @name Sort Youtube Watch Later by Duration // @namespace https://gist.github.com/KohGeek/65ad9e0118ee5f5ee484676731bcd092 // @version v1.0 // @description As the name implies, sorts youtube watch later by duration // @author KohGeek // @license GNU GPLv2 // @match http://*.youtube.com/playlist* // @match https://*.youtube.com/playlist* // @require https://gf.zukizuki.org/scripts/374849-library-onelementready-es7/code/Library%20%7C%20onElementReady%20ES7.js // @grant none // @run-at document-start // ==/UserScript== // Heavily borrowed from many places // function for triggering mouse events let fireMouseEvent = (type, elem, centerX, centerY) => { var evt = document.createEvent("MouseEvents"); evt.initMouseEvent(type, true, true, window, 1, 1, 1, centerX, centerY, false, false, false, false, 0, elem); elem.dispatchEvent(evt); }; // https://ghostinspector.com/blog/simulate-drag-and-drop-javascript-casperjs/ let simulateDrag = (elemDrag, elemDrop) => { // calculate positions var pos = elemDrag.getBoundingClientRect(); var center1X = Math.floor((pos.left + pos.right) / 2); var center1Y = Math.floor((pos.top + pos.bottom) / 2); pos = elemDrop.getBoundingClientRect(); var center2X = Math.floor((pos.left + pos.right) / 2); var center2Y = Math.floor((pos.top + pos.bottom) / 2); // mouse over dragged element and mousedown fireMouseEvent("mousemove", elemDrag, center1X, center1Y); fireMouseEvent("mouseenter", elemDrag, center1X, center1Y); fireMouseEvent("mouseover", elemDrag, center1X, center1Y); fireMouseEvent("mousedown", elemDrag, center1X, center1Y); // start dragging process over to drop target fireMouseEvent("dragstart", elemDrag, center1X, center1Y); fireMouseEvent("drag", elemDrag, center1X, center1Y); fireMouseEvent("mousemove", elemDrag, center1X, center1Y); fireMouseEvent("drag", elemDrag, center2X, center2Y); fireMouseEvent("mousemove", elemDrop, center2X, center2Y); // trigger dragging process on top of drop target fireMouseEvent("mouseenter", elemDrop, center2X, center2Y); fireMouseEvent("dragenter", elemDrop, center2X, center2Y); fireMouseEvent("mouseover", elemDrop, center2X, center2Y); fireMouseEvent("dragover", elemDrop, center2X, center2Y); // release dragged element on top of drop target fireMouseEvent("drop", elemDrop, center2X, center2Y); fireMouseEvent("dragend", elemDrag, center2X, center2Y); fireMouseEvent("mouseup", elemDrag, center2X, center2Y); } // To explain what broke in the original code, here is a comment // The original code targeted the thumbnail for dragging when that is no longer viable // Additionally, the timestamp is now two elements instead of one, so I fixed that let sortVideosByLength = (allAnchors, allDragPoints) => { let videos = []; for (let j = 0; j < allAnchors.length; j++) { let thumb = allAnchors[j]; let drag = allDragPoints[j]; let href = thumb.href; if (href && href.includes("&list=WL&")) { let timeSpan = thumb.querySelector("#text"); let timeDigits = timeSpan.innerText.trim().split(":").reverse(); var time = parseInt(timeDigits[0]); if (timeDigits[1]) time += parseInt(timeDigits[1]) * 60; if (timeDigits[2]) time += parseInt(timeDigits[2]) * 3600; videos.push({ anchor: drag, time: time, originalIndex: j }); } } if (videos.length > 1) { for (let j = 0; j < videos.length - 1; j++) { var smallestLength = 86400; var smallestIndex = -1; for (var k = j + 1; k < videos.length; k++) { if ( videos[k].time < videos[j].time && videos[k].time < smallestLength ) { smallestLength = videos[k].time; smallestIndex = k; } } if (smallestIndex > -1) { console.log("drag " + smallestIndex + " to " + j); var elemDrag = videos[smallestIndex].anchor; var elemDrop = videos[j].anchor; simulateDrag(elemDrag, elemDrop); return j; } } return videos.length; } return 0; } // There is an inherent limit in how fast you can sort the videos, due to Youtube refreshing // This limit also applies if you do it manually // It is also much worse if you have a lot of videos, for every 100 videos, it's about an extra 2-4 seconds, maybe longer let zeLoop = async () => { let count = document.querySelectorAll("ytd-playlist-video-renderer").length; let element = document.scrollingElement; let quantaToWait = Math.max(0, Math.ceil((count - 100)/100)); // about 2600 ms of load per 100 videos let currentMinimum = 0; while (true) { let allAnchors = document.querySelectorAll("div#content a#thumbnail.inline-block.ytd-thumbnail"); let allDragPoints = document.querySelectorAll("yt-icon#reorder"); let currentScroll = element.scrollTop; do { currentScroll = element.scrollTop; element.scrollTop = element.scrollHeight; await new Promise((r) => setTimeout(r, quantaToWait * 1000)); } while (currentScroll != element.scrollTop); try { currentMinimum = sortVideosByLength(allAnchors, allDragPoints); } catch (e) { if (e instanceof TypeError) { console.log("Problem with loading, waiting a bit more.") await new Promise((r) => setTimeout(r, quantaToWait * 1000)); currentMinimum = sortVideosByLength(allAnchors, allDragPoints); // If it somehow still dies, waits another full cycle } } if (currentMinimum === count) { // If your document is already partially sorted, this will break the code early console.log("Sort complete, or you didn't load all the videos. Video sorted: " + currentMinimum); break; } await new Promise((r) => setTimeout(r, quantaToWait * 2500)); //Please set this time as needed, youtube refreshes everytime the WL gets changed } } // If the loading time is for some reason hugely inconsistent, you can use this instead to do it one by one let zeWithoutLoop = () => { let allAnchors = document.querySelectorAll("div#content a#thumbnail.inline-block.ytd-thumbnail"); let allDragPoints = document.querySelectorAll("yt-icon#reorder"); sortVideosByLength(allAnchors, allDragPoints); } /** * Generate menu container element */ let renderContainerElement = () => { const element = document.createElement('div') element.className = 'sort-playlist' element.style.paddingBottom = '16px' document.querySelector('ytd-playlist-sidebar-secondary-info-renderer.ytd-playlist-sidebar-renderer').prepend(element) } /** * Generate button element * @param {function} click - OnClick handler * @param {String=} label - Button Label */ let renderButtonElement = (click = () => {}, label = '') => { // Create button const element = document.createElement('button') element.className = 'style-scope' element.style.backgroundColor = '#30d030' element.style.border = '1px #a0a0a0' element.style.borderRadius = '2px' element.style.padding = '3px' element.style.margin = '3px' element.style.cursor = 'pointer' element.innerText = label element.onclick = click // Render button document.querySelector('div.sort-playlist').appendChild(element) } (function() { 'use strict'; onElementReady('ytd-playlist-sidebar-secondary-info-renderer.ytd-playlist-sidebar-renderer', false, () => { renderContainerElement(); renderButtonElement(zeLoop,'Sort All'); renderButtonElement(zeWithoutLoop,'Sort One'); }) })();