您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.0.2 // @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 // @match https://www.youtube.com/* // @license GPL-2.0-or-later; http://www.gnu.org/licenses/gpl-2.0.txt // @require https://cdnjs.cloudflare.com/ajax/libs/JavaScript-autoComplete/1.0.4/auto-complete.min.js // @namespace https://greatest.deepsurf.us/users/16080 // @run-at document-start // @grant none // @noframes // ==/UserScript== /*jshint esversion: 6 */ (function() { 'use strict'; // ================================================================================= // // ======================= YOUTUBE PLAY NEXT QUEUE (CLASSIC) ======================= // // ================================================================================= // function youtube_play_next_queue_classic() { var script = { initialized: false, version: "2.0.0", queue: null, ytplayer: null, autoplay_suggestion: null, addButtonGenerator: null, removeButtonGenerator: null, playNextButtonGenerator: null, playNowButtonGenerator: null, suggestion_observer: null, playnext_data_observer: null, debug: false }; document.addEventListener("DOMContentLoaded", initScript); // reload script on page change using youtube spf events (http://youtube.github.io/js/documentation/events/) window.addEventListener("spfdone", function(e) { if (script.debug) { console.log("# page updated (classic) #"); } if (script.initialized) { startScript(2); } else { initScript(); startScript(5); } }); function initScript() { if (script.debug) { console.log("Youtube Play Next Queue Initializing"); } if (window.Polymer !== undefined) { return; } initQueue(); initButtonGenerators(); injectCSS(); if (script.debug) { console.log("### Classic youtube loaded ###"); } script.initialized = true; startScript(5); } function startScript(retry) { if (isPlayerAvailable()) { if (script.debug) { console.log("videoplayer is available"); } if (script.debug) { console.log("ytplayer: ", script.ytplayer); } if (script.ytplayer && !isPlaylist()) { if (getVideoInfoFromUrl(document.location.href, "t") == "0s") { script.ytplayer.seekTo(0); } if (script.debug) { console.log("initializing queue"); } loadQueue(); if (script.debug) { console.log("initializing queue add buttons"); } initAddQueueButtons(); if (script.debug) { console.log("initializing video statelistener"); } initVideoStateListener(); if (script.debug) { console.log("initializing suggestion observer"); } initSuggestionObserver(); if (script.debug) { console.log("initializing play next observer"); } initPlayNextDataObserver(); } else if (retry > 0) { // fix conflict with Youtube+ script setTimeout( function() { startScript(--retry); }, 1000); } } else { if (script.debug) { console.log("videoplayer is unavailable"); } } } // *** LISTENERS *** // function initVideoStateListener() { if (!script.ytplayer.classList.contains('initialized-listeners')) { script.ytplayer.classList.add('initialized-listeners'); script.ytplayer.addEventListener("onStateChange", handleVideoStateChanged); // run handler once to make sure queue is in sync handleVideoStateChanged(script.ytplayer.getPlayerState()); } else { if (script.debug) { console.log("statelistener already initialized"); } } } function handleVideoStateChanged(videoState) { if (script.debug) { console.log("player state changed: " + videoState + "; queue empty: " + script.queue.isEmpty()); } const FINISHED_STATE = 0; const PLAYING_STATE = 1; const PAUSED_STATE = 2; const BUFFERING_STATE = 3; const CUED_STATE = 5; 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(); } } if ((videoState === PLAYING_STATE || videoState === PAUSED_STATE) && !script.queue.isEmpty()) { changeNextVideo(script.queue.peek()); } } function initSuggestionObserver() { if (script.suggestion_observer) { script.suggestion_observer.disconnect(); } script.suggestion_observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { console.log(mutation); forEach(mutation.addedNodes, function(addedNode) { if (!addedNode.classList.contains('processed-buttons')) { initAddQueueButton(addedNode); } }); }); }); var observables = document.querySelectorAll('#watch-related, #watch-more-related'); forEach(observables, function(observable) { script.suggestion_observer.observe(observable, { childList: true }); }); } function initPlayNextDataObserver() { if (script.playnext_data_observer) { script.playnext_data_observer.disconnect(); } // If youtube updates the videoplayer with the autoplay suggestion, // replace it with the next video in our queue. script.playnext_data_observer = new MutationObserver(function(mutations) { if (!script.queue.isEmpty() && !isPlaylist() && !isLivePlayer()) { forEach(mutations, function(mutation) { if (mutation.attributeName === "href") { let nextVideoId = getVideoInfoFromUrl(document.querySelector('.ytp-next-button').href, "v"); let nextQueueItem = script.queue.peek(); if (nextQueueItem.id !== nextVideoId) { changeNextVideo(nextQueueItem); } } }); } }); let observable = document.querySelector('.ytp-next-button'); script.playnext_data_observer.observe(observable, { attributes: true }); } // *** VIDEOPLAYER *** // function getVideoPlayer() { return 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; } function isLivePlayer() { return script.ytplayer.getVideoData().isLive; } function isPlayerFullscreen() { return script.ytplayer.classList.contains('ytp-fullscreen'); } function getVideoInfoFromUrl(url, info) { if (url.indexOf("?") === -1) { return null; } let urlVariables = url.split("?")[1].split("&"); for(var i = 0; i < urlVariables.length; i++) { var varName = urlVariables[i].split("="); if (varName[0] === info) { return varName[1] === undefined ? null : varName[1]; } } } // play next video behavior depending on if you're watching fullscreen function playNextVideo(nextVideoId) { if (script.debug) { console.log("playing next song:", nextVideoId); } if (isPlayerFullscreen()) { script.ytplayer.loadVideoById(nextVideoId, 0); } else { window.spf.navigate("https://www.youtube.com/watch?v=" + nextVideoId + "&t=0s"); } } // reconfigure the next video button (video player) function changeNextVideo(video) { if (video.id === script.ytplayer.getVideoData().video_id) { return; } if (script.debug) { console.log("changing next video button"); } // next video autoplay settings var related_vid_config = window.yt.config_.RELATED_PLAYER_ARGS; if (!related_vid_config) { return; } var related_vids_params = related_vid_config.rvs.split(","); var first_vid_params = related_vids_params[0]; var other_vid_params = related_vids_params.slice(1).join(","); // changing next video with first from queue var params = ["author", "id", "title", "iurlhq", "iurlmq", "length_seconds", "short_view_count_text", "session_data", "endscreen_autoplay_session_data"]; forEach(params, function(param) { var re = new RegExp("(" + param + ")=(.[^&]+)", "g"); first_vid_params = first_vid_params.replace(re, function($0, param, value) { return param + "=" + encodeURIComponent(video[param] || ""); }); }); script.ytplayer.updateVideoData(JSON.parse('{"rvs":"' + first_vid_params + ',' + other_vid_params + '"}')); } // extracting video information and creating a video object (that can be added to the queue) function findVideoInformation(video) { var anchor = video.querySelector('.yt-uix-sessionlink:not(.related-playlist)'); if (anchor) { var id = getVideoInfoFromUrl(video.querySelector('a.yt-uix-sessionlink').href, "v"); var title = video.querySelector('span.title').textContent.trim(); var author = video.querySelector('span.author') ? video.querySelector('span.author').textContent.trim() : video.querySelector('span.attribution').textContent.trim(); var time = video.querySelector('span.video-time') ? video.querySelector('span.video-time').textContent.trim() : "0"; var thumb = video.querySelector('span.yt-uix-simple-thumb-related > img').dataset.thumb || video.querySelector('span.yt-uix-simple-thumb-related > img').src; var sessionData = video.querySelector('a.yt-uix-sessionlink').getAttribute("data-sessionlink"); return new QueueItem(title, id, video.outerHTML, anchor, author, time, null, thumb, sessionData); } return null; } // *** OBJECTS *** // // QueueItem object function QueueItem(title, id, html, anchor, author, time, stats, thumb, sessionData) { this.title = title; this.id = id; this.html = html; this.buttonAnchor = anchor; this.author = author; this.time = time; this.length_seconds = hmsToSecondsOnly(time); this.stats = stats; this.iurlhq = thumb; this.iurlmq = thumb; this.session_data = sessionData; this.endscreen_autoplay_session_data = "autonav=1&playnext=1&" + sessionData; } // Queue object function Queue() { var queue = []; this.get = function() { return queue; }; this.set = function(newQueue) { queue = newQueue; setCache("QUEUE", newQueue); }; this.size = function() { return queue.length; }; this.isEmpty = function() { return this.size() === 0; }; this.contains = function(videoId) { for (let i = 0; i < queue.length; i++) { if (queue[i].id === videoId) { return true; } } return false; } this.peek = function() { return queue[0]; }; this.enqueue = function(item) { queue.push(item); this.update(); this.show(500); }; this.dequeue = function() { var item = queue.shift(); this.update(); this.show(0); return item; }; this.remove = function(index) { queue.splice(index, 1); this.update(); this.show(250); }; this.playNext = function(index) { var video = queue.splice(index, 1); queue.unshift(video[0]); this.update(); this.show(0); }; this.playNow = function() { var video = this.dequeue(); playNextVideo(video.id); }; this.update = function() { setCache("QUEUE", this.get()); if (script.debug) { console.log("updated queue: ", this.get().slice()); } }; this.html = function() { var html = ""; queue.forEach(function(item) { html += item.html; }); return html; }; this.show = function(delay) { setTimeout(function() { displayQueue(); }, delay); }; this.reset = function() { queue = []; this.update(0); this.show(0); }; } // *** QUEUE *** // function initQueue() { script.queue = new Queue(); var cachedQueue = getCache("QUEUE"); if (cachedQueue) { script.queue.set(cachedQueue); } else { setCache("QUEUE", script.queue.get()); } } function loadQueue() { // prepare html for queue var queue = document.querySelector('.autoplay-bar'); if (queue) { queue.id = 'play-next-queue'; // add class to autoplay suggestion video so it doesn't get queue related buttons var suggestion = queue.querySelector('.related-list-item'); if (suggestion && !suggestion.classList.contains("queue-item")) { script.autoplay_suggestion = findVideoInformation(suggestion); } // show the queue if not empty if (!script.queue.isEmpty()) { displayQueue(); } } } function displayQueue() { if (script.debug) { console.log("showing queue: ", script.queue.get()); } var queue = document.getElementById('play-next-queue'); if (!queue) { return; } var queueContents = queue.querySelector('.video-list'); if (!queueContents) { return; } // cleanup current queue queueContents.innerHTML = ""; // display new queue if (!script.queue.isEmpty()) { // insert new queue queueContents.innerHTML = script.queue.html(); // add buttons var items = queueContents.querySelectorAll('.video-list-item'); forEach(items, function(item, index) { var video = findVideoInformation(item); // remove addbutton if there is one var addedButton = item.querySelector('.queue-add'); if (addedButton) { addedButton.remove(); } if (video) { if (index === 0) { changeNextVideo(video); script.playNowButtonGenerator.build(video); } else { script.playNextButtonGenerator.build(video, index); } script.removeButtonGenerator.build(video, index); } item.classList.add('processed-buttons', 'queue-item'); }); // show autoplay suggestion under queue if it is not queued if (!script.queue.contains(script.autoplay_suggestion.id)) { queueContents.insertAdjacentHTML("beforeend", script.autoplay_suggestion.html); var suggestion = queueContents.querySelector('.video-list-item:last-child'); initAddQueueButton(suggestion); } // replace autoplay options with remove queue button var upNext = queue.querySelector('h4.watch-sidebar-head'); if (upNext) { initRemoveQueueButton(upNext); } // remove not interested menu var menus = queue.getElementsByClassName('yt-uix-menu-trigger'); forEachReverse(menus, function(menu) { menu.remove(); }); } else { // restore autoplay suggestion (queue is empty) queueContents.innerHTML = script.autoplay_suggestion.html; // change next video button of the youtube player changeNextVideo(script.autoplay_suggestion); } // triggering lazyload window.scrollTo(window.scrollX, window.scrollY + 1); window.scrollTo(window.scrollX, window.scrollY - 1); } // *** BUTTONS *** // // initialize Button Generators (Template design) function initButtonGenerators() { AddButtonGenerator.prototype = new ButtonGenerator(); script.addButtonGenerator = new AddButtonGenerator(); RemoveButtonGenerator.prototype = new ButtonGenerator(); script.removeButtonGenerator = new RemoveButtonGenerator(); PlayNextButtonGenerator.prototype = new ButtonGenerator(); script.playNextButtonGenerator = new PlayNextButtonGenerator(); PlayNowButtonGenerator.prototype = new ButtonGenerator(); script.playNowButtonGenerator = new PlayNowButtonGenerator(); } function initAddQueueButton(video) { try { var videoData = findVideoInformation(video); video.classList.add('processed-buttons'); if (videoData) { script.addButtonGenerator.build(videoData); } } catch(error) { console.error("Couldn't initialize \"Add to queue\" buttons for a video \n" + error.message); } } function initAddQueueButtons() { var videos = document.querySelectorAll('#watch-related .video-list-item:not(.processed-buttons)'); forEach(videos, function(video) { initAddQueueButton(video); }); } // Button template function ButtonGenerator() { this.build = function(video, index) { var anchor = video.buttonAnchor; var html = "<div class=\"queue-button " + this.type + " yt-uix-button yt-uix-button-default yt-uix-button-size-default\"><button class=\"yt-uix-button-content\">" + this.text + "</button></div>"; anchor.insertAdjacentHTML("beforeend", html); var button = this; anchor.getElementsByClassName(this.type)[0].addEventListener("click", function handler(e) { e.preventDefault(); button.clickBehavior(this, video, index); e.currentTarget.removeEventListener(e.type, handler); this.addEventListener("click", function(e) { e.preventDefault(); }); }); }; } function AddButtonGenerator() { this.type = "queue-add"; this.text = "Add to queue"; this.clickBehavior = function(element, video, index) { if (!script.queue.contains(video.id)) { script.queue.enqueue(video); element.textContent = "Queued!"; } else { element.textContent = "Already Queued"; } }; } function RemoveButtonGenerator() { this.type = "queue-remove"; this.text = "Remove"; this.clickBehavior = function(element, video, index) { element.textContent = "Removed!"; script.queue.remove(index); restoreAddButton(video.id); }; } function PlayNextButtonGenerator() { this.type = "queue-next"; this.text = "Play Next"; this.clickBehavior = function(element, video, index) { this.textContent = "To the top!"; script.queue.playNext(index); }; } function PlayNowButtonGenerator() { this.type = "queue-now"; this.text = "Play Now"; this.clickBehavior = function(element, video, index) { this.textContent = "Playing!"; script.queue.playNow(); }; } // The "remove queue and all its videos" button function initRemoveQueueButton(anchor) { var html = "<div class=\"queue-button remove-queue yt-uix-button yt-uix-button-default yt-uix-button-size-default\"><button class=\"yt-uix-button-content\">Remove Queue</button></div>"; anchor.innerHTML = html; anchor.getElementsByClassName('remove-queue')[0].addEventListener("click", function handler(e) { e.preventDefault(); script.queue.reset(); restoreAddButton("*"); // restore all add buttons this.parentNode.innerHTML = "Up Next"; }); } function restoreAddButton(id) { var videos = document.querySelectorAll('#watch-related .related-list-item'); forEach(videos, function(video) { if (id === "*" || id === getVideoInfoFromUrl(video.querySelector('a.yt-uix-sessionlink').href, "v")) { // remove current addbutton if there is one var addedButton = video.querySelector('.queue-add'); if (addedButton) { addedButton.remove(); } // make new addbutton var videoData = findVideoInformation(video); if (videoData) { script.addButtonGenerator.build(videoData); } } }); } // *** LOCALSTORAGE *** // function getCache(key) { return JSON.parse(localStorage.getItem("YTQUEUE-CLASSIC#" + script.version + "#" + key)); } function deleteCache(key) { localStorage.removeItem("YTQUEUE-CLASSIC#" + script.version + "#" + key); } function setCache(key, value) { localStorage.setItem("YTQUEUE-CLASSIC#" + script.version + "#" + key, JSON.stringify(value)); } // injecting css function injectCSS() { var css = ` '#play-next-queue' { list-style: none; } '#play-next-queue' .standalone-collection-badge-renderer-icon { display: none; } '#play-next-queue' .standalone-collection-badge-renderer-text { display: none; } '#play-next-queue' .related-list-item span.title { max-height: 2.3em; } .processed-buttons .queue-add { display: none; } .processed-buttons:hover .queue-add { display: inline-block; } #watch-related .processed-buttons:hover .standalone-collection-badge-renderer-icon { display: none; } #watch-related .processed-buttons:hover .standalone-collection-badge-renderer-text { display: none; } .queue-item span.title { max-height: 2.3em; } .related-list-item:hover span.title { max-height: 2.3em; } .queue-button { height: 15px; padding: 0.2em 0.4em 0.2em 0.4em; margin: 2px 0; } .queue-remove { margin-left: 4px; } `; var style = document.createElement("style"); style.type = "text/css"; if (style.styleSheet){ style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } document.documentElement.appendChild(style); } // *** FUNCTIONALITY *** // function forEach(array, callback, scope) { for (var i = 0; i < array.length; i++) { callback.call(scope, array[i], i); } } // When you want to remove elements function forEachReverse(array, callback, scope) { for (var i = array.length - 1; i >= 0; i--) { callback.call(scope, array[i], i); } } // hh:mm:ss => only seconds function hmsToSecondsOnly(str) { var p = str.split(":"), s = 0, m = 1; while (p.length > 0) { s += m * parseInt(p.pop(), 10); m *= 60; } return s; } } // ================================================================================ // // ======================= YOUTUBE PLAY NEXT QUEUE (MODERN) ======================= // // ================================================================================ // function youtube_play_next_queue_modern() { var script = { initialized: false, version: "2.0.0", queue: null, ytplayer: null, autoplay_suggestion: null, queue_rendered_observer: null, video_renderer_observer: null, playnext_data_observer: null, debug: false }; document.addEventListener("DOMContentLoaded", initScript); window.addEventListener("storage", function(event) { if (/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 (material) #"); } if (script.initialized) { startScript(); } else { initScript(); startScript(); } }); function initScript() { if (script.debug) { console.log("Youtube Play Next Queue Initializing"); } if (window.Polymer === undefined) { return; } initQueue(); injectCSS(); // TODO, better / more efficient alternative? setInterval(addThumbOverlayClickListeners, 250); setInterval(initThumbOverlays, 1000); if (script.debug) { console.log("### Modern youtube loaded ###"); } script.initialized = true; } function startScript() { if (isPlayerAvailable()) { if (script.debug) { console.log("videoplayer is available"); } if (script.debug) { console.log("ytplayer: ", script.ytplayer); } if (script.ytplayer && !isPlaylist()) { if (script.debug) { console.log("initializing queue"); } loadQueue(); if (script.debug) { console.log("initializing video statelistener"); } initVideoStateListener(); if (script.debug) { console.log("initializing playnext data observer"); } initPlayNextDataObserver(); } } 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); // run handler once to make sure queue is in sync handleVideoStateChanged(script.ytplayer.getPlayerState()); } else { if (script.debug) { console.log("statelistener already initialized"); } } } function handleVideoStateChanged(videoState) { if (script.debug) { console.log("player state changed: " + videoState + "; queue empty: " + script.queue.isEmpty()); } const FINISHED_STATE = 0; const PLAYING_STATE = 1; const PAUSED_STATE = 2; const BUFFERING_STATE = 3; const CUED_STATE = 5; 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(); } } if ((videoState === PLAYING_STATE || videoState === PAUSED_STATE) && !script.queue.isEmpty()) { script.queue.peek().setAsNextVideo(); } if (videoState === PAUSED_STATE) { // TODO: check if this works // Check for annoying "are you still watching" popup setTimeout(() => { let button = document.getElementById('confirm-button'); if (button && button.offsetParent === null) { if (script.debug) { console.log("### Clicking confirm button popup ###"); } button.click(); } }, 1000); } } function initQueueRenderedObserver() { if (script.queue_rendered_observer) { script.queue_rendered_observer.disconnect(); } // if the queue is completely rendered, mutationCount is equal to the queue size // => initialize queue button listeners for Play Now, Play Next and Remove let mutationCount = 0; script.queue_rendered_observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutationCount += mutation.addedNodes.length; if (mutationCount === script.queue.size()) { initQueueButtons(); script.queue_rendered_observer.disconnect(); } }); }); let observable = document.querySelector('ytd-compact-autoplay-renderer > #contents'); script.queue_rendered_observer.observe(observable, { childList: true }); } function initPlayNextDataObserver() { if (script.playnext_data_observer) { script.playnext_data_observer.disconnect(); } // If youtube updates the videoplayer with the autoplay suggestion, // replace it with the next video in our queue. script.playnext_data_observer = new MutationObserver(function(mutations) { if (!script.queue.isEmpty() && !isPlaylist() && !isLivePlayer()) { forEach(mutations, function(mutation) { if (mutation.attributeName === "href") { let nextVideoId = getVideoInfoFromUrl(document.querySelector('.ytp-next-button').href, "v"); let nextQueueItem = script.queue.peek(); if (nextQueueItem.id !== nextVideoId) { nextQueueItem.setAsNextVideo(); } } }); } }); let observable = document.querySelector('.ytp-next-button'); script.playnext_data_observer.observe(observable, { attributes: true }); } /* function initVideoRendererObserver() { if (script.video_renderer_observer) { script.video_renderer_observer.disconnect(); } script.video_renderer_observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { forEach(mutation.addedNodes, function(node) { let tagNames = ["YTD-COMPACT-VIDEO-RENDERER", "YTD-GRID-VIDEO-RENDERER", "YTD-VIDEO-RENDERER"]; if (tagNames.includes(node.tagName)) { initThumbOverlay(node); // If youtube updates node data, reinit thumb overlay new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { initThumbOverlay(mutation.target); }); }).observe(node, { attributes: true }); } }); }); }); let observable = document.querySelector('ytd-watch-next-secondary-results-renderer > #items'); script.video_renderer_observer.observe(observable, { childList: true }); } */ // *** VIDEOPLAYER *** // function getVideoPlayer() { return 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; } function isLivePlayer() { return script.ytplayer.getVideoData().isLive; } function isPlayerFullscreen() { return script.ytplayer.classList.contains('ytp-fullscreen'); } function isPlayerMinimized() { return document.querySelector('ytd-miniplayer[active][enabled]'); } function getVideoInfoFromUrl(url, info) { if (url.indexOf("?") === -1) { return null; } let urlVariables = url.split("?")[1].split("&"); for(var i = 0; i < urlVariables.length; i++) { var varName = urlVariables[i].split("="); if (varName[0] === info) { return varName[1] === undefined ? null : varName[1]; } } } // *** OBJECTS *** // // QueueItem object class QueueItem { constructor(id, data, type) { this.id = id; this.data = data; this.type = type; } getRelatedVideoArgs() { let args = { iurlmq: this.data.thumbnail.thumbnails[0].url, length_seconds: hmsToSeconds(this.data.lengthText ? this.data.lengthText.simpleText : this.data.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer ? this.data.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer.text.simpleText : ""), id: this.data.videoId, iurlhq: this.data.thumbnail.thumbnails[0].url, title: this.data.title.simpleText, session_data: "itct=" + this.data.navigationEndpoint.clickTrackingParams, aria_label: this.data.title.accessibility.accessibilityData.label, author: this.data.shortBylineText.runs[0].text, short_view_count_text: this.data.shortViewCountText ? this.data.shortViewCountText.simpleText : "", endscreen_autoplay_session_data: "autonav=1&playnext=1&itct=" + this.data.navigationEndpoint.clickTrackingParams, }; return args; } setAsNextVideo() { const PLAYING_STATE = 1; const PAUSED_STATE = 2; 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"); } // next video autoplay settings let relatedVideoConfig = document.querySelector('ytd-player').__data.watchNextData.webWatchNextResponseExtensionData; let relatedVideosArgsList = relatedVideoConfig.relatedVideoArgs.split(","); let firstVideoArgs = relatedVideosArgsList[0]; let otherVideoArgs = relatedVideosArgsList.slice(1).join(","); let videoParams = this.getRelatedVideoArgs(); // changing next video with first from queue forEach(Object.keys(videoParams), function(param) { let re = new RegExp("(" + param + ")=(.[^&]+)", "g"); firstVideoArgs = firstVideoArgs.replace(re, function($0, param, value) { return param + "=" + encodeURIComponent(videoParams[param] || ""); }); }); script.ytplayer.updateVideoData(JSON.parse('{"rvs":"' + firstVideoArgs + ',' + otherVideoArgs + '"}')); } 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 data = Object.assign({}, element.__data.data); data.navigationEndpoint.watchEndpoint = { "videoId": data.videoId }; data.navigationEndpoint.commandMetadata = { "webCommandMetadata": { "url": "/watch?v=" + data.videoId, webPageType: "WEB_PAGE_TYPE_WATCH" } }; let id = data.videoId; let type = element.tagName.toLowerCase(); return new QueueItem(id, data, type); } 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) { let video = this.queue.splice(index, 1); this.queue.unshift(video[0]); 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) { cachedQueue = cachedQueue.map(queueItem => QueueItem.fromJSON(queueItem)); script.queue.set(cachedQueue); } else { setCache("QUEUE", script.queue.get()); } } function loadQueue() { // prepare html for queue let queue = document.querySelector('ytd-compact-autoplay-renderer'); if (!queue) { return; } let suggestion = queue.querySelector('ytd-compact-video-renderer'); if (suggestion && !suggestion.classList.contains("queue-item")) { script.autoplay_suggestion = QueueItem.fromDOM(suggestion); } // show the queue if not empty if (!script.queue.isEmpty()) { displayQueue(); } } function displayQueue() { if (script.debug) { console.log("showing queue: ", script.queue.get()); } let queue = document.querySelector('ytd-compact-autoplay-renderer'); if (!queue) { return; } let queueContents = queue.querySelector('#contents'); if (!queueContents) { return; } initQueueRenderedObserver(); // clear current content queueContents.innerHTML = ""; // display new queue if (!script.queue.isEmpty()) { forEach(script.queue.get(), function(item, index) { item.clearBadges(); if (index === 0) { item.setAsNextVideo(); item.addBadge("Play Now", ["QUEUE_BUTTON", "QUEUE_PLAY_NOW"]); item.addBadge("Remove", ["QUEUE_BUTTON", "QUEUE_REMOVE"]); } else { item.addBadge("Play Next", ["QUEUE_BUTTON", "QUEUE_PLAY_NEXT"]); item.addBadge("Remove", ["QUEUE_BUTTON", "QUEUE_REMOVE"]); } window.Polymer.dom(queueContents).appendChild(item.toNode(["queue-item"])); }); // show autoplay suggestion under queue if it is not queued if (!script.queue.contains(script.autoplay_suggestion.id)) { window.Polymer.dom(queueContents).appendChild(script.autoplay_suggestion.toNode()); } // initialize remove queue button. let upNext = queue.querySelector("#upnext"); if (upNext) { initRemoveQueueButton(upNext); } } else { // restore autoplay suggestion (queue is empty) script.autoplay_suggestion.setAsNextVideo(); window.Polymer.dom(queueContents).appendChild(script.autoplay_suggestion.toNode()); // restore up next header let upNext = queue.querySelector("#upnext"); if (upNext) { upNext.innerHTML = "Up next"; } } } // The "remove queue and all its videos" button function initRemoveQueueButton(anchor) { var 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 addThumbOverlay(videoOverlays) { // we don't use the toggled icon, that's why both have the same values. 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" } } } }; videoOverlays.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 = videoRenderer.__data.data; if (videoData && videoData.thumbnailOverlays && !hasThumbOverlay(videoData.thumbnailOverlays)) { addThumbOverlay(videoData.thumbnailOverlays); } } function initThumbOverlays() { let videoRenderers = document.querySelectorAll('ytd-compact-video-renderer, ytd-grid-video-renderer, ytd-video-renderer, ytd-playlist-video-renderer'); forEach(videoRenderers, function(videoRenderer) { initThumbOverlay(videoRenderer); }); } 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()); 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"]; if (tagNames.includes(path[i].tagName)) { let newQueueItem = QueueItem.fromDOM(path[i]); if (!script.queue.contains(newQueueItem.id)) { script.queue.enqueue(newQueueItem); openToast("Video Added to Queue", event.target); } else { openToast("Video Already Queued", event.target); } break; } } } // *** BUTTONS *** // function initQueueButtons() { // initQueueButtonAction("queue-play-now", () => script.queue.playNow()); initQueueButtonAction("queue-play-next", (pos) => script.queue.playNext(pos+1)); initQueueButtonAction("queue-remove", (pos) => script.queue.remove(pos)); } function initQueueButtonAction(className, btnAction) { let buttons = document.getElementsByClassName(className); forEach(buttons, function(button, index) { let pos = index; if (!button.classList.contains("button-listener")) { button.addEventListener("click", function(event) { event.preventDefault(); event.stopPropagation(); btnAction(pos); }); button.classList.add("button-listener"); } }); } // *** POPUPS *** // function openToast(text, target) { let openPopupAction = { "openPopupAction": { "popup": { "notificationActionRenderer": { "responseText": {simpleText: text}, "trackingParams": "" } }, "popupType": "TOAST" } }; var popupContainer = document.querySelector('ytd-popup-container'); popupContainer.handleOpenPopupAction_(openPopupAction, target); } // *** 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 *** // // injecting css function injectCSS() { let css = ` .queue-button { height: 15px; line-height: 1.7rem !important; padding: 5px !important; margin: 5px 3px !important; cursor: default; z-index: 99; background-color: var(--yt-spec-10-percent-layer); color: var(--yt-spec-text-secondary); } .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; right: auto; left: 0; } 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; } .queue-item #metadata-line { display: none; } #upnext.flex-none { flex: 0 !important; white-space: nowrap; } #upnext > .queue-button { font-size: 1.4rem; font-weight: 500; margin: 0 !important; } .flex-whitebox { flex: 1; } `; 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); } } // When you want to remove elements function forEachReverse(array, callback, scope) { for (let i = array.length - 1; i >= 0; i--) { callback.call(scope, array[i], i); } } // hh:mm:ss => only seconds function hmsToSeconds(str) { let p = str.split(":"), s = 0, m = 1; while (p.length > 0) { s += m * parseInt(p.pop(), 10); m *= 60; } return s; } } // ================================================================================= // // ====================== YOUTUBE SEARCH WHILE WATCHING VIDEO ====================== // // ================================================================================= // function youtube_search_while_watching_video() { var script = { loaded: false, ytplayer: null, modern: false, search_bar: null, search_timeout: null, search_suggestions: [], suggestion_observer: null, debug: false }; document.addEventListener("DOMContentLoaded", initScript); // reload script on page change using youtube spf events (http://youtube.github.io/js/documentation/events/) window.addEventListener("spfdone", function(e) { if (script.debug) { console.log("# page updated (normal) #"); } startScript(2); }); // 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 (material) #"); } startScript(2); }); function initScript() { if (script.debug) { console.log("Youtube search while watching video initializing"); } if (window.Polymer === undefined) { if (script.debug) { console.log("### Normal youtube loaded ###"); } script.modern = false; } else { if (script.debug) { console.log("### Material youtube loaded ###"); } script.modern = true; } initSearch(); initSuggestionObserver(); injectCSS(); script.loaded = true; startScript(5); } function startScript(retry) { if (script.loaded && 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) { // fix conflict with Youtube+ script setTimeout(function() { startScript(--this.retry); }.bind({retry:retry}), 1000); } } else { if (script.debug) { console.log("videoplayer is unavailable"); } } } // *** OBSERVERS *** // function initSuggestionObserver() { script.suggestion_observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { forEach(mutation.addedNodes, function(addedNode) { if (!addedNode.classList.contains('yt-search-generated') && addedNode.tagName !== "YTD-COMPACT-AUTOPLAY-RENDERER") { addedNode.classList.add('suggestion-tag'); } }); }); }); } // *** VIDEOPLAYER *** // // video object (normal youtube only) function YtVideo(id, title, author, time, stats, thumb, sessionData) { this.id = id; this.title = title; this.author = author; this.time = time; this.stats = stats; this.iurlhq = thumb; this.iurlmq = thumb; this.session_data = sessionData; } function getVideoPlayer() { return 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; } function isLivePlayer() { return script.ytplayer.getVideoData().isLive; } // *** SEARCH *** // function initSearch() { // callback function for search suggestion results window.suggestions_callback = suggestionsCallback; } function loadSearch() { if (script.modern) { showSuggestions(true); // prevent double searchbar var playlistOrLiveSearchBar = document.querySelector('#suggestions-search.playlist-or-live'); if (playlistOrLiveSearchBar) { playlistOrLiveSearchBar.remove(); } } if (!document.getElementById('suggestions-search')) { createSearchBar(); tagCurrentSuggestions(); } cleanupSuggestionRequests(); } function createSearchBar() { var anchor, html; if (script.modern) { anchor = document.querySelector('ytd-compact-autoplay-renderer > #contents'); if (anchor) { html = "<input id=\"suggestions-search\" type=\"search\" placeholder=\"Search\">"; anchor.insertAdjacentHTML("afterend", html); } else { // playlist or live video? 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); } } } else { anchor = document.querySelector('#watch7-sidebar-modules > div:nth-child(2)'); if (anchor) { html = "<input id=\"suggestions-search\" class=\"search-term yt-uix-form-input-bidi\" type=\"search\" placeholder=\"Search\">"; anchor.insertAdjacentHTML("afterbegin", html); } else { // playlist or live video? anchor = document.querySelector('#watch7-sidebar-modules'); if (anchor) { html = "<input id=\"suggestions-search\" class=\"search-term yt-uix-form-input-bidi playlist-or-live\" type=\"search\" placeholder=\"Search\">"; anchor.insertAdjacentHTML("afterbegin", html); } } } var searchBar = document.getElementById('suggestions-search'); if (searchBar) { script.search_bar = searchBar; new autoComplete({ selector: '#suggestions-search', minChars: 1, delay: 250, source: function(term, suggest) { suggest(script.search_suggestions); }, onSelect: function(event, term, item) { prepareNewSearchRequest(term); } }); script.search_bar.addEventListener("keyup", function(event) { if (this.value === "") { showSuggestions(true); } else { searchSuggestions(this.value); } }); // seperate keydown listener because the search listener blocks keyup..? 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(); // close search suggestions dropdown script.search_suggestions = []; // clearing the search suggestions showSuggestions(true); } }); script.search_bar.addEventListener("focus", function(event) { this.select(); }); } } // add class to current suggestions, so we can toggle hide/show function tagCurrentSuggestions() { if (script.suggestion_observer) { script.suggestion_observer.disconnect(); var observables = document.querySelectorAll('ytd-watch-next-secondary-results-renderer > #items, #watch-related, #watch-more-related'); forEach(observables, function(observable) { script.suggestion_observer.observe(observable, { childList: true }); }); } var suggestions = document.querySelectorAll('#watch-related > li.video-list-item, ytd-compact-video-renderer.ytd-watch-next-secondary-results-renderer, ytd-compact-radio-renderer.ytd-watch-next-secondary-results-renderer'); forEach(suggestions, function(suggestion) { suggestion.classList.add('suggestion-tag'); }); } // toggle hide/show suggestions depending on $show and remove previously searched videos if any function showSuggestions(show) { var videoListItems = document.querySelectorAll('#watch-related > li.video-list-item, #watch-more-related > li.video-list-item, #items > ytd-compact-video-renderer, #items > ytd-compact-radio-renderer, #items > ytd-compact-playlist-renderer'); forEachReverse(videoListItems, function(video) { if (video.classList.contains('suggestion-tag')) { video.style.display = (show) ? "" : "none"; } else { video.remove(); } }); if (!script.modern) { var watchRelated = document.getElementById('watch-related'); var currNavigation = watchRelated.parentNode.querySelector('.search-pager'); if (currNavigation) { currNavigation.remove(); } // remove navigation var seperationLine = watchRelated.parentNode.querySelector('.watch-sidebar-separation-line'); if (seperationLine) { seperationLine.remove(); } // remove seperation line } var showMore = document.getElementById('watch-more-related-button') || document.querySelector('#continuations.ytd-watch-next-secondary-results-renderer'); if (showMore) { showMore.style.display = (show) ? "" : "none"; } // toggle hide/show the "More Suggestions" link } // callback from search suggestions attached to window function suggestionsCallback(data) { var raw = data[1]; // extract relevant data from json var suggestions = raw.map(function(array) { return array[0]; // change 2D array to 1D array with only suggestions }); if (script.debug) { console.log(suggestions); } script.search_suggestions = suggestions; } function searchSuggestions(value) { if (script.search_timeout !== null) { clearTimeout(script.search_timeout); } // youtube search parameters const GeoLocation = window.yt.config_.INNERTUBE_CONTEXT_GL; const HostLanguage = window.yt.config_.INNERTUBE_CONTEXT_HL; // only allow 1 suggestion request every 100 milliseconds script.search_timeout = setTimeout(function() { if (script.debug) { console.log("suggestion request send", this.searchValue); } var 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(this.searchValue) + "&callback=suggestions_callback"; (document.body || document.head || document.documentElement).appendChild(scriptElement); }.bind({searchValue:value}), 100); } function cleanupSuggestionRequests() { var requests = document.getElementsByClassName('suggestion-request'); forEachReverse(requests, function(request) { request.remove(); }); } // send new search request (with the search bar) function prepareNewSearchRequest(value) { if (script.debug) { console.log("searching for " + value); } script.search_bar.blur(); // close search suggestions dropdown script.search_suggestions = []; // clearing the search suggestions sendSearchRequest("https://www.youtube.com/results?" + (script.modern ? "pbj=1&search_query=" : "disable_polymer=1&q=") + encodeURIComponent(value)); } // given the url, retrieve the search results function sendSearchRequest(url) { var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { if (script.modern) { processSearchModern(xmlHttp.responseText); } else { var container = document.implementation.createHTMLDocument().documentElement; container.innerHTML = xmlHttp.responseText; processSearch(container); } } }; xmlHttp.open("GET", url, true); if (script.modern) { 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-identity-token", window.yt.config_.ID_TOKEN); } xmlHttp.send(null); } // process search request (normal youtube) function processSearch(container) { var watchRelated = document.getElementById('watch-related'); // hide current suggestions and remove searched videos if any showSuggestions(false); // insert searched videos var videoItems = container.querySelectorAll('.item-section .yt-lockup-video'); forEach(videoItems, function(videoItem) { if (videoItem.querySelector('.yt-badge-live') === null) { try { var videoId = videoItem.dataset.contextItemId; var videoTitle = videoItem.querySelector('.yt-lockup-title > a').title; var videoStats = videoItem.querySelector('.yt-lockup-meta').innerHTML; var videoTime = videoItem.querySelector('.video-time') ? videoItem.querySelector('.video-time').textContent : "0"; var author = videoItem.querySelector('.yt-lockup-byline') ? videoItem.querySelector('.yt-lockup-byline').textContent : ""; var videoThumb = videoItem.querySelector('div.yt-lockup-thumbnail img').dataset.thumb || videoItem.querySelector('div.yt-lockup-thumbnail img').src; var sessionData = videoItem.querySelector('a.yt-uix-sessionlink').getAttribute("data-sessionlink"); var videoObject = new YtVideo(videoId, videoTitle, author, videoTime, videoStats, videoThumb, sessionData); if (script.debug) { console.log(videoObject); } watchRelated.insertAdjacentHTML("beforeend", videoQueueHTML(videoObject).html); } catch (error) { console.error("failed to process video " + error.message, videoItem); } } }); // insert navigation buttons var navigation = container.querySelector('.search-pager'); var navigationButtons = navigation.getElementsByTagName('a'); forEach(navigationButtons, function(button) { button.addEventListener("click", function handler(e) { e.preventDefault(); script.search_bar.scrollIntoView(); window.scrollBy(0, -1 * document.getElementById('yt-masthead-container').clientHeight); sendSearchRequest(this.href); }); }); watchRelated.parentNode.appendChild(navigation); // append new navigation watchRelated.insertAdjacentHTML("afterend", "<hr class=\"watch-sidebar-separation-line\">"); // insert separation line between videos and navigation } // process search request (material youtube) function processSearchModern(responseText) { var data = JSON.parse(responseText); if (data && data[1] && data[1].response) { try { // dat chain o.O var videosData = data[1].response.contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents[0].itemSectionRenderer.contents; if (script.debug) { console.log(videosData); } // hide current suggestions and remove previously searched videos if any showSuggestions(false); var watchRelated = document.querySelector('ytd-watch-next-secondary-results-renderer > #items'); forEach(videosData, function(videoData) { if (videoData.videoRenderer) { window.Polymer.dom(watchRelated).appendChild(videoQueuePolymer(videoData.videoRenderer, "ytd-compact-video-renderer")); } else if (videoData.radioRenderer) { window.Polymer.dom(watchRelated).appendChild(videoQueuePolymer(videoData.radioRenderer, "ytd-compact-radio-renderer")); } else if (videoData.playlistRenderer) { window.Polymer.dom(watchRelated).appendChild(videoQueuePolymer(videoData.playlistRenderer, "ytd-compact-playlist-renderer")); } }); } catch (error) { alert("failed to retrieve search data, sorry! " + error.message); } } } // *** HTML & CSS *** // function videoQueueHTML(video) { var strVar = ""; strVar += "<li class=\"video-list-item related-list-item show-video-time related-list-item-compact-video yt-search-generated\">"; strVar += " <div class=\"related-item-dismissable\">"; strVar += " <div class=\"content-wrapper\">"; strVar += " <a href=\"\/watch?v=" + video.id + "\" class=\"yt-uix-sessionlink content-link spf-link spf-link\" data-sessionlink=\"" + video.session_data + "\" rel=\"spf-prefetch\" title=\"" + video.title + "\">"; strVar += " <span dir=\"ltr\" class=\"title\">" + video.title + "<\/span>"; strVar += " <span class=\"stat author\">" + video.author + "<\/span>"; strVar += " <div class=\"yt-lockup-meta stat\">" + video.stats + "<\/div>"; strVar += " <\/a>"; strVar += " <\/div>"; strVar += " <div class=\"thumb-wrapper\">"; strVar += " <a href=\"\/watch?v=" + video.id + "\" class=\"yt-uix-sessionlink thumb-link spf-link spf-link\" data-sessionlink=\"" + video.session_data + "\" rel=\"spf-prefetch\" tabindex=\"-1\" aria-hidden=\"true\">"; strVar += " <span class=\"yt-uix-simple-thumb-wrap yt-uix-simple-thumb-related\" tabindex=\"0\" data-vid=\"" + video.id + "\">"; strVar += " <img aria-hidden=\"true\" alt=\"\" src=\"" + video.iurlhq + "\">"; strVar += " <\/span>"; strVar += " <\/a>"; strVar += " <span class=\"video-time\">"+ video.time +"<\/span>"; strVar += " <button class=\"yt-uix-button yt-uix-button-size-small yt-uix-button-default yt-uix-button-empty yt-uix-button-has-icon no-icon-markup addto-button video-actions spf-nolink hide-until-delayloaded addto-watch-later-button yt-uix-tooltip\" type=\"button\" onclick=\";return false;\" title=\"Watch Later\" role=\"button\" data-video-ids=\"" + video.id + "\" data-tooltip-text=\"Watch Later\"><\/button>"; strVar += " <\/div>"; strVar += " <\/div>"; strVar += "<\/li>"; video.html = strVar; return video; } 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() { var css; if (script.modern) { css = ` .autocomplete-suggestions { text-align: left; cursor: default; border: 1px solid var(--ytd-searchbox-legacy-border-color); border-top: 0; background: var(--yt-searchbox-background); position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box; box-shadow: -1px 1px 3px rgba(0,0,0,.1); } .autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.22em; color: var(--yt-placeholder-text); } .autocomplete-suggestion b { font-weight: normal; color: #b31217; } .autocomplete-suggestion.selected { background: #ddd; } [dark] .autocomplete-suggestion.selected { background: #333; } ytd-compact-autoplay-renderer { padding-bottom: 0px; } #suggestions-search { outline: none; width: 100%; padding: 6px 5px; margin: 8px 0 0 0; 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(--yt-searchbox-text-color); background-color: var(--yt-searchbox-background); } #suggestions-search.playlist-or-live { margin-bottom: 16px; } `; } else { css = ` .autocomplete-suggestions { text-align: left; cursor: default; border: 1px solid #ccc; border-top: 0; background: #fff; box-shadow: -1px 1px 3px rgba(0,0,0,.1); position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box; } .autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.02em; color: #333; } .autocomplete-suggestion b { font-weight: normal; color: #b31217; } .autocomplete-suggestion.selected { background: #f0f0f0; } .yt-uix-simple-thumb-wrap > img { top: 0px; width: 168px; height: 94px; } .watch-sidebar-body > div.search-pager { width: 97.5%; padding: 5px 5px; display: flex; justify-content: center; } .watch-sidebar-body > div.search-pager > .yt-uix-button { margin: 0 1px; } #suggestions-search { outline: none; width: 98%; padding: 5px 5px; margin: 0 4px; } #suggestions-search.playlist-or-live { width: 97%; margin: 0 10px 10px 10px; } `; } var 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 (var i = 0; i < array.length; i++) { callback.call(scope, array[i], i); } } // When you want to remove elements function forEachReverse(array, callback, scope) { for (var i = array.length - 1; i >= 0; i--) { callback.call(scope, array[i], i); } } } // ================================================================================= // // =============================== INJECTING SCRIPTS =============================== // // ================================================================================= // var autoCompleteScript = document.createElement('script'); autoCompleteScript.appendChild(document.createTextNode('window.autoComplete = ' + autoComplete + ';')); (document.body || document.head || document.documentElement).appendChild(autoCompleteScript); var queueScriptClassic = document.createElement('script'); queueScriptClassic.appendChild(document.createTextNode('('+ youtube_play_next_queue_classic +')();')); (document.body || document.head || document.documentElement).appendChild(queueScriptClassic); var queueScriptModern = document.createElement('script'); queueScriptModern.appendChild(document.createTextNode('('+ youtube_play_next_queue_modern +')();')); (document.body || document.head || document.documentElement).appendChild(queueScriptModern); var searchScript = document.createElement('script'); searchScript.appendChild(document.createTextNode('('+ youtube_search_while_watching_video +')();')); (document.body || document.head || document.documentElement).appendChild(searchScript); })();