Last active
October 20, 2021 23:44
-
-
Save elazar/c0e1c7d18aa0c23848c41ac632e5bd6e to your computer and use it in GitHub Desktop.
Userscript to add sort options for video length and channel to YouTube
This file contains 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
const getVideoList = () => document.getElementById("contents"); | |
const getVideoCount = () => { | |
return Number( | |
document | |
.getElementById("stats") | |
.getElementsByClassName("yt-formatted-string")[0] | |
.textContent | |
); | |
} | |
const getVideos = () => { | |
const list = getVideoList(); | |
const videos = Array.from( | |
list.getElementsByTagName("ytd-playlist-video-renderer") | |
); | |
return videos; | |
} | |
const scroll = async () => { | |
const videoCount = getVideoCount(); | |
const videoList = getVideoList(); | |
do { | |
window.scrollTo(0, videoList.scrollHeight); | |
await new Promise(resolve => setTimeout(resolve, 500)); | |
} while (getVideos().length < videoCount); | |
window.scrollTo(0, 0); | |
}; | |
const getOption = (text, callback) => { | |
const html = ` | |
<a class="yt-simple-endpoint style-scope yt-dropdown-menu" tabindex="-1" aria-selected="false"> | |
<tp-yt-paper-item class="style-scope yt-dropdown-menu" role="option" tabindex="0" aria-disabled="false"> | |
<!--css-build:shady--> | |
<tp-yt-paper-item-body class="style-scope yt-dropdown-menu"> | |
<!--css-build:shady--> | |
<div class="item style-scope yt-dropdown-menu">${text}</div> | |
<div secondary="" id="subtitle" class="style-scope yt-dropdown-menu" hidden=""> | |
</div> | |
</tp-yt-paper-item-body> | |
<yt-reload-continuation class="style-scope yt-dropdown-menu"> | |
</yt-reload-continuation> | |
</tp-yt-paper-item> | |
</a> | |
`; | |
const document = new DOMParser().parseFromString(html, "text/html"); | |
const element = document.activeElement.firstChild; | |
element.onclick = callback; | |
return element; | |
}; | |
const sortVideos = (callback) => { | |
const list = getVideoList(); | |
const videos = getVideos(); | |
videos.forEach((video) => video.parentNode.removeChild(video)); | |
videos.sort(callback); | |
videos.forEach((video) => list.appendChild(video)); | |
}; | |
const getTime = (video) => { | |
const text = video.getElementsByTagName("span")[0].textContent.trim(); | |
const numbers = text.split(":").map(Number); | |
numbers.reverse(); | |
numbers[1] *= 60; | |
if (numbers[2]) { | |
numbers[2] *= 3600; | |
} | |
const time = numbers.reduce((previous, current) => previous + current, 0); | |
return time; | |
}; | |
const getChannelName = (video) => { | |
const name = video.getElementsByTagName("ytd-channel-name")[0]; | |
const a = name.getElementsByTagName("a")[0]; | |
const text = a.textContent.trim(); | |
return text; | |
}; | |
scroll().then(() => { | |
const menu = Array.from( | |
document.getElementsByTagName("tp-yt-paper-listbox") | |
) | |
.find(el => el.id === "menu"); | |
menu.prepend( | |
getOption("Length (shortest)", () => { | |
sortVideos((a, b) => getTime(a) - getTime(b)); | |
}) | |
); | |
menu.prepend( | |
getOption("Length (longest)", () => { | |
sortVideos((a, b) => getTime(b) - getTime(a)) | |
}) | |
); | |
menu.prepend( | |
getOption("Channel (A-Z)", () => { | |
sortVideos((a, b) => getChannelName(a) < getChannelName(b) ? -1 : 1); | |
}) | |
); | |
menu.prepend( | |
getOption("Channel (Z-A)", () => { | |
sortVideos((a, b) => getChannelName(b) < getChannelName(a) ? -1 : 1); | |
}) | |
); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment