Greasy Fork 支持 简体中文。

Youtube Auto-translate Canceler

Remove auto-translated youtube titles

  1. // ==UserScript==
  2. // @name Youtube Auto-translate Canceler
  3. // @namespace https://github.com/pcouy/YoutubeAutotranslateCanceler/
  4. // @version 0.4
  5. // @description Remove auto-translated youtube titles
  6. // @author Pierre Couy
  7. // @match https://www.youtube.com/*
  8. // @grant GM.setValue
  9. // @grant GM.getValue
  10. // @grant GM.deleteValue
  11. // ==/UserScript==
  12.  
  13. (async () => {
  14. 'use strict';
  15.  
  16. /*
  17. Get a YouTube Data v3 API key from https://console.developers.google.com/apis/library/youtube.googleapis.com?q=YoutubeData
  18. */
  19. var NO_API_KEY = false;
  20. var api_key_awaited = await GM.getValue("api_key");
  21. if(api_key_awaited === undefined || api_key_awaited === null || api_key_awaited === ""){
  22. 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."));
  23. }
  24. var api_key_awaited = await GM.getValue("api_key");
  25. if(api_key_awaited === undefined || api_key_awaited === null || api_key_awaited === ""){
  26. NO_API_KEY = true; // Resets after page reload, still allows local title to be replaced
  27. console.log("NO API KEY PRESENT");
  28. }
  29. const API_KEY = await GM.getValue("api_key");
  30. var API_KEY_VALID = false;
  31. console.log(API_KEY);
  32.  
  33. var url_template = "https://www.googleapis.com/youtube/v3/videos?part=snippet&id={IDs}&key=" + API_KEY;
  34.  
  35. var cachedTitles = {} // Dictionary(id, title): Cache of API fetches, survives only Youtube Autoplay
  36.  
  37. var currentLocation; // String: Current page URL
  38. var changedDescription; // Bool: Changed description
  39. var alreadyChanged; // List(string): Links already changed
  40.  
  41. function getVideoID(a)
  42. {
  43. while(a.tagName != "A"){
  44. a = a.parentNode;
  45. }
  46. var href = a.href;
  47. var tmp = href.split('v=')[1];
  48. return tmp.split('&')[0];
  49. }
  50.  
  51. function resetChanged(){
  52. console.log(" --- Page Change detected! --- ");
  53. currentLocation = document.title;
  54. changedDescription = false;
  55. alreadyChanged = [];
  56. }
  57. resetChanged();
  58.  
  59. function changeTitles(){
  60. if(currentLocation !== document.title) resetChanged();
  61.  
  62. // MAIN TITLE - no API key required
  63. if (window.location.href.includes ("/watch")){
  64. var titleMatch = document.title.match (/^(?:\([0-9]+\) )?(.*?)(?: - YouTube)$/); // ("(n) ") + "TITLE - YouTube"
  65. var pageTitle = document.getElementsByClassName("title style-scope ytd-video-primary-info-renderer");
  66. if (pageTitle.length > 0 && pageTitle[0] !== undefined && titleMatch != null) {
  67. if (pageTitle[0].innerText != titleMatch[1]){
  68. console.log ("Reverting main video title '" + pageTitle[0].innerText + "' to '" + titleMatch[1] + "'");
  69. pageTitle[0].innerText = titleMatch[1];
  70. }
  71. }
  72. }
  73.  
  74. if (NO_API_KEY) {
  75. return;
  76. }
  77.  
  78. var APIcallIDs;
  79.  
  80. // REFERENCED VIDEO TITLES - find video link elements in the page that have not yet been changed
  81. var links = Array.prototype.slice.call(document.getElementsByTagName("a")).filter( a => {
  82. return a.id == 'video-title' && alreadyChanged.indexOf(a) == -1;
  83. } );
  84. var spans = Array.prototype.slice.call(document.getElementsByTagName("span")).filter( a => {
  85. return a.id == 'video-title'
  86. && !a.className.includes("-radio-")
  87. && !a.className.includes("-playlist-")
  88. && alreadyChanged.indexOf(a) == -1;
  89. } );
  90. links = links.concat(spans).slice(0,30);
  91.  
  92. // MAIN VIDEO DESCRIPTION - request to load original video description
  93. var mainVidID = "";
  94. if (!changedDescription && window.location.href.includes ("/watch")){
  95. mainVidID = window.location.href.split('v=')[1].split('&')[0];
  96. }
  97.  
  98. if(mainVidID != "" || links.length > 0)
  99. { // Initiate API request
  100.  
  101. console.log("Checking " + (mainVidID != ""? "main video and " : "") + links.length + " video titles!");
  102.  
  103. // Get all videoIDs to put in the API request
  104. var IDs = links.map( a => getVideoID (a));
  105. var APIFetchIDs = IDs.filter(id => cachedTitles[id] === undefined);
  106. var requestUrl = url_template.replace("{IDs}", (mainVidID != ""? (mainVidID + ",") : "") + APIFetchIDs.join(','));
  107.  
  108. // Issue API request
  109. var xhr = new XMLHttpRequest();
  110. xhr.onreadystatechange = function ()
  111. {
  112. if (xhr.readyState === 4)
  113. { // Success
  114. var data = JSON.parse(xhr.responseText);
  115.  
  116. if(data.kind == "youtube#videoListResponse")
  117. {
  118. API_KEY_VALID = true;
  119.  
  120. data = data.items;
  121.  
  122. if (mainVidID != "")
  123. { // Replace Main Video Description
  124. var videoDescription = data[0].snippet.description;
  125. var pageDescription = document.getElementsByClassName("content style-scope ytd-video-secondary-info-renderer");
  126. if (pageDescription.length > 0 && videoDescription != null && pageDescription[0] !== undefined) {
  127. // linkify replaces links correctly, but without redirect or other specific youtube stuff (no problem if missing)
  128. // Still critical, since it replaces ALL descriptions, even if it was not translated in the first place (no easy comparision possible)
  129. pageDescription[0].innerHTML = linkify(videoDescription);
  130. console.log ("Reverting main video description!");
  131. changedDescription = true;
  132. }
  133. else console.log ("Failed to find main video description!");
  134. }
  135.  
  136. // Create dictionary for all IDs and their original titles
  137. data = data.forEach( v => {
  138. cachedTitles[v.id] = v.snippet.title;
  139. } );
  140.  
  141. // Change all previously found link elements
  142. for(var i=0 ; i < links.length ; i++){
  143. var curID = getVideoID(links[i]);
  144. if (curID !== IDs[i]) { // Can happen when Youtube was still loading when script was invoked
  145. console.log ("YouTube was too slow again...");
  146. changedDescription = false; // Might not have been loaded aswell - fixes rare errors
  147. }
  148. if (cachedTitles[curID] !== undefined)
  149. {
  150. var originalTitle = cachedTitles[curID];
  151. var pageTitle = links[i].innerText.trim();
  152. if(pageTitle != originalTitle.replace(/\s{2,}/g, ' '))
  153. {
  154. console.log ("'" + pageTitle + "' --> '" + originalTitle + "'");
  155. links[i].innerText = originalTitle;
  156. }
  157. alreadyChanged.push(links[i]);
  158. }
  159. }
  160. }
  161. else
  162. {
  163. console.log("API Request Failed!");
  164. console.log(requestUrl);
  165. console.log(data);
  166.  
  167. // This ensures that occasional fails don't stall the script
  168. // But if the first query is a fail then it won't try repeatedly
  169. NO_API_KEY = !API_KEY_VALID;
  170. if (NO_API_KEY) {
  171. GM_setValue('api_key', '');
  172. console.log("API Key Fail! Please Reload!");
  173. }
  174. }
  175. }
  176. };
  177. xhr.open('GET', requestUrl);
  178. xhr.send();
  179.  
  180. }
  181. }
  182.  
  183. function linkify(inputText) {
  184. var replacedText, replacePattern1, replacePattern2, replacePattern3;
  185.  
  186. //URLs starting with http://, https://, or ftp://
  187. replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
  188. replacedText = inputText.replace(replacePattern1, '<a class="yt-simple-endpoint style-scope yt-formatted-string" spellcheck="false" href="$1">$1</a>');
  189.  
  190.  
  191. //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
  192. replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
  193. replacedText = replacedText.replace(replacePattern2, '<a class="yt-simple-endpoint style-scope yt-formatted-string" spellcheck="false" href="http://$1">$1</a>');
  194.  
  195. //Change email addresses to mailto:: links.
  196. replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
  197. replacedText = replacedText.replace(replacePattern3, '<a class="yt-simple-endpoint style-scope yt-formatted-string" spellcheck="false" href="mailto:$1">$1</a>');
  198.  
  199. return replacedText;
  200. }
  201.  
  202. // Execute every seconds in case new content has been added to the page
  203. // DOM listener would be good if it was not for the fact that Youtube changes its DOM frequently
  204. setInterval(changeTitles, 1000);
  205. })();
  206.