SkipCut PowerTools

SkipCut PowerTools – Minimal/Full UI + Fast Invidious Buttons

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         SkipCut PowerTools
// @name:en      SkipCut PowerTools
// @name:nl      SkipCut PowerTools
// @name:es      Herramientas Avanzadas de SkipCut
// @name:fr      Outils Avancés SkipCut
// @name:de      SkipCut PowerTools
// @name:zh-CN   SkipCut 高级工具
// @name:ja      SkipCut パワーツール
// @name:ru      Инструменты SkipCut
// @name:pt      Ferramentas Avançadas SkipCut
// @name:it      Strumenti Avanzati SkipCut
// @name:ko      SkipCut 파워툴
// @namespace    https://greasyfork.org/users/1197317-opus-x
// @version      1.04
// @description  SkipCut PowerTools – Minimal/Full UI + Fast Invidious Buttons
// @description:en SkipCut PowerTools – Minimal/Full UI + Fast Invidious Buttons
// @description:nl SkipCut PowerTools – Minimale/Volledige UI + Snelle Invidious Knoppen
// @description:es Herramientas Avanzadas de SkipCut – Interfaz Mínima/Completa + Botones Rápidos para Invidious
// @description:fr Outils Avancés SkipCut – Interface Minimale/Complète + Boutons Rapides pour Invidious
// @description:de SkipCut PowerTools – Minimales/Volles UI + Schnelle Invidious-Schaltflächen
// @description:zh-CN SkipCut 高级工具 - 极简/完整用户界面 + 快速 Invidious 按钮
// @description:ja SkipCut パワーツール - ミニマル/フル UI + 高速 Invidious ボタン
// @description:ru Инструменты SkipCut – Минимальный/Полный интерфейс + Быстрые кнопки для Invidious
// @description:pt Ferramentas Avançadas SkipCut – Interface Mínima/Completa + Botões Rápidos para Invidious
// @description:it Strumenti Avanzati SkipCut – Interfaccia Minimale/Completa + Pulsanti Rapidi per Invidious
// @description:ko SkipCut 파워툴 - 최소/완전 UI + 빠른 Invidious 버튼
// @author       Opus-X
// @license      MIT
// @match        https://skipcut.com/*
// @match        https://www.skipcut.com/*
// @run-at       document-start
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      *
// ==/UserScript==

(function () {
    'use strict';

    // ---------------------------
    // Localization
    // ---------------------------
    const translations = {
        'en': {
            openInvidious: 'Open Invidious',
            refreshMirrors: 'Refresh mirrors',
            minimalUI: 'Minimal UI',
            fullUI: 'Full UI',
            checkingMirrors: 'Checking mirrors…',
            reChecking: 'Re-checking…',
            invidiousOK: 'Invidious OK',
            noMirrors: 'No mirrors available'
        },
        'nl': {
            openInvidious: 'Open Invidious',
            refreshMirrors: 'Spiegels vernieuwen',
            minimalUI: 'Minimale UI',
            fullUI: 'Volledige UI',
            checkingMirrors: 'Spiegels controleren…',
            reChecking: 'Opnieuw controleren…',
            invidiousOK: 'Invidious OK',
            noMirrors: 'Geen spiegels beschikbaar'
        },
        'es': {
            openInvidious: 'Abrir Invidious',
            refreshMirrors: 'Actualizar espejos',
            minimalUI: 'Interfaz Mínima',
            fullUI: 'Interfaz Completa',
            checkingMirrors: 'Comprobando espejos…',
            reChecking: 'Volviendo a comprobar…',
            invidiousOK: 'Invidious OK',
            noMirrors: 'No hay espejos disponibles'
        },
        'fr': {
            openInvidious: 'Ouvrir Invidious',
            refreshMirrors: 'Actualiser les miroirs',
            minimalUI: 'Interface Minimale',
            fullUI: 'Interface Complète',
            checkingMirrors: 'Vérification des miroirs…',
            reChecking: 'Vérification en cours…',
            invidiousOK: 'Invidious OK',
            noMirrors: 'Aucun miroir disponible'
        },
        'de': {
            openInvidious: 'Invidious öffnen',
            refreshMirrors: 'Spiegel aktualisieren',
            minimalUI: 'Minimales UI',
            fullUI: 'Volles UI',
            checkingMirrors: 'Spiegel werden überprüft…',
            reChecking: 'Erneute Überprüfung…',
            invidiousOK: 'Invidious OK',
            noMirrors: 'Keine Spiegel verfügbar'
        },
        'zh-CN': {
            openInvidious: '打开 Invidious',
            refreshMirrors: '刷新镜像',
            minimalUI: '极简界面',
            fullUI: '完整界面',
            checkingMirrors: '正在检查镜像…',
            reChecking: '正在重新检查…',
            invidiousOK: 'Invidious 正常',
            noMirrors: '没有可用的镜像'
        },
        'ja': {
            openInvidious: 'Invidious を開く',
            refreshMirrors: 'ミラーを更新',
            minimalUI: 'ミニマル UI',
            fullUI: 'フル UI',
            checkingMirrors: 'ミラーを確認中…',
            reChecking: '再確認中…',
            invidiousOK: 'Invidious OK',
            noMirrors: '利用可能なミラーがありません'
        },
        'ru': {
            openInvidious: 'Открыть Invidious',
            refreshMirrors: 'Обновить зеркала',
            minimalUI: 'Минимальный интерфейс',
            fullUI: 'Полный интерфейс',
            checkingMirrors: 'Проверка зеркал…',
            reChecking: 'Повторная проверка…',
            invidiousOK: 'Invidious OK',
            noMirrors: 'Зеркала недоступны'
        },
        'pt': {
            openInvidious: 'Abrir Invidious',
            refreshMirrors: 'Atualizar espelhos',
            minimalUI: 'Interface Mínima',
            fullUI: 'Interface Completa',
            checkingMirrors: 'Verificando espelhos…',
            reChecking: 'Verificando novamente…',
            invidiousOK: 'Invidious OK',
            noMirrors: 'Nenhum espelho disponível'
        },
        'it': {
            openInvidious: 'Apri Invidious',
            refreshMirrors: 'Aggiorna mirror',
            minimalUI: 'Interfaccia Minimale',
            fullUI: 'Interfaccia Completa',
            checkingMirrors: 'Controllo dei mirror…',
            reChecking: 'Ricontrollo in corso…',
            invidiousOK: 'Invidious OK',
            noMirrors: 'Nessun mirror disponibile'
        },
        'ko': {
            openInvidious: 'Invidious 열기',
            refreshMirrors: '미러 새로고침',
            minimalUI: '최소 UI',
            fullUI: '완전 UI',
            checkingMirrors: '미러 확인 중…',
            reChecking: '다시 확인 중…',
            invidiousOK: 'Invidious OK',
            noMirrors: '사용 가능한 미러 없음'
        }
    };

    // Determine user language (fallback to English)
    const userLang = (navigator.language || navigator.userLanguage || 'en').split('-')[0];
    const lang = translations[userLang] ? userLang : 'en';
    const t = translations[lang];

    // ---------------------------
    // Mirror lists
    // ---------------------------
    const INVIDIOUS_MIRRORS = [
        "https://yewtu.be",
        "https://inv.tux.pizza",
        "https://invidious.privacydev.net",
        "https://invidious.protokolla.fi",
        "https://inv.nadeko.net",
        "https://invidious.nerdvpn.de",
        "https://invidious.f5.si"
    ];

    // ---------------------------
    // Config
    // ---------------------------
    const PING_TIMEOUT_MS = 2500;
    const MIRROR_CACHE_TTL_MS = 6 * 60 * 60 * 1000;

    const urlParams = new URLSearchParams(location.search);
    const hasVideo = urlParams.has('v');
    const videoId = urlParams.get('v');

    // ---------------------------
    // Minimal Layout Toggle
    // ---------------------------
    const MINIMAL_KEY = 'sc_minimal_layout';
    let minimalMode = GM_getValue(MINIMAL_KEY, true);
    let minimalStyleEl;

    function applyMinimalLayout(enable) {
        if (!hasVideo) return;

        // Create the <style> tag if it doesn't exist yet
        if (!minimalStyleEl) {
            minimalStyleEl = document.createElement('style');
            minimalStyleEl.id = 'sc-minimal-style';
            document.head.appendChild(minimalStyleEl);
        }

        // Minimal Mode active → adjust layout
        if (enable) {
            minimalStyleEl.textContent = `
                /* Hide unnecessary sections */
                .nav-menu, .hero-section, .input-section,
                #bmc-wbtn, .trending-container,
                .features-highlight, .testimonials-section,
                .infographic-section, .faq-section,
                .featured-section, .footer-container,
                .mobile-menu-toggle, .mob-nav-link,
                .ybug-launcher--active, .footer,
                .history-section {
                    display: none !important;
                }

                /* Remove container min-height + apply compact padding */
                .container {
                    width: 100% !important;
                    min-height: auto !important;
                    padding: 1rem !important;
                }

                /* Reduce body top padding */
                body {
                    padding-top: 48px !important;
                }
            `;
        } else {
            // Full Mode → restore original layout
            minimalStyleEl.textContent = `
                /* Restore original container settings */
                .container {
                    width: 100% !important;
                    min-height: 100vh !important;
                    padding: 1rem !important;
                }

                /* Restore original body top padding */
                body {
                    padding-top: 70px !important;
                }
            `;
        }
    }

    // Pas meteen de layout toe bij start
    applyMinimalLayout(minimalMode);

    // ---------------------------
    // Styles
    // ---------------------------
    GM_addStyle(`
        #sc-powertools {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 6px 0;
            margin: 10px 0;
            gap: 10px;
            flex-wrap: wrap;
        }
        #sc-powertools .sc-left {
            display: flex;
            gap: 8px;
            flex-wrap: wrap;
            align-items: center;
        }
        #sc-powertools .sc-btn {
            background: #222;
            color: #fff;
            padding: 6px 12px;
            border-radius: 6px;
            border: none;
            cursor: pointer;
            font-size: 13px;
            text-decoration: none;
            transition: background 0.2s ease-in-out;
        }
        #sc-powertools .sc-btn:hover { background: #444; }
        #sc-powertools .sc-btn[disabled] { opacity: 0.55; cursor: not-allowed; }
        #sc-profile-select {
            background:#333;
            color:#fff;
            padding:6px 10px;
            border-radius:6px;
            border:none;
            cursor:pointer;
            font-size:13px;
            margin-left:auto;
        }
        .sc-status {
            font-size: 13px;
            color: #777;
            margin-left: 5px;
        }
    `);

    // ---------------------------
    // Fastest mirror detection
    // ---------------------------
    function pingMirror(baseUrl) {
        return new Promise(resolve => {
            const started = performance.now();
            GM_xmlhttpRequest({
                method: "HEAD",
                url: baseUrl.replace(/\/+$/, "") + "/favicon.ico",
                timeout: PING_TIMEOUT_MS,
                onload: (res) => {
                    if (res.status === 200) {
                        resolve({ url: baseUrl, time: performance.now() - started });
                    } else resolve(null);
                },
                onerror: () => resolve(null),
                ontimeout: () => resolve(null)
            });
        });
    }

    async function pickFastestMirror(kind, list) {
        const cacheKey = `scpt_fastest_${kind}`;
        const tsKey = `${cacheKey}_ts`;
        const now = Date.now();
        const cached = GM_getValue(cacheKey, null);
        const cachedTs = GM_getValue(tsKey, 0);

        if (cached && (now - cachedTs) < MIRROR_CACHE_TTL_MS) return cached;

        const checks = await Promise.all(list.map(pingMirror));
        const working = checks.filter(Boolean).sort((a, b) => a.time - b.time);
        const fastest = working.length ? working[0].url : null;

        GM_setValue(cacheKey, fastest);
        GM_setValue(tsKey, now);
        return fastest;
    }

    // ---------------------------
    // Main container creation
    // ---------------------------
    function insertMirrorButtonsContainer() {
        if (!hasVideo || document.getElementById('sc-powertools')) return null;
        const videoInfo = document.querySelector('.video-info');
        if (!videoInfo) return null;

        const container = document.createElement('div');
        container.id = 'sc-powertools';

        const leftContainer = document.createElement('div');
        leftContainer.className = 'sc-left';
        container.appendChild(leftContainer);

        videoInfo.parentNode.insertBefore(container, videoInfo);
        return container;
    }

    // ---------------------------
    // Fill buttons & dropdown
    // ---------------------------
    async function fillMirrorButtons(container) {
        const leftContainer = container.querySelector('.sc-left');
        leftContainer.innerHTML = ''; // Reset buttons on refresh

        // Status text
        let status = container.querySelector('.sc-status');
        if (!status) {
            status = document.createElement('span');
            status.className = 'sc-status';
            leftContainer.appendChild(status);
        }
        status.textContent = t.checkingMirrors;

        const fastestInv = await pickFastestMirror('invidious', INVIDIOUS_MIRRORS);

        const makeBtn = (label, href) => {
            const a = document.createElement('a');
            a.className = 'sc-btn sc-mirror-btn';
            a.textContent = label;
            a.href = href;
            a.target = '_blank';
            a.rel = 'noopener noreferrer';
            return a;
        };

        if (fastestInv) leftContainer.appendChild(makeBtn(t.openInvidious, `${fastestInv}/watch?v=${videoId}`));

        // Refresh button
        let refresh = container.querySelector('.sc-refresh-btn');
        if (!refresh) {
            refresh = document.createElement('button');
            refresh.className = 'sc-btn sc-mirror-btn sc-refresh-btn';
            refresh.textContent = t.refreshMirrors;
            refresh.addEventListener('click', async () => {
                GM_setValue('scpt_fastest_invidious', null);
                GM_setValue('scpt_fastest_invidious_ts', 0);
                status.textContent = t.reChecking;
                await fillMirrorButtons(container);
            });
        }
        leftContainer.appendChild(refresh);

        // Profile selector
        let profileSelect = container.querySelector('#sc-profile-select');
        if (!profileSelect) {
            profileSelect = document.createElement('select');
            profileSelect.id = 'sc-profile-select';
            [t.minimalUI, t.fullUI].forEach((p, i) => {
                const o = document.createElement('option');
                o.value = i;
                o.textContent = p;
                profileSelect.appendChild(o);
            });
            profileSelect.value = minimalMode ? '0' : '1';
            profileSelect.addEventListener('change', e => {
                minimalMode = e.target.value === '0';
                GM_setValue(MINIMAL_KEY, minimalMode);
                applyMinimalLayout(minimalMode);
            });
            container.appendChild(profileSelect);
        }

        // Update status text
        status.textContent = fastestInv ? t.invidiousOK : t.noMirrors;
    }

    // ---------------------------
    // Bootstrap when ready
    // ---------------------------
    function bootWhenReady() {
        if (!hasVideo) return;
        const tryInit = () => {
            if (document.getElementById('sc-powertools')) return false;
            const container = insertMirrorButtonsContainer();
            if (container) { fillMirrorButtons(container); return true; }
            return false;
        };
        if (tryInit()) return;
        const mo = new MutationObserver(() => { if (tryInit()) mo.disconnect(); });
        mo.observe(document.documentElement, { childList: true, subtree: true });
    }

    if (hasVideo) {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', bootWhenReady, { once: true });
        } else {
            bootWhenReady();
        }
    }
})();