您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
SkipCut 高级工具 - 极简/完整用户界面 + 快速 Invidious 按钮
// ==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(); } } })();