Greasy Fork is available in English.

YouTube Shorts URL Conversion Button

Convert YouTube Shorts URL to regular YouTube video URL.

// ==UserScript==
// @name                YouTube Shorts URL Conversion Button
// @name:zh-TW          YouTube Shorts URL 轉換按鈕
// @name:zh-HK          YouTube Shorts URL 轉換按鈕
// @name:ja             YouTube Shorts URL コンバーター
// @name:zh-CN          YouTube Shorts URL 转换按钮
// @name:ko             YouTube Shorts URL 변환 버튼
// @name:ru             Кнопка преобразования URL YouTube Shorts
// @name:de             YouTube Shorts URL Konvertierungstaste
// @name:es             Botón de conversión de URL de YouTube Shorts
// @name:fr             Bouton de conversion d'URL YouTube Shorts
// @name:it             Pulsante di conversione URL YouTube Shorts
// @namespace           http://tampermonkey.net/
// @version             1.7
// @description         Convert YouTube Shorts URL to regular YouTube video URL.
// @description:zh-TW   將 YouTube Shorts 網址轉換為常規的 YouTube 影片網址。
// @description:zh-HK   將 YouTube Shorts 網址轉換為常規的 YouTube 影片網址。
// @description:ja      YouTube Shorts URLを通常のYouTubeビデオURLに変換します。
// @description:zh-CN   将 YouTube Shorts 网址转换为常规的 YouTube 视频网址。
// @description:ko      YouTube Shorts URL을 일반 YouTube 비디오 URL로 변환합니다.
// @description:ru      Преобразование URL YouTube Shorts в обычный URL видео YouTube.
// @description:de      Konvertiere YouTube Shorts URL in reguläre YouTube Video URL.
// @description:es      Convierte la URL de YouTube Shorts en una URL de video de YouTube normal.
// @description:fr      Convertir l'URL YouTube Shorts en URL vidéo YouTube classique.
// @description:it      Converti l'URL YouTube Shorts in URL video YouTube normale.
// @author              鮪魚大師
// @match               https://www.youtube.com/*
// @grant               none
// @license             MIT
// ==/UserScript==

(function() {
    'use strict';

    const debug = false;

    let convertButton;
    let isButtonDown = false;

    const lang = navigator.language;
    const langData = [
        {
            name: "English",
            match: ["en"],
            lang: {
                buttonText: "Convert Shorts",
                buttonTitle: "Convert Shorts URL to regular video URL",
            },
        },
        {
            name: "Chinese (Traditional)",
            match: ["zh-TW", "zh-HK"],
            lang: {
                buttonText: "Shorts轉換",
                buttonTitle: "將Shorts網址轉換成一般影片網址",
            },
        },
        {
            name: "Japanese",
            match: ["ja"],
            lang: {
                buttonText: "Shorts変換",
                buttonTitle: "YouTube Shorts URLを通常のYouTubeビデオURLに変換します",
            },
        },
        {
            name: "Chinese (Simplified)",
            match: ["zh-CN"],
            lang: {
                buttonText: "Shorts转换",
                buttonTitle: "将 YouTube Shorts 网址转换为常规的 YouTube 视频网址",
            },
        },
        {
            name: "Korean",
            match: ["ko"],
            lang: {
                buttonText: "Shorts변환",
                buttonTitle: "YouTube Shorts URL을 일반 YouTube 비디오 URL로 변환합니다",
            },
        },
        {
            name: "Russian",
            match: ["ru"],
            lang: {
                buttonText: "Shorts конвертация",
                buttonTitle: "Преобразование URL YouTube Shorts в обычный URL видео YouTube",
            },
        },
        {
            name: "German",
            match: ["de"],
            lang: {
                buttonText: "Shorts konvertieren",
                buttonTitle: "Konvertiere YouTube Shorts URL in reguläre YouTube Video URL",
            },
        },
        {
            name: "Spanish",
            match: ["es"],
            lang: {
                buttonText: "Convertir Shorts",
                buttonTitle: "Convierte la URL de YouTube Shorts en una URL de video de YouTube normal",
            },
        },
        {
            name: "French",
            match: ["fr"],
            lang: {
                buttonText: "Convertir Shorts",
                buttonTitle: "Convertir l'URL YouTube Shorts en URL vidéo YouTube classique",
            },
        },
        {
            name: "Italian",
            match: ["it"],
            lang: {
                buttonText: "Converti Shorts",
                buttonTitle: "Converti l'URL YouTube Shorts in URL video YouTube normale",
            },
        },
    ];

    function debugLog(message) {
        if (debug) {
            console.log("[DEBUG] " + message);
        }
    }

    function getLanguageData() {
        for (const data of langData) {
            if (data.match.includes(lang)) {
                debugLog(`檢測到的語言: ${data.name}`);
                return data.lang;
            }
        }
        debugLog(`找不到語言,預設為 ${langData[0].name}`);
        return langData[0].lang;
    }

    const languageData = getLanguageData();

    function createConvertButton() {
        if (!convertButton) {
            debugLog("正在創建按鈕...");
            convertButton = document.createElement('button');
            convertButton.textContent = languageData.buttonText;
            convertButton.style.position = 'fixed';
            convertButton.style.top = '150px';
            convertButton.style.right = '10px';
            convertButton.style.zIndex = '9999';
            convertButton.style.backgroundColor = '#FF0000';
            convertButton.style.color = '#FFFFFF';
            convertButton.style.fontSize = '24px';
            convertButton.style.padding = '10px 20px';
            convertButton.style.border = 'none';
            convertButton.style.borderRadius = '5px';
            convertButton.title = languageData.buttonTitle;
            document.body.appendChild(convertButton);

            convertButton.addEventListener('click', convertURL);
            convertButton.addEventListener('auxclick', function(event) {
                convertURL(event);
            });

            convertButton.addEventListener('mousedown', function() {
                convertButton.style.backgroundColor = '#D80000';
                isButtonDown = true;
            });

            convertButton.addEventListener('mouseup', function() {
                convertButton.style.backgroundColor = '#FF0000';
                isButtonDown = false;
            });

            convertButton.addEventListener('mouseout', function() {
                if (!isButtonDown) {
                    convertButton.style.backgroundColor = '#FF0000';
                }
            });

            convertButton.addEventListener('mouseenter', function() {
                convertButton.style.backgroundColor = '#FF3333';
            });

            convertButton.addEventListener('mouseleave', function() {
                convertButton.style.backgroundColor = '#FF0000';
            });

            debugLog("按鈕創建成功");
        }
    }

    function convertURL(event) {
        const currentURL = window.location.href;
        debugLog("當前網址: " + currentURL);

        if (currentURL.includes("youtube.com/shorts/")) {
            const match = currentURL.match(/https:\/\/www\.youtube\.com\/shorts\/([A-Za-z0-9_-]+)/);
            if (match) {
                const videoID = match[1];
                const videoURL = `https://www.youtube.com/watch?v=${videoID}`;
                debugLog(`匹配到的影片ID: ${videoID}`);
                debugLog(`轉換後的網址: ${videoURL}`);

                if (event && event.button === 2) {
                    debugLog("檢測到右鍵點擊(未執行任何操作)");
                } else if (event && event.button === 1) {
                    debugLog("檢測到中鍵點擊(開啟新標籤)");
                    window.open(videoURL, '_blank');
                } else {
                    debugLog("正在導航至轉換後的網址");
                    window.location.href = videoURL;
                }
            } else {
                debugLog("網址中沒有匹配到影片ID");
            }
        } else {
            debugLog("不是 YouTube Shorts 網址");
        }
    }

    function removeConvertButton() {
        if (convertButton) {
            debugLog("正在移除按鈕...");
            convertButton.remove();
            convertButton = null;
            debugLog("按鈕已移除");
        }
    }

    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                timeout = null;
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    const checkAndCreateButton = debounce(function() {
        debugLog("檢查是否應該創建按鈕...");
        if (window.location.href.includes("youtube.com/shorts/")) {
            createConvertButton();
        } else {
            removeConvertButton();
        }
    }, 200); // 防抖200ms

    window.addEventListener('popstate', checkAndCreateButton);

    const observer = new MutationObserver(function(mutationsList) {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList') {
                checkAndCreateButton();
            }
        }
    });

    observer.observe(document.documentElement, { childList: true, subtree: true });

})();