Greasy Fork is available in English.
Don't like the youtube autoplay suggestion? This script can create a queue with videos you want to play after your current video has finished!
// ==UserScript==
// @name Youtube Play Next Queue
// @version 2.5.6
// @description Don't like the youtube autoplay suggestion? This script can create a queue with videos you want to play after your current video has finished!
// @author Cpt_mathix & CY Fung
// @match https://www.youtube.com/*
// @include https://www.youtube.com/*
// @license GPL-2.0-or-later; http://www.gnu.org/licenses/gpl-2.0.txt
// @require https://cdn.jsdelivr.net/gh/culefa/JavaScript-autoComplete@19203f30f148e2d9d810ece292b987abb157bbe0/auto-complete.min.js
// @namespace https://greatest.deepsurf.us/users/16080
// @run-at document-start
// @grant none
// @noframes
// ==/UserScript==
/**
*
* The latest fixes to the script were contributed by [CY Fung](https://greatest.deepsurf.us/en/users/371179)
* Modern yt-lockup-view-model hover overlay support added.
*
**/
/* jshint esversion: 11 */
(function() {
'use strict';
// Handle Trusted Type Policy Violations
var TTP = window.TTP = {createHTML: (string, sink) => string, createScript: (string, sink) => string, createScriptURL: (string, sink) => string};
if(typeof window.isSecureContext !== 'undefined' && window.isSecureContext){
if (window.trustedTypes && window.trustedTypes.createPolicy){
if(window.trustedTypes.defaultPolicy) {
TTP = window.TTP = window.trustedTypes.defaultPolicy;
} else {
TTP = window.TTP = window.trustedTypes.createPolicy("default", TTP);
}
}
}
// ================================================================================= //
// ============================ YOUTUBE PLAY NEXT QUEUE ============================ //
// ================================================================================= //
function youtube_play_next_queue_modern() {
let script = {
version: "2.0.0",
initialized: false,
queue: null,
ytplayer: null,
queue_visible: false,
queue_rendered_observer: null,
video_renderer_observer: null,
playnext_data_observer: null,
modern_thumb_overlay_observer: null,
debug: false
};
const MODERN_QUEUE_BUTTON_CLASS = "youtube-play-next-modern-queue-button";
const insp = o => o ? (o.polymerController || o.inst || o || null) : (o || null);
document.addEventListener("load", loadScript);
document.addEventListener("DOMContentLoaded", initScript);
window.addEventListener("storage", function(event) {
if (script.initialized && /YTQUEUE-MODERN#.*#QUEUE/.test(event.key)) {
initQueue();
displayQueue();
}
});
// reload script on page change using youtube polymer fire events
window.addEventListener("yt-page-data-updated", function(event) {
if (script.debug) { console.log("# page updated #"); }
startScript(2);
initModernThumbOverlays();
});
window.addEventListener("yt-navigate-finish", function() {
initModernThumbOverlays();
});
function initScript() {
if (script.debug) { console.log("### Youtube Play Next Queue Initializing ###"); }
if (window.Polymer === undefined) {
return;
}
initQueue();
injectCSS();
// Old Polymer/ytd renderer overlays.
setInterval(addThumbOverlayClickListeners, 250);
setInterval(initThumbOverlays, 1000);
// New yt-lockup-view-model hover overlays.
initModernThumbOverlayObserver();
initModernThumbOverlays();
setInterval(initVideoPreviewThumbOverlay, 1000);
if (script.debug) { console.log("### Youtube Play Next Queue Initialized ###"); }
script.initialized = true;
}
function loadScript() {
startScript(5);
}
function startScript(retry) {
script.queue_visible = false;
if (script.initialized && isPlayerAvailable()) {
if (script.debug) { console.log("videoplayer is available"); }
if (script.debug) { console.log("ytplayer: ", script.ytplayer); }
if (script.ytplayer) {
if (script.debug) { console.log("initializing queue"); }
displayQueue();
if (script.debug) { console.log("initializing video statelistener"); }
initVideoStateListener();
if (script.debug) { console.log("initializing playnext data observer"); }
initPlayNextDataObserver();
} else {
hideQueue();
}
} else if (retry > 0) { // fix conflict with Youtube+ script
setTimeout( function() {
startScript(--retry);
}, 1000);
} else {
if (script.debug) { console.log("videoplayer is unavailable"); }
}
}
// *** LISTENERS & OBSERVERS *** //
function initVideoStateListener() {
if (!script.ytplayer.classList.contains('initialized-listeners')) {
script.ytplayer.classList.add('initialized-listeners');
script.ytplayer.addEventListener("onStateChange", handleVideoStateChanged);
} else {
if (script.debug) { console.log("statelistener already initialized"); }
}
// run handler once to make sure queue is in sync
handleVideoStateChanged(script.ytplayer.getPlayerState());
}
function handleVideoStateChanged(videoState) {
if (script.debug) { console.log("player state changed: " + videoState + "; queue empty: " + script.queue.isEmpty()); }
const PLAYING_STATE = 1;
const PAUSED_STATE = 2;
const BUFFERING_STATE = 3;
if (!script.queue.isEmpty()) {
// dequeue video from the queue if it is currently playing
if (script.ytplayer.getVideoData().video_id === script.queue.peek().id) {
script.queue.dequeue();
}
}
let currentVideoIdFromUrl = getVideoIdFromUrl(window.location.href);
if (videoState !== BUFFERING_STATE && isWatchPage() && !!currentVideoIdFromUrl && script.ytplayer.getVideoData().video_id !== currentVideoIdFromUrl && script.ytplayer.getVideoData().isListed) {
if (script.debug) { console.log("Videoplayer not correctly loaded, LoadVideoById manually"); }
script.ytplayer.loadVideoById(currentVideoIdFromUrl);
script.ytplayer.playVideo();
}
if ((videoState === PLAYING_STATE || videoState === PAUSED_STATE) && !script.queue.isEmpty() && !isPlaylist()) {
if (script.debug) { console.log("SetAsNextVideo: HandleVideoStateChanged"); }
script.queue.peek().setAsNextVideo();
}
if (videoState === PAUSED_STATE) {
// Check for annoying "are you still watching" popup
setTimeout(() => {
let button = document.querySelector('yt-confirm-dialog-renderer #confirm-button');
if (button && !!(button.offsetWidth || button.offsetHeight || button.getClientRects().length)) {
if (script.debug) { console.log("### Clicking confirm button popup ###"); }
button.click();
}
}, 1000);
}
}
function initQueueRenderedObserver() {
// Queue controls are now created directly in loadQueueItem().
// No post-render observer is needed anymore.
}
function scheduleQueueButtonInitialization() {
// Queue controls are now created directly in loadQueueItem().
}
function initPlayNextDataObserver() {
if (script.playnext_data_observer) {
script.playnext_data_observer.disconnect();
}
script.playnext_data_observer = new MutationObserver(function(mutations) {
if (!script.queue.isEmpty() && script.queue_visible) {
if (isPlaylist()) {
if (script.debug) { console.log("Play next observer triggered but found playlist, hiding current queue"); }
hideQueue();
} else {
forEach(mutations, function(mutation) {
if (mutation.attributeName === "href") {
let nextVideoId = getVideoIdFromUrl(document.querySelector('.ytp-next-button').href);
let nextQueueItem = script.queue.peek();
if (nextQueueItem.id !== nextVideoId) {
if (script.debug) { console.log("SetAsNextVideo: PlayNextDataObserver"); }
nextQueueItem.setAsNextVideo();
}
}
});
}
}
});
let observable = document.querySelector('.ytp-next-button');
if (observable) {
script.playnext_data_observer.observe(observable, { attributes: true });
}
}
// *** VIDEOPLAYER *** //
function getVideoPlayer() {
return insp(document.getElementById('movie_player'));
}
function isPlayerAvailable() {
script.ytplayer = getVideoPlayer();
return script.ytplayer !== null && !!script.ytplayer.getVideoData?.().video_id;
}
function isPlaylist() {
return !!script.ytplayer.getVideoStats().list || !document.querySelector('ytd-playlist-panel-renderer.ytd-watch-flexy[hidden]');
}
function isPlayerMinimized() {
return !!document.querySelector('ytd-miniplayer[active][enabled]');
}
function isWatchPage() {
return !!insp(document.querySelector('ytd-app'))?.__data?.isWatchPage;
}
function getVideoData(element) {
let data = insp(element)?.__data?.data || insp(element)?.data;
if (data?.content) {
return data.content.videoRenderer;
} else {
return data;
}
}
function getAutoplaySuggestion() {
return document.querySelector('ytd-compact-autoplay-renderer ytd-compact-video-renderer') || document.querySelector('#related > ytd-watch-next-secondary-results-renderer ytd-compact-video-renderer');
}
function getVideoIdFromUrl(url) {
let o = null;
try {
o = new URL(url, location.origin);
} catch (e) { }
let v = (o?.searchParams ? o.searchParams.get('v') : '') || '';
return v;
}
// *** OBJECTS *** //
// QueueItem object
class QueueItem {
constructor(id, data, type) {
this.id = id;
this.data = data;
this.type = type;
}
getVideoLength() {
if (this.data.lengthText) {
return this.data.lengthText.simpleText;
} else if (this.data.thumbnailOverlays && this.data.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer) {
return this.data.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer.text.simpleText;
} else {
return "";
}
}
getSmallestThumb() {
let thumbnails = this.data?.thumbnail?.thumbnails || [];
if (!thumbnails.length) {
return { url: "", width: 168, height: 94 };
}
return thumbnails.reduce(function (thumb, currentSmallestThumb) {
return (currentSmallestThumb.height * currentSmallestThumb.width < thumb.height * thumb.width) ? currentSmallestThumb : thumb;
});
}
getBiggestThumb() {
let thumbnails = this.data?.thumbnail?.thumbnails || [];
if (!thumbnails.length) {
return { url: "", width: 336, height: 188 };
}
return thumbnails.reduce(function (thumb, currentBiggestThumb) {
return (currentBiggestThumb.height * currentBiggestThumb.width > thumb.height * thumb.width) ? currentBiggestThumb : thumb;
});
}
setAsNextVideo() {
const PLAYING_STATE = 1;
const PAUSED_STATE = 2;
if (isPlaylist()) { return; }
let currentVideoState = script.ytplayer.getPlayerState();
if (currentVideoState !== PLAYING_STATE && currentVideoState !== PAUSED_STATE) {
return;
}
if (this.id === script.ytplayer.getVideoData().video_id) {
return;
}
if (script.debug) { console.log("changing next video"); }
let ytdPlayer = document.querySelector('ytd-player');
let watchNextData = insp(ytdPlayer)?.__data?.watchNextData;
if (watchNextData && watchNextData.contents && watchNextData.contents.twoColumnWatchNextResults && watchNextData.playerOverlays && watchNextData.playerOverlays.playerOverlayRenderer) {
if (watchNextData.contents.twoColumnWatchNextResults.playlist) {
return;
}
let watchNextEndScreenRenderer = watchNextData.playerOverlays.playerOverlayRenderer.endScreen.watchNextEndScreenRenderer;
watchNextEndScreenRenderer.results[0].endScreenVideoRenderer = this.data;
watchNextEndScreenRenderer.results[0].endScreenVideoRenderer.lengthInSeconds = hmsToSeconds(this.getVideoLength());
let playerOverlayAutoplayRenderer = watchNextData.playerOverlays.playerOverlayRenderer.autoplay.playerOverlayAutoplayRenderer;
playerOverlayAutoplayRenderer.background.thumbnails = this.data.thumbnail.thumbnails;
playerOverlayAutoplayRenderer.byline = this.data.longBylineText || this.data.shortBylineText;
playerOverlayAutoplayRenderer.nextButton.buttonRenderer.navigationEndpoint = this.data.navigationEndpoint;
playerOverlayAutoplayRenderer.videoId = this.data.videoId;
playerOverlayAutoplayRenderer.videoTitle = this.data.title.simpleText || this.data.title.runs?.[0]?.text || "";
let autoplay = watchNextData.contents.twoColumnWatchNextResults.autoplay.autoplay;
autoplay.sets[0].autoplayVideo.watchEndpoint.videoId = this.data.videoId;
let watchNextResponse = { "raw_watch_next_response" : watchNextData};
script.ytplayer.updateVideoData(watchNextResponse);
if (!script.queue_visible) {
displayQueue();
}
}
}
clearBadges() {
this.data.badges = [];
}
addBadge(label, classes = []) {
let badge = {
"metadataBadgeRenderer": {
"style": classes.join(" "),
"label": label
}
};
this.data.badges.push(badge);
}
toNode(classes = []) {
let node = document.createElement("ytd-compact-video-renderer");
node.classList.add("style-scope", "ytd-watch-next-secondary-results-renderer");
classes.forEach(className => node.classList.add(className));
node.data = this.data;
return node;
}
static fromDOM(element) {
let originalData = getVideoData(element);
if (!originalData) {
throw new Error("No video data found on element");
}
let data = Object.assign({}, originalData);
data.navigationEndpoint = data.navigationEndpoint || {};
data.navigationEndpoint.watchEndpoint = { "videoId": data.videoId };
data.navigationEndpoint.commandMetadata = { "webCommandMetadata": { "url": "/watch?v=" + data.videoId, webPageType: "WEB_PAGE_TYPE_WATCH" } };
if (!data.shortBylineText) {
let titleLabel = data.title?.accessibility?.accessibilityData?.label || data.title?.simpleText || data.title?.runs?.[0]?.text || "";
data.shortBylineText = { "runs": [ { "text": titleLabel } ] };
}
let id = data.videoId;
let type = element.tagName.toLowerCase();
if (!id) {
throw new Error("No videoId found on element data");
}
return new QueueItem(id, data, type);
}
static fromModernDOM(element) {
let link =
element.querySelector('a[href*="/watch?v="]') ||
element.querySelector('a[href*="youtu.be/"]');
if (!link) {
return null;
}
let url = new URL(link.href, location.origin);
let videoId = url.searchParams.get("v");
if (!videoId && url.hostname.includes("youtu.be")) {
videoId = url.pathname.slice(1).split("/")[0];
}
if (!videoId) {
return null;
}
let title = readModernVideoTitle(element, link);
let channel = readModernVideoChannel(element);
let thumbnailUrl = readModernThumbnailUrl(element, videoId);
let thumbnails = thumbnailUrl ? [
{ url: thumbnailUrl, width: 168, height: 94 },
{ url: thumbnailUrl, width: 336, height: 188 }
] : [
{ url: "https://i.ytimg.com/vi/" + videoId + "/mqdefault.jpg", width: 320, height: 180 }
];
let data = {
videoId: videoId,
title: {
runs: [
{
text: title,
navigationEndpoint: {
watchEndpoint: {
videoId: videoId
},
commandMetadata: {
webCommandMetadata: {
url: "/watch?v=" + videoId,
webPageType: "WEB_PAGE_TYPE_WATCH"
}
}
}
}
],
accessibility: {
accessibilityData: {
label: title
}
}
},
shortBylineText: {
runs: [
{
text: channel || "YouTube"
}
]
},
longBylineText: {
runs: [
{
text: channel || "YouTube"
}
]
},
thumbnail: {
thumbnails: thumbnails
},
lengthText: {
simpleText: readModernVideoLength(element)
},
navigationEndpoint: {
watchEndpoint: {
videoId: videoId
},
commandMetadata: {
webCommandMetadata: {
url: "/watch?v=" + videoId,
webPageType: "WEB_PAGE_TYPE_WATCH"
}
}
},
badges: [],
ownerBadges: [],
trackingParams: ""
};
return new QueueItem(videoId, data, element.tagName.toLowerCase());
}
static fromJSON(json) {
let data = json.data;
let id = json.id;
let type = json.type;
return new QueueItem(id, data, type);
}
}
// Queue object
class Queue {
constructor() {
this.queue = [];
}
get() {
return this.queue;
}
set(queue) {
this.queue = queue;
setCache("QUEUE", queue);
}
size() {
return this.queue.length;
}
isEmpty() {
return this.size() === 0;
}
contains(videoId) {
for (let i = 0; i < this.queue.length; i++) {
if (this.queue[i].id === videoId) {
return true;
}
}
return false;
}
peek() {
return this.queue[0];
}
enqueue(item) {
this.queue.push(item);
this.update();
this.show(250);
}
dequeue() {
let item = this.queue.shift();
this.update();
this.show(0);
return item;
}
remove(index) {
this.queue.splice(index, 1);
this.update();
this.show(250);
}
playNext(index) {
if (index <= 0 || index >= this.queue.length) {
return;
}
let video = this.queue.splice(index, 1)[0];
if (!video) {
return;
}
this.queue.unshift(video);
this.update();
this.show(0);
}
playNow() {
script.ytplayer.nextVideo(true);
}
update() {
setCache("QUEUE", this.get());
if (script.debug) { console.log("updated queue: ", this.get().slice()); }
}
show(delay) {
setTimeout(function() {
if (isPlayerAvailable()) {
displayQueue();
}
}, delay);
}
reset() {
this.queue = [];
this.update();
this.show(0);
}
}
// *** QUEUE *** //
function initQueue() {
script.queue = new Queue();
let cachedQueue = getCache("QUEUE");
if (cachedQueue) {
try {
cachedQueue = cachedQueue.map(queueItem => QueueItem.fromJSON(queueItem));
script.queue.set(cachedQueue);
} catch(e) {
setCache("QUEUE", script.queue.get());
}
} else {
setCache("QUEUE", script.queue.get());
}
}
function displayQueue() {
if (script.debug) { console.log("showing queue: ", script.queue.get()); }
script.queue_visible = true;
let queue = document.querySelector('#youtube-play-next-queue-renderer #contents');
if (!queue && isWatchPage()) {
let anchor = document.querySelector('#related');
if (anchor) {
let node = document.createElement("ytd-item-section-renderer");
node.classList.add("style-scope", "ytd-watch-next-secondary-results-renderer", "youtube-play-next-queue");
node.id = "youtube-play-next-queue-renderer";
window.Polymer.dom(anchor).insertBefore(node, anchor.firstChild);
queue = document.querySelector('#youtube-play-next-queue-renderer #contents');
}
} else if (!queue) {
return;
}
queue.innerHTML = "";
initQueueRenderedObserver();
if (isPlaylist()) {
if (script.debug) { console.log("Playlist found, hiding queue"); }
queue.parentNode.setAttribute("hidden", "");
script.queue_visible = false;
return;
}
if (!script.queue.isEmpty()) {
queue.parentNode.removeAttribute("hidden", "");
let autoplay = document.querySelector('ytd-compact-autoplay-renderer #contents');
if (autoplay) { autoplay.setAttribute("hidden", ""); }
forEach(script.queue.get(), function(item, index) {
try {
loadQueueItem(item, index, queue);
} catch (ex) {
console.log("Failed to display queue item", ex);
}
});
scheduleQueueButtonInitialization();
} else {
queue.parentNode.setAttribute("hidden", "");
let autoplay = document.querySelector('ytd-compact-autoplay-renderer #contents');
if (autoplay) { autoplay.removeAttribute("hidden", ""); }
if (script.debug) { console.log("SetAsNextVideo: Restore suggestion"); }
let autoplaySuggestion = getAutoplaySuggestion();
if (autoplaySuggestion) {
QueueItem.fromDOM(getAutoplaySuggestion()).setAsNextVideo();
}
script.queue_visible = false;
}
}
function loadQueueItem(item, index, queueContents) {
item.clearBadges();
if (index === 0) {
if (script.debug) { console.log("SetAsNextVideo: Load first queue item"); }
item.setAsNextVideo();
}
let wrapper = document.createElement("div");
wrapper.className = "youtube-play-next-queue-item-wrapper";
wrapper.dataset.queueIndex = String(index);
let node = item.toNode(["queue-item"]);
node.classList.add("youtube-play-next-queue-item-host");
node.dataset.queueIndex = String(index);
wrapper.appendChild(node);
wrapper.appendChild(createQueueItemControls(index));
queueContents.appendChild(wrapper);
}
function createQueueItemControls(index) {
let controls = document.createElement("div");
controls.className = "youtube-play-next-inline-queue-controls";
controls.dataset.queueIndex = String(index);
if (index === 0) {
controls.appendChild(createInlineQueueButton("Play Now", "queue-play-now", index));
controls.appendChild(createInlineQueueButton("Remove", "queue-remove", index));
} else {
controls.appendChild(createInlineQueueButton("Play Next", "queue-play-next", index));
controls.appendChild(createInlineQueueButton("Remove", "queue-remove", index));
}
return controls;
}
function createInlineQueueButton(label, className, index) {
let button = document.createElement("button");
button.type = "button";
button.className = "youtube-play-next-inline-queue-button " + className;
button.dataset.queueIndex = String(index);
button.textContent = label;
button.setAttribute("aria-label", label);
[
"pointerdown",
"pointerup",
"mousedown",
"mouseup",
"touchstart",
"touchend",
"auxclick",
"dblclick"
].forEach(function(eventName) {
button.addEventListener(eventName, stopQueueButtonEvent, true);
button.addEventListener(eventName, stopQueueButtonEvent, false);
});
button.addEventListener("click", function(event) {
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
let pos = Number(button.dataset.queueIndex || 0);
if (button.classList.contains("queue-play-now")) {
script.queue.playNow();
} else if (button.classList.contains("queue-play-next")) {
script.queue.playNext(pos);
} else if (button.classList.contains("queue-remove")) {
script.queue.remove(pos);
}
}, true);
button.addEventListener("click", function(event) {
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
}, false);
return button;
}
function hideQueue() {
script.queue_visible = false;
if (script.debug) { console.log("hiding queue"); }
let queue = document.querySelector('#youtube-play-next-queue-renderer #contents');
if (!queue) { return; }
openToast("Youtube Play Next Queue hidden while playlist, mix or native youtube queue is active.");
queue.innerHTML = "";
queue.parentNode.setAttribute("hidden", "");
}
function initRemoveQueueButton(anchor) {
let html = "<div class=\"queue-button remove-queue\">Remove Queue</div>";
anchor.innerHTML = html;
if (!anchor.querySelector(".flex-whitebox")) {
anchor.classList.add("flex-none");
anchor.insertAdjacentHTML("afterend", "<div class=\"flex-whitebox\"></div>");
}
anchor.querySelector('.remove-queue').addEventListener("click", function handler(e) {
e.preventDefault();
script.queue.reset();
this.parentNode.innerHTML = "Up next";
});
}
// *** THUMB OVERLAYS *** //
function initModernThumbOverlayObserver() {
if (script.modern_thumb_overlay_observer) {
script.modern_thumb_overlay_observer.disconnect();
}
script.modern_thumb_overlay_observer = new MutationObserver(function(mutations) {
forEach(mutations, function(mutation) {
forEach(mutation.addedNodes, function(node) {
if (!(node instanceof Element)) {
return;
}
if (node.matches && node.matches("yt-thumbnail-hover-overlay-toggle-actions-view-model")) {
injectModernThumbOverlayButton(node);
}
if (node.querySelectorAll) {
node.querySelectorAll("yt-thumbnail-hover-overlay-toggle-actions-view-model")
.forEach(injectModernThumbOverlayButton);
if (
node.matches?.("yt-lockup-view-model, .yt-lockup-view-model") ||
node.querySelector?.("yt-lockup-view-model, .yt-lockup-view-model")
) {
injectModernButtonsIntoLockupsWithoutOverlay();
}
}
});
});
});
script.modern_thumb_overlay_observer.observe(document.documentElement, {
childList: true,
subtree: true
});
}
function initModernThumbOverlays() {
document
.querySelectorAll("yt-thumbnail-hover-overlay-toggle-actions-view-model")
.forEach(injectModernThumbOverlayButton);
injectModernButtonsIntoLockupsWithoutOverlay();
}
function injectModernButtonsIntoLockupsWithoutOverlay() {
document
.querySelectorAll([
"yt-lockup-view-model",
".yt-lockup-view-model",
"ytd-item-section-renderer yt-lockup-view-model",
"ytd-watch-next-secondary-results-renderer yt-lockup-view-model"
].join(","))
.forEach(function(lockup) {
if (!(lockup instanceof Element)) {
return;
}
if (lockup.querySelector("." + MODERN_QUEUE_BUTTON_CLASS)) {
return;
}
let link =
lockup.querySelector('a[href*="/watch?v="]') ||
lockup.querySelector('a[href*="youtu.be/"]');
if (!link) {
return;
}
let thumbnailHost = findModernThumbnailHost(lockup, link);
if (!thumbnailHost) {
return;
}
thumbnailHost.classList.add("youtube-play-next-modern-thumbnail-host");
lockup.classList.add("youtube-play-next-modern-lockup-host");
bindModernLockupHoverState(lockup);
let fallbackOverlay = document.createElement("div");
fallbackOverlay.className = "youtube-play-next-modern-fallback-overlay";
injectModernThumbOverlayButton(fallbackOverlay);
thumbnailHost.appendChild(fallbackOverlay);
});
}
function bindModernLockupHoverState(lockup) {
if (!lockup || lockup.classList.contains("youtube-play-next-hover-bound")) {
return;
}
lockup.classList.add("youtube-play-next-hover-bound");
lockup.addEventListener("pointerenter", function() {
lockup.classList.add("youtube-play-next-modern-lockup-hover");
}, true);
lockup.addEventListener("mouseenter", function() {
lockup.classList.add("youtube-play-next-modern-lockup-hover");
}, true);
lockup.addEventListener("pointerleave", function() {
lockup.classList.remove("youtube-play-next-modern-lockup-hover");
}, true);
lockup.addEventListener("mouseleave", function() {
lockup.classList.remove("youtube-play-next-modern-lockup-hover");
}, true);
}
function findModernThumbnailHost(lockup, fallbackLink) {
let selectors = [
"yt-thumbnail-view-model",
".yt-thumbnail-view-model",
"yt-collection-thumbnail-view-model",
".yt-lockup-view-model-wiz__content-image",
".yt-lockup-view-model__content-image",
"a[href*='/watch?v=']"
];
for (let i = 0; i < selectors.length; i++) {
let candidate = lockup.querySelector(selectors[i]);
if (candidate && candidate.querySelector?.("img")) {
return candidate;
}
}
return fallbackLink;
}
function injectModernThumbOverlayButton(overlay) {
if (!overlay || overlay.querySelector("." + MODERN_QUEUE_BUTTON_CLASS)) {
return;
}
let button = document.createElement("button");
button.type = "button";
button.className = MODERN_QUEUE_BUTTON_CLASS;
button.title = "Queue";
button.setAttribute("aria-label", "Queue");
button.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" focusable="false" aria-hidden="true" viewBox="0 0 24 24">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"></path>
</svg>
`;
[
"pointerdown",
"pointerup",
"mousedown",
"mouseup",
"touchstart",
"touchend",
"auxclick",
"dblclick"
].forEach(function(eventName) {
button.addEventListener(eventName, stopModernThumbOverlayEvent, true);
button.addEventListener(eventName, stopModernThumbOverlayEvent, false);
});
button.addEventListener("click", handleModernThumbOverlayClick, true);
button.addEventListener("click", handleModernThumbOverlayClick, false);
overlay.appendChild(button);
}
function stopModernThumbOverlayEvent(event) {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
}
function handleModernThumbOverlayClick(event) {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
let videoRenderer = findModernVideoRenderer(event.target, event);
if (!videoRenderer) {
openToast("Could not find video item", event.target);
return;
}
let newQueueItem;
try {
newQueueItem = QueueItem.fromDOM(videoRenderer);
} catch (error) {
if (script.debug) {
console.log("QueueItem.fromDOM failed for modern lockup, using fallback", error);
}
newQueueItem = QueueItem.fromModernDOM(videoRenderer);
}
if (!newQueueItem || !newQueueItem.id) {
openToast("Could not read video data", event.target);
return;
}
enqueueQueueItemFromThumbOverlay(newQueueItem, event.target);
}
function findModernVideoRenderer(startNode, event) {
let path =
event?.composedPath?.() ||
event?.path ||
eventPathFromNode(startNode);
for (let i = 0; i < path.length; i++) {
let node = path[i];
if (!(node instanceof Element)) {
continue;
}
if (
node.matches &&
node.matches([
"yt-lockup-view-model",
".yt-lockup-view-model",
"ytd-rich-item-renderer",
"ytd-rich-grid-video-renderer",
"ytd-video-renderer",
"ytd-compact-video-renderer",
"ytd-grid-video-renderer",
"ytd-playlist-video-renderer"
].join(","))
) {
return node;
}
}
return startNode.closest([
"yt-lockup-view-model",
".yt-lockup-view-model",
"ytd-rich-item-renderer",
"ytd-rich-grid-video-renderer",
"ytd-video-renderer",
"ytd-compact-video-renderer",
"ytd-grid-video-renderer",
"ytd-playlist-video-renderer"
].join(","));
}
function eventPathFromNode(node) {
let path = [];
while (node) {
path.push(node);
node = node.parentNode || node.host;
}
path.push(window);
return path;
}
function enqueueQueueItemFromThumbOverlay(newQueueItem, target) {
if (!script.queue.contains(newQueueItem.id)) {
script.queue.enqueue(newQueueItem);
if (script.queue_visible && (isWatchPage() || isPlayerMinimized())) {
openToast("Video Added to Queue!", target);
} else if (isPlaylist()) {
openToast("Video Added to Queue! Queue is hidden while playlist, mix or native youtube queue is active", target);
} else {
openToast("Video Added to Queue! Play any video to view it.", target);
}
} else {
openToast("Video Already Queued", target);
}
}
function readModernVideoTitle(element, link) {
let selectors = [
"a[aria-label][href*='/watch?v=']",
"a[title][href*='/watch?v=']",
".yt-lockup-metadata-view-model-wiz__title",
".yt-lockup-metadata-view-model__title",
".yt-lockup-view-model-wiz__title",
"h3 a",
"#video-title",
"a[href*='/watch?v=']"
];
for (let i = 0; i < selectors.length; i++) {
let candidate = element.querySelector(selectors[i]);
let text =
candidate?.getAttribute("title") ||
candidate?.getAttribute("aria-label") ||
candidate?.textContent?.trim() ||
"";
text = cleanupModernText(text);
if (text && !/^watch later$/i.test(text) && !/^add to queue$/i.test(text)) {
return text;
}
}
let text =
link?.getAttribute("title") ||
link?.getAttribute("aria-label") ||
link?.textContent?.trim() ||
"Unknown title";
return cleanupModernText(text) || "Unknown title";
}
function readModernVideoChannel(element) {
let selectors = [
".yt-lockup-metadata-view-model-wiz__metadata a",
".yt-lockup-metadata-view-model__metadata a",
"a[href^='/@']",
"a[href^='/channel/']",
"a[href^='/c/']",
"ytd-channel-name a"
];
for (let i = 0; i < selectors.length; i++) {
let text = element.querySelector(selectors[i])?.textContent?.trim() || "";
text = cleanupModernText(text);
if (text) {
return text;
}
}
return "";
}
function readModernThumbnailUrl(element, videoId) {
let imgs = element.querySelectorAll("img");
for (let i = 0; i < imgs.length; i++) {
let src = imgs[i].currentSrc || imgs[i].src || imgs[i].getAttribute("src") || "";
if (src && !src.startsWith("data:")) {
return src;
}
}
return "https://i.ytimg.com/vi/" + videoId + "/mqdefault.jpg";
}
function cleanupModernText(text) {
return String(text || "")
.replace(/\s+/g, " ")
.replace(/ - YouTube$/, "")
.trim();
}
function readModernVideoLength(element) {
let candidates = element.querySelectorAll([
"badge-shape",
"thumbnail-badge-view-model",
".badge-shape-wiz__text",
".ytd-thumbnail-overlay-time-status-renderer",
"ytd-thumbnail-overlay-time-status-renderer",
"[aria-label]"
].join(","));
for (let i = 0; i < candidates.length; i++) {
let text =
candidates[i].textContent?.trim() ||
candidates[i].getAttribute("aria-label") ||
"";
let match = text.match(/\b\d{1,2}:\d{2}(?::\d{2})?\b/);
if (match) {
return match[0];
}
}
return "";
}
function addThumbOverlay(thumbOverlays) {
let overlay = {
"thumbnailOverlayToggleButtonRenderer": {
"ytQueue": true,
"isToggled": false,
"toggledIcon": {iconType: "ADD"},
"toggledTooltip": "Queue",
"toggledAccessibility": {
"accessibilityData": {
"label": "Queue"
}
},
"untoggledIcon": {iconType: "ADD"},
"untoggledTooltip": "Queue",
"untoggledAccessibility": {
"accessibilityData": {
"label": "Queue"
}
}
}
};
thumbOverlays.push(overlay);
}
function hasThumbOverlay(videoOverlays) {
for(let i = 0; i < videoOverlays.length; i++) {
if (videoOverlays[i].thumbnailOverlayToggleButtonRenderer && videoOverlays[i].thumbnailOverlayToggleButtonRenderer.ytQueue) {
return true;
}
}
return false;
}
function initThumbOverlay(videoRenderer) {
let videoData = getVideoData(videoRenderer);
if (videoData && videoData.thumbnailOverlays && !hasThumbOverlay(videoData.thumbnailOverlays) && !videoData.upcomingEventData) {
addThumbOverlay(videoData.thumbnailOverlays);
} else if (videoData && videoData?.contentImage?.thumbnailViewModel?.overlays && !hasThumbOverlay(videoData.contentImage.thumbnailViewModel.overlays)) {
addThumbOverlay(videoData.contentImage.thumbnailViewModel.overlays);
}
}
function initThumbOverlays() {
let videoRenderers = document.querySelectorAll('ytd-compact-video-renderer, ytd-grid-video-renderer, ytd-video-renderer, ytd-playlist-video-renderer, ytd-rich-grid-video-renderer, ytd-rich-item-renderer');
forEach(videoRenderers, function(videoRenderer) {
initThumbOverlay(videoRenderer);
detectPeekAPic(videoRenderer);
});
}
function detectPeekAPic(videoRenderer) {
var peekAPicStoryboard = videoRenderer.querySelector(".yt-peek-a-pic-storyboard");
if (!peekAPicStoryboard) return;
videoRenderer.classList.add("peek-a-pic-enabled");
}
function addThumbOverlayClickListeners() {
let overlays = document.querySelectorAll('ytd-thumbnail-overlay-toggle-button-renderer > yt-icon');
forEach(overlays, function(overlay) {
overlay.removeEventListener("click", handleThumbOverlayClick);
if (overlay.parentNode.getAttribute("aria-label") !== "Queue") {
return;
}
overlay.addEventListener("click", handleThumbOverlayClick);
});
}
function handleThumbOverlayClick(event) {
event.stopPropagation(); event.preventDefault();
let path = event.path || (event.composedPath && event.composedPath()) || event._composedPath;
for(let i = 0; i < path.length; i++) {
let tagNames = ["YTD-COMPACT-VIDEO-RENDERER", "YTD-GRID-VIDEO-RENDERER", "YTD-VIDEO-RENDERER", "YTD-PLAYLIST-VIDEO-RENDERER", "YTD-RICH-GRID-VIDEO-RENDERER", "YTD-RICH-ITEM-RENDERER"];
if (tagNames.includes(path[i].tagName)) {
let newQueueItem = QueueItem.fromDOM(path[i]);
enqueueQueueItemFromThumbOverlay(newQueueItem, event.target);
break;
}
}
}
// *** VIDEO PREVIEW THUMB OVERLAYS *** //
function initVideoPreviewThumbOverlay() {
let videoPreview = document.querySelector('ytd-video-preview');
if (videoPreview) {
let previewControls = videoPreview.querySelector(".ytp-inline-preview-controls");
if (previewControls) {
let queueControl = previewControls.querySelector("#queue-control-add");
if (!queueControl) {
previewControls.insertAdjacentHTML("beforeend", '<button id="queue-control-add" class="ytp-subtitles-button ytp-button" aria-keyshortcuts="m" data-title-no-tooltip="Queue" aria-label="Queue" title="Queue"><svg xmlns="http://www.w3.org/2000/svg" focusable="false" fill-opacity="1" width="100%" height="100%" viewBox="-5 -7 36 36"><path d="M20 12h-8v8h-1v-8H3v-1h8V3h1v8h8v1z" fill="#fff"></path></svg></button>');
previewControls.querySelector("#queue-control-add").addEventListener("click", handlePreviewThumbOverlayClick);
}
}
}
}
function handlePreviewThumbOverlayClick(event) {
event.stopPropagation(); event.preventDefault();
let videoPreview = document.querySelector('ytd-video-preview');
if (videoPreview) {
let mediaRenderer = insp(videoPreview).__data.opts.mediaRenderer;
let newQueueItem = QueueItem.fromDOM(mediaRenderer);
enqueueQueueItemFromThumbOverlay(newQueueItem, event.target);
}
}
// *** BUTTONS *** //
function initQueueButtons() {
// Queue controls are now created directly in loadQueueItem().
}
function initQueueButtonAction(className, btnAction) {
// Legacy badge-button initializer. Kept as a no-op for safety.
}
function stopQueueButtonEvent(event) {
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
}
// *** POPUPS *** //
function openToast(text, target) {
let openPopupAction = {
"openPopupAction": {
"popup": {
"notificationActionRenderer": {
"responseText": {simpleText: text},
"trackingParams": ""
}
},
"popupType": "TOAST"
}
};
let popupContainer = document.querySelector('ytd-popup-container');
if (!popupContainer) {
console.log(text);
return;
}
if (popupContainer.handleOpenPopupAction_) {
popupContainer.handleOpenPopupAction_(openPopupAction, target || document.documentElement);
} else {
popupContainer.handleOpenPopupAction(openPopupAction, target || document.documentElement);
}
}
// *** LOCALSTORAGE *** //
function getCache(key) {
return JSON.parse(localStorage.getItem("YTQUEUE-MODERN#" + script.version + "#" + key));
}
function deleteCache(key) {
localStorage.removeItem("YTQUEUE-MODERN#" + script.version + "#" + key);
}
function setCache(key, value) {
localStorage.setItem("YTQUEUE-MODERN#" + script.version + "#" + key, JSON.stringify(value));
}
// *** CSS *** //
function injectCSS() {
let css = `
#youtube-play-next-queue-renderer {
height: 310px;
position: sticky;
border: 1px solid var(--yt-spec-10-percent-layer);
padding: 5px 0 0 5px;
margin-bottom: 16px;
overflow-y: visible;
overflow-x: hidden;
resize: vertical;
}
ytd-compact-autoplay-renderer > #contents { padding-bottom: 8px }
.queue-item { margin-top: 0px !important; margin-bottom: 6px !important; }
.queue-item #metadata-line { display: none; }
.queue-button {
min-height: 24px;
line-height: 1.7rem !important;
padding: 5px 8px !important;
margin: 0 !important;
cursor: pointer !important;
z-index: 10000 !important;
position: relative !important;
pointer-events: auto !important;
border: 0 !important;
border-radius: 2px !important;
background-color: var(--yt-spec-10-percent-layer);
color: var(--yt-spec-text-secondary);
font: inherit !important;
appearance: none !important;
-webkit-appearance: none !important;
}
#youtube-play-next-queue-renderer .youtube-play-next-queue-item-wrapper {
position: relative !important;
min-height: 104px !important;
margin-bottom: 8px !important;
overflow: visible !important;
}
#youtube-play-next-queue-renderer .youtube-play-next-queue-item-wrapper > .queue-item,
#youtube-play-next-queue-renderer .youtube-play-next-queue-item-wrapper > .queue-item.youtube-play-next-queue-item-host {
position: relative !important;
margin-bottom: 0 !important;
}
#youtube-play-next-queue-renderer .queue-item .youtube-play-next-queue-actions,
#youtube-play-next-queue-renderer .queue-item .youtube-play-next-queue-positioned-actions {
display: none !important;
}
#youtube-play-next-queue-renderer .youtube-play-next-queue-item-wrapper > .youtube-play-next-inline-queue-controls {
position: absolute !important;
left: 176px !important;
right: 4px !important;
top: 66px !important;
bottom: auto !important;
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
align-items: center !important;
justify-content: flex-start !important;
gap: 6px !important;
z-index: 2147483647 !important;
pointer-events: auto !important;
opacity: 1 !important;
visibility: visible !important;
overflow: visible !important;
}
#youtube-play-next-queue-renderer .youtube-play-next-queue-item-wrapper .youtube-play-next-inline-queue-button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
min-width: auto !important;
width: auto !important;
height: 24px !important;
min-height: 24px !important;
max-height: 24px !important;
line-height: 16px !important;
padding: 4px 8px !important;
margin: 0 !important;
cursor: pointer !important;
z-index: 2147483647 !important;
position: relative !important;
pointer-events: auto !important;
border: 0 !important;
border-radius: 3px !important;
background: rgba(0, 0, 0, 0.78) !important;
color: #fff !important;
font-family: Roboto, Arial, sans-serif !important;
font-size: 12px !important;
font-weight: 500 !important;
text-align: center !important;
white-space: nowrap !important;
opacity: 1 !important;
visibility: visible !important;
appearance: none !important;
-webkit-appearance: none !important;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35) !important;
}
#youtube-play-next-queue-renderer .youtube-play-next-queue-item-wrapper .youtube-play-next-inline-queue-button:hover {
background: rgba(0, 0, 0, 0.92) !important;
color: #fff !important;
}
#youtube-play-next-queue-renderer .youtube-play-next-queue-item-wrapper .youtube-play-next-inline-queue-button * {
pointer-events: none !important;
}
.queue-button.queue-play-now, .queue-button.queue-play-next { margin: 5px 3px 5px 0 !important; }
.queue-button:hover { box-shadow: 0px 0px 3px black; }
[dark] .queue-button:hover { box-shadow: 0px 0px 3px white; }
ytd-thumbnail-overlay-toggle-button-renderer[aria-label=Queue] { bottom: 0; top: auto !important; right: auto; left: 0; }
.peek-a-pic-enabled ytd-thumbnail-overlay-toggle-button-renderer[aria-label=Queue] { bottom: 25%; top: auto !important; right: auto; left: 0; }
ytd-thumbnail-overlay-toggle-button-renderer[aria-label=Queue] #label-container { left: 28px !important; right: auto !important; }
ytd-thumbnail-overlay-toggle-button-renderer[aria-label=Queue] #label-container > #label { padding: 0 8px 0 2px !important; }
ytd-thumbnail-overlay-toggle-button-renderer[aria-label=Queue] paper-tooltip { right: -70px !important; left: auto !important }
.queue-item ytd-thumbnail-overlay-toggle-button-renderer[aria-label=Queue] { display: none; }
ytd-thumbnail-overlay-toggle-button-renderer[aria-label=Queued] { display: none; }
yt-thumbnail-hover-overlay-toggle-actions-view-model {
position: absolute !important;
inset: 0 !important;
pointer-events: none !important;
}
yt-thumbnail-hover-overlay-toggle-actions-view-model > * {
pointer-events: auto !important;
}
.youtube-play-next-modern-queue-button {
position: absolute !important;
left: 6px !important;
bottom: 6px !important;
top: auto !important;
right: auto !important;
width: 34px !important;
height: 34px !important;
min-width: 34px !important;
min-height: 34px !important;
max-width: 34px !important;
max-height: 34px !important;
box-sizing: border-box !important;
padding: 6px !important;
margin: 0 !important;
border: 0 !important;
border-radius: 50% !important;
background-color: rgba(0, 0, 0, 0.78) !important;
color: #fff !important;
cursor: pointer !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
z-index: 2147483647 !important;
pointer-events: auto !important;
opacity: 1 !important;
appearance: none !important;
-webkit-appearance: none !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.55) !important;
overflow: visible !important;
}
.youtube-play-next-modern-queue-button:hover {
background-color: rgba(0, 0, 0, 0.92) !important;
}
.youtube-play-next-modern-thumbnail-host {
position: relative !important;
}
.youtube-play-next-modern-lockup-host,
.youtube-play-next-modern-thumbnail-host {
position: relative !important;
}
.youtube-play-next-modern-fallback-overlay {
position: absolute !important;
inset: 0 !important;
z-index: 2147483647 !important;
pointer-events: none !important;
opacity: 1 !important;
visibility: visible !important;
display: block !important;
overflow: visible !important;
}
.youtube-play-next-modern-fallback-overlay .youtube-play-next-modern-queue-button {
opacity: 0 !important;
visibility: visible !important;
transition: opacity 120ms ease-in-out !important;
}
.youtube-play-next-modern-thumbnail-host:hover .youtube-play-next-modern-fallback-overlay .youtube-play-next-modern-queue-button,
.youtube-play-next-modern-lockup-host:hover .youtube-play-next-modern-fallback-overlay .youtube-play-next-modern-queue-button,
.youtube-play-next-modern-lockup-host.youtube-play-next-modern-lockup-hover .youtube-play-next-modern-fallback-overlay .youtube-play-next-modern-queue-button,
.youtube-play-next-modern-fallback-overlay:hover .youtube-play-next-modern-queue-button,
.youtube-play-next-modern-fallback-overlay:focus-within .youtube-play-next-modern-queue-button {
opacity: 1 !important;
visibility: visible !important;
display: flex !important;
}
.youtube-play-next-modern-fallback-overlay .youtube-play-next-modern-queue-button {
position: absolute !important;
left: 6px !important;
bottom: 6px !important;
top: auto !important;
right: auto !important;
pointer-events: auto !important;
}
.youtube-play-next-modern-queue-button svg {
width: 22px !important;
height: 22px !important;
min-width: 22px !important;
min-height: 22px !important;
display: block !important;
fill: #fff !important;
color: #fff !important;
pointer-events: none !important;
flex: 0 0 auto !important;
overflow: visible !important;
}
`;
let style = document.createElement("style");
style.type = "text/css";
if (style.styleSheet){
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
(document.body || document.head || document.documentElement).appendChild(style);
}
// *** FUNCTIONALITY *** //
function forEach(array, callback, scope) {
for (let i = 0; i < array.length; i++) {
callback.call(scope, array[i], i);
}
}
function forEachReverse(array, callback, scope) {
for (let i = array.length - 1; i >= 0; i--) {
callback.call(scope, array[i], i);
}
}
function hmsToSeconds(str) {
if (!str) {
return 0;
}
let p = str.split(":"),
s = 0, m = 1;
while (p.length > 0) {
s += m * parseInt(p.pop(), 10);
m *= 60;
}
return s;
}
}
function youtube_search_while_watching_video() {
let script = {
initialized: false,
ytplayer: null,
search_bar: null,
search_autocomplete: null,
search_suggestions: [],
searched: false,
debug: false
};
const insp = o => o ? (o.polymerController || o.inst || o || null) : (o || null);
document.addEventListener("DOMContentLoaded", initScript);
window.addEventListener("yt-page-data-updated", function(event) {
if (script.debug) { console.log("# page updated #"); }
cleanupSearch();
startScript(2);
});
function initScript() {
if (script.debug) { console.log("### Youtube Search While Watching Video Initializing ###"); }
initSearch();
injectCSS();
if (script.debug) { console.log("### Youtube Search While Watching Video Initialized ###"); }
script.initialized = true;
startScript(5);
}
function startScript(retry) {
if (script.initialized && isPlayerAvailable()) {
if (script.debug) { console.log("videoplayer is available"); }
if (script.debug) { console.log("ytplayer: ", script.ytplayer); }
if (script.ytplayer) {
try {
if (script.debug) { console.log("initializing search"); }
loadSearch();
} catch (error) {
console.log("Failed to initialize search: ", (script.debug) ? error : error.message);
}
}
} else if (retry > 0) {
setTimeout( function() {
startScript(--retry);
}, 1000);
} else {
if (script.debug) { console.log("videoplayer is unavailable"); }
}
}
// *** VIDEOPLAYER *** //
function getVideoPlayer() {
return insp(document.getElementById('movie_player'));
}
function isPlayerAvailable() {
script.ytplayer = getVideoPlayer();
return script.ytplayer !== null && script.ytplayer.getVideoData?.().video_id;
}
// *** SEARCH *** //
function initSearch() {
window.suggestions_callback = suggestionsCallback;
}
function loadSearch() {
let playlistOrLiveSearchBar = document.querySelector('#suggestions-search.playlist-or-live');
if (playlistOrLiveSearchBar) { playlistOrLiveSearchBar.remove(); }
let searchbar = document.getElementById('suggestions-search');
if (!searchbar) {
createSearchBar();
} else {
searchbar.value = "";
}
script.searched = false;
}
function cleanupSearch() {
if (script.search_autocomplete) {
script.search_autocomplete.destroy();
}
cleanupSuggestionRequests();
}
function createSearchBar() {
let anchor, html;
anchor = document.querySelector('ytd-compact-autoplay-renderer > #contents');
if (anchor) {
html = "<input id=\"suggestions-search\" type=\"search\" placeholder=\"Search\">";
anchor.insertAdjacentHTML("afterend", html);
} else {
anchor = document.querySelector('#related > ytd-watch-next-secondary-results-renderer');
if (anchor) {
html = "<input id=\"suggestions-search\" class=\"playlist-or-live\" type=\"search\" placeholder=\"Search\">";
anchor.insertAdjacentHTML("beforebegin", html);
}
}
let searchBar = document.getElementById('suggestions-search');
if (searchBar) {
script.search_bar = searchBar;
script.search_autocomplete = new window.autoComplete({
selector: '#suggestions-search',
minChars: 1,
delay: 100,
source: function(term, suggest) {
script.search_suggestions = {
query: term,
suggest: suggest
};
searchSuggestions(term);
},
onSelect: function(event, term, item) {
prepareNewSearchRequest(term);
}
});
script.search_bar.addEventListener("keyup", function(event) {
if (this.value === "") {
resetSuggestions();
}
});
script.search_bar.addEventListener("keydown", function(event) {
const ENTER = 13;
if (this.value.trim() !== "" && (event.key == "Enter" || event.keyCode === ENTER)) {
prepareNewSearchRequest(this.value.trim());
}
});
script.search_bar.addEventListener("search", function(event) {
if(this.value === "") {
script.search_bar.blur();
script.search_suggestions = [];
resetSuggestions();
}
});
script.search_bar.addEventListener("focus", function(event) {
this.select();
});
}
}
function suggestionsCallback(data) {
if (script.debug) { console.log(data); }
let query = data[0];
if (query !== script.search_suggestions.query) {
return;
}
let raw = data[1];
let suggestions = raw.map(function(array) {
return array[0];
});
script.search_suggestions.suggest(suggestions);
}
function searchSuggestions(query) {
const GeoLocation = window.yt.config_.INNERTUBE_CONTEXT_GL;
const HostLanguage = window.yt.config_.INNERTUBE_CONTEXT_HL;
if (script.debug) { console.log("suggestion request send", query); }
let scriptElement = document.createElement("script");
scriptElement.type = "text/javascript";
scriptElement.className = "suggestion-request";
scriptElement.src = "https://clients1.google.com/complete/search?client=youtube&hl=" + HostLanguage + "&gl=" + GeoLocation + "&gs_ri=youtube&ds=yt&q=" + encodeURIComponent(query) + "&callback=suggestions_callback";
(document.body || document.head || document.documentElement).appendChild(scriptElement);
}
function cleanupSuggestionRequests() {
let requests = document.getElementsByClassName('suggestion-request');
forEachReverse(requests, function(request) {
request.remove();
});
}
function prepareNewSearchRequest(value) {
if (script.debug) { console.log("searching for " + value); }
script.search_bar.blur();
script.search_suggestions = [];
cleanupSuggestionRequests();
sendSearchRequest("https://www.youtube.com/results?pbj=1&search_query=" + encodeURIComponent(value));
}
function sendSearchRequest(url) {
let xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
processSearch(xmlHttp.responseText);
}
};
xmlHttp.open("GET", url, true);
xmlHttp.setRequestHeader("x-youtube-client-name", window.yt.config_.INNERTUBE_CONTEXT_CLIENT_NAME);
xmlHttp.setRequestHeader("x-youtube-client-version", window.yt.config_.INNERTUBE_CONTEXT_CLIENT_VERSION);
xmlHttp.setRequestHeader("x-youtube-client-utc-offset", new Date().getTimezoneOffset() * -1);
if (window.yt.config_.ID_TOKEN) {
xmlHttp.setRequestHeader("x-youtube-identity-token", window.yt.config_.ID_TOKEN);
}
xmlHttp.send(null);
}
function processSearch(responseText) {
try {
let data = JSON.parse(responseText);
let found = searchJson(data, (key, value) => {
if (key === "itemSectionRenderer") {
if (script.debug) { console.log(value.contents); }
let succeeded = createSuggestions(value.contents);
return succeeded;
}
return false;
});
if (!found) {
alert("The search request was succesful but the script was unable to parse the results");
}
} catch (error) {
alert("Failed to retrieve search data, sorry!\nError message: " + error.message + "\nSearch response: " + responseText);
}
}
function searchJson(json, func) {
let found = false;
for (let item in json) {
found = func(item, json[item]);
if (found) { break; }
if (json[item] !== null && typeof(json[item]) == "object") {
found = searchJson(json[item], func);
if (found) { break; }
}
}
return found;
}
// *** HTML & CSS *** //
function createSuggestions(data) {
if (data.length < 10) {
return false;
}
let hidden_continuation_item_renderer;
let watchRelated = document.querySelector('#related ytd-watch-next-secondary-results-renderer #items ytd-item-section-renderer #contents') || document.querySelector('#related ytd-watch-next-secondary-results-renderer #items');
forEachReverse(watchRelated.children, function(item) {
if (item.tagName === "YTD-CONTINUATION-ITEM-RENDERER") {
item.setAttribute("hidden", "");
hidden_continuation_item_renderer = item;
} else if (item.tagName !== "YTD-COMPACT-AUTOPLAY-RENDERER") {
item.remove();
}
});
forEach(data, function(videoData) {
if (videoData.videoRenderer || videoData.compactVideoRenderer) {
window.Polymer.dom(watchRelated).appendChild(videoQueuePolymer(videoData.videoRenderer || videoData.compactVideoRenderer, "ytd-compact-video-renderer"));
} else if (videoData.radioRenderer || videoData.compactRadioRenderer) {
window.Polymer.dom(watchRelated).appendChild(videoQueuePolymer(videoData.radioRenderer || videoData.compactRadioRenderer, "ytd-compact-radio-renderer"));
} else if (videoData.playlistRenderer || videoData.compactPlaylistRenderer) {
window.Polymer.dom(watchRelated).appendChild(videoQueuePolymer(videoData.playlistRenderer || videoData.compactPlaylistRenderer, "ytd-compact-playlist-renderer"));
}
});
if (hidden_continuation_item_renderer) {
watchRelated.appendChild(hidden_continuation_item_renderer);
}
script.searched = true;
return true;
}
function resetSuggestions() {
if (script.searched) {
let itemSectionRenderer = document.querySelector('#related ytd-watch-next-secondary-results-renderer #items ytd-item-section-renderer') || document.querySelector("#related ytd-watch-next-secondary-results-renderer");
let data = insp(itemSectionRenderer).__data.data;
createSuggestions(data.contents || data.results);
let continuation = itemSectionRenderer.querySelector('ytd-continuation-item-renderer[hidden]');
if (continuation) {
continuation.removeAttribute("hidden");
}
}
script.searched = false;
}
function videoQueuePolymer(videoData, type) {
let node = document.createElement(type);
node.classList.add("style-scope", "ytd-watch-next-secondary-results-renderer", "yt-search-generated");
node.data = videoData;
return node;
}
function injectCSS() {
let css = `
.autocomplete-suggestions {
text-align: left; cursor: default; border: 1px solid var(--ytd-searchbox-legacy-border-color); border-top: 0; background: var(--ytd-searchbox-background);
position: absolute; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box; box-shadow: -1px 1px 3px rgba(0,0,0,.1);
left: auto; top: auto; width: 100%; margin: 0; contain: content;
}
.autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.22em; color: var(--ytd-searchbox-text-color); }
.autocomplete-suggestion b { font-weight: normal; color: #b31217; }
.autocomplete-suggestion.selected { background: #ddd; }
[dark] .autocomplete-suggestion.selected { background: #333; }
autocomplete-holder {
overflow: visible; position: absolute; left: auto; top: auto; width: 100%; height: 0; z-index: 9999; box-sizing: border-box; margin:0; padding:0; border:0; contain: size layout;
}
ytd-compact-autoplay-renderer { padding-bottom: 0px; }
#suggestions-search {
outline: none; width: 100%; padding: 6px 5px; margin-bottom: 16px;
border: 1px solid var(--ytd-searchbox-legacy-border-color); border-radius: 2px 0 0 2px;
box-shadow: inset 0 1px 2px var(--ytd-searchbox-legacy-border-shadow-color);
color: var(--ytd-searchbox-text-color); background-color: var(--ytd-searchbox-background);
}
`;
let style = document.createElement("style");
style.type = "text/css";
if (style.styleSheet){
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
(document.body || document.head || document.documentElement).appendChild(style);
}
// *** FUNCTIONALITY *** //
function forEach(array, callback, scope) {
for (let i = 0; i < array.length; i++) {
callback.call(scope, array[i], i);
}
}
function forEachReverse(array, callback, scope) {
for (let i = array.length - 1; i >= 0; i--) {
callback.call(scope, array[i], i);
}
}
}
// ================================================================================= //
// =============================== INJECTING SCRIPTS =============================== //
// ================================================================================= //
document.documentElement.setAttribute("youtube-play-next-queue", "");
if (!document.getElementById("autocomplete_script")) {
let autoCompleteScript = document.createElement('script');
autoCompleteScript.id = "autocomplete_script";
autoCompleteScript.type = 'text/javascript';
autoCompleteScript.textContent = 'window.autoComplete = ' + autoComplete + ';';
(document.body || document.head || document.documentElement).appendChild(autoCompleteScript);
}
if (!document.getElementById("play_next_queue_script")) {
let playNextQueueScript = document.createElement('script');
playNextQueueScript.id = "play_next_queue_script";
playNextQueueScript.type = 'text/javascript';
playNextQueueScript.textContent = '('+ youtube_play_next_queue_modern +')();';
(document.body || document.head || document.documentElement).appendChild(playNextQueueScript);
}
if (!document.getElementById("search_while_watching_video")) {
let searchWhileWatchingVideoScript = document.createElement('script');
searchWhileWatchingVideoScript.id = "search_while_watching_video";
searchWhileWatchingVideoScript.type = 'text/javascript';
searchWhileWatchingVideoScript.textContent = '('+ youtube_search_while_watching_video +')();';
(document.body || document.head || document.documentElement).appendChild(searchWhileWatchingVideoScript);
}
})();