// ==UserScript==
// @name Youtube Auto-translate Canceler
// @namespace https://github.com/pcouy/YoutubeAutotranslateCanceler/
// @version 0.4
// @description Remove auto-translated youtube titles
// @author Pierre Couy
// @match https://www.youtube.com/*
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.deleteValue
// @downloadURL https://update.greasyfork.icu/scripts/374453/Youtube%20Auto-translate%20Canceler.user.js
// @updateURL https://update.greasyfork.icu/scripts/374453/Youtube%20Auto-translate%20Canceler.meta.js
// ==/UserScript==
(async () => {
'use strict';
/*
Get a YouTube Data v3 API key from https://console.developers.google.com/apis/library/youtube.googleapis.com?q=YoutubeData
*/
var NO_API_KEY = false;
var api_key_awaited = await GM.getValue("api_key");
if(api_key_awaited === undefined || api_key_awaited === null || api_key_awaited === ""){
await GM.setValue("api_key", prompt("Enter your API key. Go to https://developers.google.com/youtube/v3/getting-started to know how to obtain an API key, then go to https://console.developers.google.com/apis/api/youtube.googleapis.com/ in order to enable Youtube Data API for your key."));
}
var api_key_awaited = await GM.getValue("api_key");
if(api_key_awaited === undefined || api_key_awaited === null || api_key_awaited === ""){
NO_API_KEY = true; // Resets after page reload, still allows local title to be replaced
console.log("NO API KEY PRESENT");
}
const API_KEY = await GM.getValue("api_key");
var API_KEY_VALID = false;
console.log(API_KEY);
var url_template = "https://www.googleapis.com/youtube/v3/videos?part=snippet&id={IDs}&key=" + API_KEY;
var cachedTitles = {} // Dictionary(id, title): Cache of API fetches, survives only Youtube Autoplay
var currentLocation; // String: Current page URL
var changedDescription; // Bool: Changed description
var alreadyChanged; // List(string): Links already changed
function getVideoID(a)
{
while(a.tagName != "A"){
a = a.parentNode;
}
var href = a.href;
var tmp = href.split('v=')[1];
return tmp.split('&')[0];
}
function resetChanged(){
console.log(" --- Page Change detected! --- ");
currentLocation = document.title;
changedDescription = false;
alreadyChanged = [];
}
resetChanged();
function changeTitles(){
if(currentLocation !== document.title) resetChanged();
// MAIN TITLE - no API key required
if (window.location.href.includes ("/watch")){
var titleMatch = document.title.match (/^(?:\([0-9]+\) )?(.*?)(?: - YouTube)$/); // ("(n) ") + "TITLE - YouTube"
var pageTitle = document.getElementsByClassName("title style-scope ytd-video-primary-info-renderer");
if (pageTitle.length > 0 && pageTitle[0] !== undefined && titleMatch != null) {
if (pageTitle[0].innerText != titleMatch[1]){
console.log ("Reverting main video title '" + pageTitle[0].innerText + "' to '" + titleMatch[1] + "'");
pageTitle[0].innerText = titleMatch[1];
}
}
}
if (NO_API_KEY) {
return;
}
var APIcallIDs;
// REFERENCED VIDEO TITLES - find video link elements in the page that have not yet been changed
var links = Array.prototype.slice.call(document.getElementsByTagName("a")).filter( a => {
return a.id == 'video-title' && alreadyChanged.indexOf(a) == -1;
} );
var spans = Array.prototype.slice.call(document.getElementsByTagName("span")).filter( a => {
return a.id == 'video-title'
&& !a.className.includes("-radio-")
&& !a.className.includes("-playlist-")
&& alreadyChanged.indexOf(a) == -1;
} );
links = links.concat(spans).slice(0,30);
// MAIN VIDEO DESCRIPTION - request to load original video description
var mainVidID = "";
if (!changedDescription && window.location.href.includes ("/watch")){
mainVidID = window.location.href.split('v=')[1].split('&')[0];
}
if(mainVidID != "" || links.length > 0)
{ // Initiate API request
console.log("Checking " + (mainVidID != ""? "main video and " : "") + links.length + " video titles!");
// Get all videoIDs to put in the API request
var IDs = links.map( a => getVideoID (a));
var APIFetchIDs = IDs.filter(id => cachedTitles[id] === undefined);
var requestUrl = url_template.replace("{IDs}", (mainVidID != ""? (mainVidID + ",") : "") + APIFetchIDs.join(','));
// Issue API request
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function ()
{
if (xhr.readyState === 4)
{ // Success
var data = JSON.parse(xhr.responseText);
if(data.kind == "youtube#videoListResponse")
{
API_KEY_VALID = true;
data = data.items;
if (mainVidID != "")
{ // Replace Main Video Description
var videoDescription = data[0].snippet.description;
var pageDescription = document.getElementsByClassName("content style-scope ytd-video-secondary-info-renderer");
if (pageDescription.length > 0 && videoDescription != null && pageDescription[0] !== undefined) {
// linkify replaces links correctly, but without redirect or other specific youtube stuff (no problem if missing)
// Still critical, since it replaces ALL descriptions, even if it was not translated in the first place (no easy comparision possible)
pageDescription[0].innerHTML = linkify(videoDescription);
console.log ("Reverting main video description!");
changedDescription = true;
}
else console.log ("Failed to find main video description!");
}
// Create dictionary for all IDs and their original titles
data = data.forEach( v => {
cachedTitles[v.id] = v.snippet.title;
} );
// Change all previously found link elements
for(var i=0 ; i < links.length ; i++){
var curID = getVideoID(links[i]);
if (curID !== IDs[i]) { // Can happen when Youtube was still loading when script was invoked
console.log ("YouTube was too slow again...");
changedDescription = false; // Might not have been loaded aswell - fixes rare errors
}
if (cachedTitles[curID] !== undefined)
{
var originalTitle = cachedTitles[curID];
var pageTitle = links[i].innerText.trim();
if(pageTitle != originalTitle.replace(/\s{2,}/g, ' '))
{
console.log ("'" + pageTitle + "' --> '" + originalTitle + "'");
links[i].innerText = originalTitle;
}
alreadyChanged.push(links[i]);
}
}
}
else
{
console.log("API Request Failed!");
console.log(requestUrl);
console.log(data);
// This ensures that occasional fails don't stall the script
// But if the first query is a fail then it won't try repeatedly
NO_API_KEY = !API_KEY_VALID;
if (NO_API_KEY) {
GM_setValue('api_key', '');
console.log("API Key Fail! Please Reload!");
}
}
}
};
xhr.open('GET', requestUrl);
xhr.send();
}
}
function linkify(inputText) {
var replacedText, replacePattern1, replacePattern2, replacePattern3;
//URLs starting with http://, https://, or ftp://
replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = inputText.replace(replacePattern1, '$1');
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, '$1');
//Change email addresses to mailto:: links.
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '$1');
return replacedText;
}
// Execute every seconds in case new content has been added to the page
// DOM listener would be good if it was not for the fact that Youtube changes its DOM frequently
setInterval(changeTitles, 1000);
})();