YouTube Shorts URL 轉換按鈕

將 YouTube Shorts 網址轉換為常規的 YouTube 影片網址。

目前為 2024-10-21 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name YouTube Shorts URL Conversion Button
  3. // @name:zh-TW YouTube Shorts URL 轉換按鈕
  4. // @name:zh-HK YouTube Shorts URL 轉換按鈕
  5. // @name:ja YouTube Shorts URL コンバーター
  6. // @name:zh-CN YouTube Shorts URL 转换按钮
  7. // @name:ko YouTube Shorts URL 변환 버튼
  8. // @name:ru Кнопка преобразования URL YouTube Shorts
  9. // @name:de YouTube Shorts URL Konvertierungstaste
  10. // @name:es Botón de conversión de URL de YouTube Shorts
  11. // @name:fr Bouton de conversion d'URL YouTube Shorts
  12. // @name:it Pulsante di conversione URL YouTube Shorts
  13. // @namespace http://tampermonkey.net/
  14. // @version 1.7
  15. // @description Convert YouTube Shorts URL to regular YouTube video URL.
  16. // @description:zh-TW 將 YouTube Shorts 網址轉換為常規的 YouTube 影片網址。
  17. // @description:zh-HK 將 YouTube Shorts 網址轉換為常規的 YouTube 影片網址。
  18. // @description:ja YouTube Shorts URLを通常のYouTubeビデオURLに変換します。
  19. // @description:zh-CN 将 YouTube Shorts 网址转换为常规的 YouTube 视频网址。
  20. // @description:ko YouTube Shorts URL을 일반 YouTube 비디오 URL로 변환합니다.
  21. // @description:ru Преобразование URL YouTube Shorts в обычный URL видео YouTube.
  22. // @description:de Konvertiere YouTube Shorts URL in reguläre YouTube Video URL.
  23. // @description:es Convierte la URL de YouTube Shorts en una URL de video de YouTube normal.
  24. // @description:fr Convertir l'URL YouTube Shorts en URL vidéo YouTube classique.
  25. // @description:it Converti l'URL YouTube Shorts in URL video YouTube normale.
  26. // @author 鮪魚大師
  27. // @match https://www.youtube.com/*
  28. // @grant none
  29. // @license MIT
  30. // ==/UserScript==
  31.  
  32. (function() {
  33. 'use strict';
  34.  
  35. const debug = false;
  36.  
  37. let convertButton;
  38. let isButtonDown = false;
  39.  
  40. const lang = navigator.language;
  41. const langData = [
  42. {
  43. name: "English",
  44. match: ["en"],
  45. lang: {
  46. buttonText: "Convert Shorts",
  47. buttonTitle: "Convert Shorts URL to regular video URL",
  48. },
  49. },
  50. {
  51. name: "Chinese (Traditional)",
  52. match: ["zh-TW", "zh-HK"],
  53. lang: {
  54. buttonText: "Shorts轉換",
  55. buttonTitle: "將Shorts網址轉換成一般影片網址",
  56. },
  57. },
  58. {
  59. name: "Japanese",
  60. match: ["ja"],
  61. lang: {
  62. buttonText: "Shorts変換",
  63. buttonTitle: "YouTube Shorts URLを通常のYouTubeビデオURLに変換します",
  64. },
  65. },
  66. {
  67. name: "Chinese (Simplified)",
  68. match: ["zh-CN"],
  69. lang: {
  70. buttonText: "Shorts转换",
  71. buttonTitle: "将 YouTube Shorts 网址转换为常规的 YouTube 视频网址",
  72. },
  73. },
  74. {
  75. name: "Korean",
  76. match: ["ko"],
  77. lang: {
  78. buttonText: "Shorts변환",
  79. buttonTitle: "YouTube Shorts URL을 일반 YouTube 비디오 URL로 변환합니다",
  80. },
  81. },
  82. {
  83. name: "Russian",
  84. match: ["ru"],
  85. lang: {
  86. buttonText: "Shorts конвертация",
  87. buttonTitle: "Преобразование URL YouTube Shorts в обычный URL видео YouTube",
  88. },
  89. },
  90. {
  91. name: "German",
  92. match: ["de"],
  93. lang: {
  94. buttonText: "Shorts konvertieren",
  95. buttonTitle: "Konvertiere YouTube Shorts URL in reguläre YouTube Video URL",
  96. },
  97. },
  98. {
  99. name: "Spanish",
  100. match: ["es"],
  101. lang: {
  102. buttonText: "Convertir Shorts",
  103. buttonTitle: "Convierte la URL de YouTube Shorts en una URL de video de YouTube normal",
  104. },
  105. },
  106. {
  107. name: "French",
  108. match: ["fr"],
  109. lang: {
  110. buttonText: "Convertir Shorts",
  111. buttonTitle: "Convertir l'URL YouTube Shorts en URL vidéo YouTube classique",
  112. },
  113. },
  114. {
  115. name: "Italian",
  116. match: ["it"],
  117. lang: {
  118. buttonText: "Converti Shorts",
  119. buttonTitle: "Converti l'URL YouTube Shorts in URL video YouTube normale",
  120. },
  121. },
  122. ];
  123.  
  124. function debugLog(message) {
  125. if (debug) {
  126. console.log("[DEBUG] " + message);
  127. }
  128. }
  129.  
  130. function getLanguageData() {
  131. for (const data of langData) {
  132. if (data.match.includes(lang)) {
  133. debugLog(`檢測到的語言: ${data.name}`);
  134. return data.lang;
  135. }
  136. }
  137. debugLog(`找不到語言,預設為 ${langData[0].name}`);
  138. return langData[0].lang;
  139. }
  140.  
  141. const languageData = getLanguageData();
  142.  
  143. function createConvertButton() {
  144. if (!convertButton) {
  145. debugLog("正在創建按鈕...");
  146. convertButton = document.createElement('button');
  147. convertButton.textContent = languageData.buttonText;
  148. convertButton.style.position = 'fixed';
  149. convertButton.style.top = '150px';
  150. convertButton.style.right = '10px';
  151. convertButton.style.zIndex = '9999';
  152. convertButton.style.backgroundColor = '#FF0000';
  153. convertButton.style.color = '#FFFFFF';
  154. convertButton.style.fontSize = '24px';
  155. convertButton.style.padding = '10px 20px';
  156. convertButton.style.border = 'none';
  157. convertButton.style.borderRadius = '5px';
  158. convertButton.title = languageData.buttonTitle;
  159. document.body.appendChild(convertButton);
  160.  
  161. convertButton.addEventListener('click', convertURL);
  162. convertButton.addEventListener('auxclick', function(event) {
  163. convertURL(event);
  164. });
  165.  
  166. convertButton.addEventListener('mousedown', function() {
  167. convertButton.style.backgroundColor = '#D80000';
  168. isButtonDown = true;
  169. });
  170.  
  171. convertButton.addEventListener('mouseup', function() {
  172. convertButton.style.backgroundColor = '#FF0000';
  173. isButtonDown = false;
  174. });
  175.  
  176. convertButton.addEventListener('mouseout', function() {
  177. if (!isButtonDown) {
  178. convertButton.style.backgroundColor = '#FF0000';
  179. }
  180. });
  181.  
  182. convertButton.addEventListener('mouseenter', function() {
  183. convertButton.style.backgroundColor = '#FF3333';
  184. });
  185.  
  186. convertButton.addEventListener('mouseleave', function() {
  187. convertButton.style.backgroundColor = '#FF0000';
  188. });
  189.  
  190. debugLog("按鈕創建成功");
  191. }
  192. }
  193.  
  194. function convertURL(event) {
  195. const currentURL = window.location.href;
  196. debugLog("當前網址: " + currentURL);
  197.  
  198. if (currentURL.includes("youtube.com/shorts/")) {
  199. const match = currentURL.match(/https:\/\/www\.youtube\.com\/shorts\/([A-Za-z0-9_-]+)/);
  200. if (match) {
  201. const videoID = match[1];
  202. const videoURL = `https://www.youtube.com/watch?v=${videoID}`;
  203. debugLog(`匹配到的影片ID: ${videoID}`);
  204. debugLog(`轉換後的網址: ${videoURL}`);
  205.  
  206. if (event && event.button === 2) {
  207. debugLog("檢測到右鍵點擊(未執行任何操作)");
  208. } else if (event && event.button === 1) {
  209. debugLog("檢測到中鍵點擊(開啟新標籤)");
  210. window.open(videoURL, '_blank');
  211. } else {
  212. debugLog("正在導航至轉換後的網址");
  213. window.location.href = videoURL;
  214. }
  215. } else {
  216. debugLog("網址中沒有匹配到影片ID");
  217. }
  218. } else {
  219. debugLog("不是 YouTube Shorts 網址");
  220. }
  221. }
  222.  
  223. function removeConvertButton() {
  224. if (convertButton) {
  225. debugLog("正在移除按鈕...");
  226. convertButton.remove();
  227. convertButton = null;
  228. debugLog("按鈕已移除");
  229. }
  230. }
  231.  
  232. function debounce(func, wait) {
  233. let timeout;
  234. return function executedFunction(...args) {
  235. const later = () => {
  236. timeout = null;
  237. func(...args);
  238. };
  239. clearTimeout(timeout);
  240. timeout = setTimeout(later, wait);
  241. };
  242. }
  243.  
  244. const checkAndCreateButton = debounce(function() {
  245. debugLog("檢查是否應該創建按鈕...");
  246. if (window.location.href.includes("youtube.com/shorts/")) {
  247. createConvertButton();
  248. } else {
  249. removeConvertButton();
  250. }
  251. }, 200); // 防抖200ms
  252.  
  253. window.addEventListener('popstate', checkAndCreateButton);
  254.  
  255. const observer = new MutationObserver(function(mutationsList) {
  256. for (const mutation of mutationsList) {
  257. if (mutation.type === 'childList') {
  258. checkAndCreateButton();
  259. }
  260. }
  261. });
  262.  
  263. observer.observe(document.documentElement, { childList: true, subtree: true });
  264.  
  265. })();