Greasy Fork is available in English.
Restores titles for removed or private videos in YouTube playlists
// ==UserScript== // @name Filmot Title Restorer // @namespace http://tampermonkey.net/ // @version 0.41 // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt // @description Restores titles for removed or private videos in YouTube playlists // @author Jopik // @match https://*.youtube.com/* // @icon https://www.google.com/s2/favicons?domain=filmot.com // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/cash/8.1.5/cash.min.js // ==/UserScript== var darkModeBackground="#000099"; var lightModeBackground="#b0f2f4"; var darkModeLinkColor="#f1f1f1"; //console.log("Starting filmot title restorer"); document.addEventListener('yt-navigate-start', handleNavigateStart); document.addEventListener('yt-navigate-finish', handleNavigateFinish); document.addEventListener( 'yt-action', handlePageDataLoad ); //console.log("addEventListener completed"); //fire at least once on load, sometimes handleNavigateFinish on first load yt-navigate-finish already fired before script loads handleNavigateFinish(); function escapeHTML(unsafe) { return unsafe.replace( /[\u0000-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u00FF]/g, c => '&#' + ('000' + c.charCodeAt(0)).substr(-4, 4) + ';' ) } function handlePageDataLoad(event){ if (event.detail!=null && event.detail.actionName!=null && event.detail.actionName.indexOf("yt-append-continuation")>=0) { if (window.location.href.indexOf("/playlist?")>0) { extractIDsFullView(); } } } function handleNavigateStart(){ var filmotTitles=$(".filmot_title"); filmotTitles.text(""); filmotTitles.removeClass("filmot_title"); var filmotChannels=$(".filmot_channel"); filmotChannels.text(""); filmotChannels.attr("onclick",""); filmotChannels.removeClass("filmot_channel"); cleanUP(); } function handleNavigateFinish(){ cleanUP(); if (window.location.href.indexOf("/playlist?")>0) { setTimeout(function(){ extractIDsFullView(); }, 500); } if (window.location.href.indexOf("/watch?")>0) { setTimeout(function(){ checkIfPrivatedOrRemoved(); }, 500); } } function cleanUP() { $(".filmot_hide").show(); $(".filmot_hide").removeClass("filmot_hide"); $(".filmot_newimg").remove(); $(".filmot_highlight").css("background-color",""); $(".filmot_highlight").removeClass("filmot_highlight"); $("#TitleRestoredDiv").remove(); $(".filmot_c_link").remove(); window.RecoveredIDS={}; window.DetectedIDS={}; } function checkIfPrivatedOrRemoved() { var status=window.ytInitialPlayerResponse.playabilityStatus.status; if (status=="ERROR" || status=="LOGIN_REQUIRED") { var id=window.ytInitialData.currentVideoEndpoint.watchEndpoint.videoId; if (id.length>=11) { window.deletedIDs=id; window.deletedIDCnt=1; window.DetectedIDS[id]=1; processClick(2,0); } } } function createRestoreButton() { // Time to create the 'Restore Titles' button in the Playlist Description Box (left side pane, beneath playlist thumbnail) console.log("[Filmot] [DEBUG] Creating 'Restore Titles' button in playlist description box."); // For some reason, YouTube (or a browser plugin) sometimes creates one or more duplicate, commented-out Description Boxes. // Therefore, we locate all Playlist Description Box elements, and filter out the commented ones. var metactionbars = Array.from(document.querySelectorAll('div.page-header-view-model-wiz__page-header-content > div.page-header-view-model-wiz__page-header-headline-info')).filter(el => el.offsetParent !== null); // Select the first instance (the top of the Description Box) var metactionbar = metactionbars[0]; if (metactionbar) { // Create the container div var containerDiv = document.createElement('div'); containerDiv.id = 'TitleRestoredDiv'; containerDiv.style.textAlign = 'center'; // Create the button var button = document.createElement('button'); button.id = 'TitleRestoredBtn'; button.textContent = 'Restore Titles'; // Create the link var link = document.createElement('a'); link.href = 'https://filmot.com'; link.target = '_blank'; link.style.color = 'white'; link.style.fontSize = 'large'; link.textContent = 'Powered by filmot.com'; // Assemble the elements containerDiv.appendChild(document.createElement('br')); containerDiv.appendChild(button); containerDiv.appendChild(document.createElement('br')); containerDiv.appendChild(link); // Insert the container at the beginning of metactionbar metactionbar.insertBefore(containerDiv, metactionbar.firstChild); } } function extractIDsFullView() { window.deletedIDs=""; window.deletedIDCnt=0; var deletedIDs=""; var deletedIDsCnt=0; var rendererSelector="h3.ytd-playlist-video-renderer"; var a=$(rendererSelector).filter(function() { if ($(this).attr('aria-label')) { return false; } var meta=$(this).parents("#meta"); if (meta.length==0) { return false; } return true; }).each(function( index, element ) { // element == this var ahref= $(this).children("a.yt-simple-endpoint"); if (ahref.length>0) { var href=ahref.attr("href"); var checked=ahref.attr("filmot_chk"); var id=String(href.match(/v=[0-9A-Za-z_\-]*/gm)); id=id.substring(2); if (id.length>=11 && !(checked)) { ahref.attr("filmot_chk","1"); window.DetectedIDS[id]=1; if (deletedIDs.length>0) { deletedIDs+=","; } deletedIDs+=id; deletedIDsCnt++; } } }); if (deletedIDs.length>0) { window.deletedIDs=deletedIDs; window.deletedIDCnt=deletedIDsCnt; // If there are titles to be restored... if (document.getElementById ("TitleRestoredBtn")==null) { console.log("[Filmot] [DEBUG] There are " + deletedIDsCnt + " titles to restore."); // Add the 'restore titles' button in the playlist info/description pane createRestoreButton(); /* document.getElementById ("TitleRestoredBtn").addEventListener ("click", ButtonClickActionFullView, false); */ } processClick(1,0); } } function reportAJAXError(error) { alert("Error fetching API r###lts " + error); } function rgb2lum(rgb) { // calculate relative luminance of a color provided by rgb() string // black is 0, white is 1 rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); if (rgb.length==4) { var R=parseInt(rgb[1],10)/255.0; var G=parseInt(rgb[2],10)/255.0; var B=parseInt(rgb[3],10)/255.0; return 0.2126*R + 0.7152*G + 0.0722*B; } return 1; } function processJSONR###ltSingleVideo(fetched_details, format) { var darkMode = -1; for (let i = 0; i < fetched_details.length; ++i) { var meta = fetched_details[i]; var escapedTitle = meta.title; // dead channel item = $("div.promo-message").first(); if (item.length === 0) { //location for deleted/privated videos where channel is still alive var item = $("#subreason.yt-player-error-message-renderer").first(); } if (darkMode == -1) { var lum = rgb2lum(item.css("color")); darkMode = (lum > 0.51) ? 1 : 0; //if text is bright it means we are in dark mode } if (!window.RecoveredIDS[meta.id]) { window.RecoveredIDS[meta.id] = 1; if (meta.channelname == null) { meta.channelname = fetched_details[i].channelid; } // Create "Powered by Filmot" link var brEl = document.createElement('br'); item[0].appendChild(brEl); var poweredByFilmot = document.createElement('a'); poweredByFilmot.style.fontSize = 'large'; poweredByFilmot.className = 'yt-simple-endpoint style-scope yt-formatted-string'; poweredByFilmot.href = 'https://filmot.com'; poweredByFilmot.target = '_blank'; poweredByFilmot.textContent = 'Title and Channel from filmot.com'; item[0].appendChild(poweredByFilmot); // Create title link var titleContainer = document.createElement('h2'); titleContainer.textContent = 'Title: '; var titleLink = document.createElement('a'); titleLink.className = 'filmot_c_link yt-simple-endpoint style-scope yt-formatted-string'; titleLink.dir = 'auto'; titleLink.href = 'https://filmot.com/video/' + meta.id; titleLink.textContent = escapedTitle; titleLink.style.color = ((darkMode == 0) ? 'black': 'white'); titleContainer.appendChild(titleLink); item[0].appendChild(titleContainer); // Create channel link var channelContainer = document.createElement('h2'); channelContainer.textContent = 'Channel: '; var channelLink = document.createElement('a'); channelLink.className = 'filmot_c_link yt-simple-endpoint style-scope yt-formatted-string'; channelLink.dir = 'auto'; channelLink.href = 'https://www.youtube.com/channel/' + meta.channelid; channelLink.textContent = meta.channelname; channelContainer.appendChild(channelLink); item[0].appendChild(channelContainer); // Create thumbnail image var newThumb = document.createElement('img'); newThumb.id = 'filmot_newimg'; newThumb.className = 'style-scope yt-img-shadow filmot_newimg'; newThumb.onclick = function(event) { prompt('Full Title', escapedTitle); event.stopPropagation(); return false; }; newThumb.title = escapedTitle; newThumb.width = 320; newThumb.src = 'https://filmot.com/vi/' + meta.id + '/default.jpg'; item[0].appendChild(newThumb); } } } function processJSONR###ltFullView(fetched_details, format) { var darkMode = -1; for (let i = 0; i < fetched_details.length; ++i) { var meta = fetched_details[i]; window.RecoveredIDS[meta.id] = 1; var escapedTitle = meta.title; if (meta.channelname == null) { meta.channelname = fetched_details[i].channelid; } var rendererSelector = "#container.ytd-playlist-video-renderer"; $(rendererSelector).filter(function() { return $(this).find("a.ytd-playlist-video-renderer[href*='" + meta.id + "']").length > 0; }).each(function(index, element) { var item = $(element); item.addClass("filmot_highlight"); var titleItem = item.find("#video-title"); titleItem.text(meta.title); titleItem.attr("title", meta.title); titleItem.attr("aria-label", meta.title); titleItem.addClass("filmot_title"); if (darkMode == -1) { var lum = rgb2lum(titleItem.css("color")); darkMode = (lum > 0.51) ? 1 : 0; //if text is bright it means we are in dark mode } item.css("background-color", (darkMode == 0) ? lightModeBackground : darkModeBackground); var channelItem = titleItem.parent(); channelItem.find("a.filmot_c_link").remove(); // Create a new anchor element var channelLinkElement = document.createElement('a'); channelLinkElement.className = 'filmot_c_link yt-simple-endpoint style-scope yt-formatted-string'; channelLinkElement.dir = 'auto'; channelLinkElement.href = 'https://www.youtube.com/channel/' + meta.channelid; channelLinkElement.textContent = meta.channelname; if (darkMode==1) { channelLinkElement.style.color=darkModeLinkColor; } // Append the new element channelItem[0].appendChild(channelLinkElement); item.find("#byline-container").attr("hidden", false); item.find(".filmot_newimg").remove(); // Create a new image element var newThumbElement = document.createElement('img'); newThumbElement.id = 'filmot_newimg'; newThumbElement.className = 'style-scope yt-img-shadow filmot_newimg'; newThumbElement.width = 100; newThumbElement.src = 'https://filmot.com/vi/' + meta.id + '/default.jpg'; newThumbElement.title = escapedTitle; newThumbElement.onclick = function(event) { prompt('Full Title', escapedTitle); event.stopPropagation(); return false; }; // Append the new image item.find("yt-image")[0].appendChild(newThumbElement); item.find("img.yt-core-image").addClass("filmot_hide").hide(); }); } $("#TitleRestoredBtn").text(Object.keys(window.RecoveredIDS).length + " of " + Object.keys(window.DetectedIDS).length + " restored"); } function processClick(format, nTry) { var maxTries = 2; var apiURL = 'https://filmot.com/api/getvideos?key=md5paNgdbaeudounjp39&id=' + window.deletedIDs; fetch(apiURL) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { if (format == 1) { processJSONR###ltFullView(data, format); } else if (format == 2) { processJSONR###ltSingleVideo(data, format); } }) .catch(error => { if (nTry >= maxTries) { console.error("filmot fetch error:", error); console.error("filmot fetch message:", error.message); console.error("filmot fetch stack:", error.stack); reportAJAXError(apiURL + " " + JSON.stringify(error)); return; } processClick(format, nTry + 1); }) .finally(() => { // This function will be called regardless of success or failure }); } function ButtonClickActionFullView (zEvent) { processClick(2,0); return false; }