YouTube Shorts URL 转换按钮

将 YouTube Shorts 网址转换为常规的 YouTube 视频网址。

当前为 2025-02-24 提交的版本,查看 最新版本

  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.8
  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 resizeButton;
  39. let isButtonDown = false;
  40. let buttonSize = "medium"; // 預設大小 目前只有"medium"跟"small"
  41.  
  42. const lang = navigator.language;
  43. const langData = [
  44. {
  45. name: "English",
  46. match: ["en"],
  47. lang: {
  48. buttonText: "Convert Shorts",
  49. buttonTitle: "Convert Shorts URL to regular video URL",
  50. resizeButtonTitle: "Resize the buttons",
  51. },
  52. },
  53. {
  54. name: "Chinese (Traditional)",
  55. match: ["zh-TW", "zh-HK"],
  56. lang: {
  57. buttonText: "Shorts轉換",
  58. buttonTitle: "將Shorts網址轉換成一般影片網址",
  59. resizeButtonTitle: "調整按鈕大小",
  60. },
  61. },
  62. {
  63. name: "Japanese",
  64. match: ["ja"],
  65. lang: {
  66. buttonText: "Shorts変換",
  67. buttonTitle: "YouTube Shorts URLを通常のYouTubeビデオURLに変換します",
  68. resizeButtonTitle: "ボタンサイズを変更する",
  69. },
  70. },
  71. {
  72. name: "Chinese (Simplified)",
  73. match: ["zh-CN"],
  74. lang: {
  75. buttonText: "Shorts转换",
  76. buttonTitle: "将 YouTube Shorts 网址转换为常规的 YouTube 视频网址",
  77. resizeButtonTitle: "调整按钮大小",
  78. },
  79. },
  80. {
  81. name: "Korean",
  82. match: ["ko"],
  83. lang: {
  84. buttonText: "Shorts변환",
  85. buttonTitle: "YouTube Shorts URL을 일반 YouTube 비디오 URL로 변환합니다",
  86. resizeButtonTitle: "버튼 크기 조정",
  87. },
  88. },
  89. {
  90. name: "Russian",
  91. match: ["ru"],
  92. lang: {
  93. buttonText: "Shorts конвертация",
  94. buttonTitle: "Преобразование URL YouTube Shorts в обычный URL видео YouTube",
  95. resizeButtonTitle: "Изменить размер кнопок",
  96. },
  97. },
  98. {
  99. name: "German",
  100. match: ["de"],
  101. lang: {
  102. buttonText: "Shorts konvertieren",
  103. buttonTitle: "Konvertiere YouTube Shorts URL in reguläre YouTube Video URL",
  104. resizeButtonTitle: "Größe der Schaltflächen ändern",
  105. },
  106. },
  107. {
  108. name: "Spanish",
  109. match: ["es"],
  110. lang: {
  111. buttonText: "Convertir Shorts",
  112. buttonTitle: "Convierte la URL de YouTube Shorts en una URL de video de YouTube normal",
  113. resizeButtonTitle: "Cambiar el tamaño de los botones",
  114. },
  115. },
  116. {
  117. name: "French",
  118. match: ["fr"],
  119. lang: {
  120. buttonText: "Convertir Shorts",
  121. buttonTitle: "Convertir l'URL YouTube Shorts en URL vidéo YouTube classique",
  122. resizeButtonTitle: "Redimensionner les boutons",
  123. },
  124. },
  125. {
  126. name: "Italian",
  127. match: ["it"],
  128. lang: {
  129. buttonText: "Converti Shorts",
  130. buttonTitle: "Converti l'URL YouTube Shorts in URL video YouTube normale",
  131. resizeButtonTitle: "Ridimensiona i pulsanti",
  132. },
  133. },
  134. ];
  135.  
  136. function debugLog(message) {
  137. if (debug) {
  138. console.log("[DEBUG] " + message);
  139. }
  140. }
  141.  
  142. function getLanguageData() {
  143. for (const data of langData) {
  144. if (data.match.includes(lang)) {
  145. debugLog(`檢測到的語言: ${data.name}`);
  146. return data.lang;
  147. }
  148. }
  149. debugLog(`找不到語言,預設為 ${langData[0].name}`);
  150. return langData[0].lang;
  151. }
  152.  
  153. const languageData = getLanguageData();
  154.  
  155. // 根據 'buttonSize' 變數更新按鈕大小
  156. function updateButtonSize() {
  157. if (convertButton) {
  158. convertButton.style.fontSize = buttonSize === "small" ? "14px" : "24px";
  159. convertButton.style.padding = buttonSize === "small" ? "5px 10px" : "10px 20px";
  160. }
  161. }
  162.  
  163. // 建立 Convert Button 和 Resize Button
  164. function createButtons() {
  165. // Convert 按鈕
  166. if (!convertButton) {
  167. debugLog("正在創建 Convert 按鈕...");
  168. convertButton = document.createElement('button');
  169. convertButton.textContent = languageData.buttonText;
  170. convertButton.style.position = 'fixed';
  171. convertButton.style.top = '150px';
  172. convertButton.style.right = '10px';
  173. convertButton.style.zIndex = '9999';
  174. convertButton.style.backgroundColor = '#FF0000';
  175. convertButton.style.color = '#FFFFFF';
  176. convertButton.style.fontSize = '24px';
  177. convertButton.style.padding = '10px 20px';
  178. convertButton.style.border = 'none';
  179. convertButton.style.borderRadius = '5px';
  180. convertButton.title = languageData.buttonTitle;
  181. document.body.appendChild(convertButton);
  182. updateButtonSize();
  183.  
  184. convertButton.addEventListener('click', convertURL);
  185. convertButton.addEventListener('auxclick', function(event) {
  186. convertURL(event);
  187. });
  188.  
  189. convertButton.addEventListener('mousedown', function() {
  190. convertButton.style.backgroundColor = '#D80000';
  191. isButtonDown = true;
  192. });
  193.  
  194. convertButton.addEventListener('mouseup', function() {
  195. convertButton.style.backgroundColor = '#FF0000';
  196. isButtonDown = false;
  197. });
  198.  
  199. convertButton.addEventListener('mouseout', function() {
  200. if (!isButtonDown) {
  201. convertButton.style.backgroundColor = '#FF0000';
  202. }
  203. });
  204.  
  205. convertButton.addEventListener('mouseenter', function() {
  206. convertButton.style.backgroundColor = '#FF3333';
  207. });
  208.  
  209. convertButton.addEventListener('mouseleave', function() {
  210. convertButton.style.backgroundColor = '#FF0000';
  211. });
  212.  
  213. debugLog("按鈕創建成功");
  214. }
  215.  
  216. // Resize 按鈕
  217. if (!resizeButton) {
  218. debugLog("正在創建 Resize 按鈕...");
  219. resizeButton = document.createElement('button');
  220. resizeButton.textContent = "◱";
  221. resizeButton.style.position = 'fixed';
  222. resizeButton.style.right = '10px';
  223. resizeButton.style.top = (convertButton.getBoundingClientRect().bottom + 5) + 'px';
  224. resizeButton.style.zIndex = '9999';
  225. resizeButton.style.backgroundColor = '#008CBA';
  226. resizeButton.style.color = '#FFFFFF';
  227. resizeButton.style.fontSize = '18px';
  228. resizeButton.style.width = '25px';
  229. resizeButton.style.height = '25px';
  230. resizeButton.style.display = 'flex';
  231. resizeButton.style.justifyContent = 'center';
  232. resizeButton.style.alignItems = 'center';
  233. resizeButton.style.border = 'none';
  234. resizeButton.style.borderRadius = '5px';
  235. resizeButton.title = languageData.resizeButtonTitle;
  236. document.body.appendChild(resizeButton);
  237.  
  238. resizeButton.addEventListener('click', () => {
  239. buttonSize = buttonSize === "small" ? "medium" : "small";
  240. updateButtonSize();
  241. resizeButton.style.top = (convertButton.getBoundingClientRect().bottom + 5) + 'px';
  242. });
  243.  
  244. resizeButton.addEventListener('mouseenter', function() {
  245. resizeButton.style.backgroundColor = '#00bcd4';
  246. });
  247.  
  248. resizeButton.addEventListener('mouseleave', function() {
  249. resizeButton.style.backgroundColor = '#008CBA';
  250. });
  251. }
  252. }
  253.  
  254. //主要功能
  255. function convertURL(event) {
  256. const currentURL = window.location.href;
  257. debugLog("當前網址: " + currentURL);
  258.  
  259. if (currentURL.includes("youtube.com/shorts/")) {
  260. const match = currentURL.match(/https:\/\/www\.youtube\.com\/shorts\/([A-Za-z0-9_-]+)/);
  261. if (match) {
  262. const videoID = match[1];
  263. const videoURL = `https://www.youtube.com/watch?v=${videoID}`;
  264. debugLog(`匹配到的影片ID: ${videoID}`);
  265. debugLog(`轉換後的網址: ${videoURL}`);
  266.  
  267. if (event && event.button === 2) {
  268. debugLog("檢測到右鍵點擊(未執行任何操作)");
  269. } else if (event && event.button === 1) {
  270. debugLog("檢測到中鍵點擊(開啟新標籤)");
  271. window.open(videoURL, '_blank');
  272. } else {
  273. debugLog("正在導航至轉換後的網址");
  274. window.location.href = videoURL;
  275. }
  276. } else {
  277. debugLog("網址中沒有匹配到影片ID");
  278. }
  279. } else {
  280. debugLog("不是 YouTube Shorts 網址");
  281. }
  282. }
  283.  
  284. function removeConvertButton() {
  285. if (convertButton) {
  286. debugLog("正在移除按鈕...");
  287. convertButton.remove();
  288. convertButton = null;
  289. debugLog("按鈕已移除");
  290. }
  291. }
  292.  
  293. function debounce(func, wait) {
  294. let timeout;
  295. return function executedFunction(...args) {
  296. const later = () => {
  297. timeout = null;
  298. func(...args);
  299. };
  300. clearTimeout(timeout);
  301. timeout = setTimeout(later, wait);
  302. };
  303. }
  304.  
  305. const checkAndCreateButton = debounce(function() {
  306. debugLog("檢查是否應該創建按鈕...");
  307. if (window.location.href.includes("youtube.com/shorts/")) {
  308. createButtons();
  309. } else {
  310. removeConvertButton();
  311. }
  312. }, 200); // 防抖200ms
  313.  
  314. window.addEventListener('popstate', checkAndCreateButton);
  315.  
  316. const observer = new MutationObserver(function(mutationsList) {
  317. for (const mutation of mutationsList) {
  318. if (mutation.type === 'childList') {
  319. checkAndCreateButton();
  320. }
  321. }
  322. });
  323.  
  324. observer.observe(document.documentElement, { childList: true, subtree: true });
  325.  
  326. })();