您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
取代 YouTube 分享按鈕,複製不含追蹤參數的短網址到剪貼簿。
当前为
// ==UserScript== // @name YouTube 乾淨短網址分享器 // @name:en Clean YouTube Short URL Sharer // @name:zh-TW YouTube 乾淨短網址分享器 // @name:ja YouTubeクリーンショートURLシェア // @name:es Compartidor limpio de URL corta de YouTube // @name:de Sauberer YouTube-Kurzlink-Teiler // @description 取代 YouTube 分享按鈕,複製不含追蹤參數的短網址到剪貼簿。 // @description:en Replaces the YouTube Share button with a cleaner version that copies a tracking-free short URL using GM_setClipboard. // @description:zh-TW 取代 YouTube 分享按鈕,複製不含追蹤參數的短網址到剪貼簿。 // @description:ja YouTubeの共有ボタンを置き換え、トラッキングなしの短縮URLをクリップボードにコピーします。 // @description:es Reemplaza el botón de compartir de YouTube por uno que copia una URL corta sin rastreo al portapapeles. // @description:de Ersetzt die YouTube-Teilen-Schaltfläche durch eine saubere Version, die einen trackingfreien Kurzlink kopiert. // @author Max // @namespace https://github.com/Max46656 // @license MPL2.0 // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @match https://www.youtube.com/* // @match https://www.youtube.com/watch* // @grant GM_setClipboard // @grant GM.info // @version 1.0.1 // ==/UserScript== class YouTubeShortUrlCopier { constructor() { const existingCopier = document.querySelector('#yscsb-timestamp-checkbox-wrapper'); if(existingCopier){ return; } this.shareButtonSelector = '#actions yt-button-view-model button-view-model button'; this.notificationDuration = 1200; this.pollInterval = 100; this.maxAttempts = 30; this.attempts = 0; this.timestampEnabled = false; this.init(); } init() { this.i18n = new LocalizationManager(); this.injectStyles(); this.waitForShareButton(); } waitForShareButton() { const interval = setInterval(() => { const originalButton = document.querySelector(this.shareButtonSelector); this.attempts++; if (originalButton) { clearInterval(interval); this.replaceShareButton(originalButton); } else if (this.attempts >= this.maxAttempts) { clearInterval(interval); console.warn(this.i18n.get('max_retry', { name: GM_info.script.name })); } }, this.pollInterval); } replaceShareButton(originalButton) { const wrapper = this.createSegmentedShareButtons(originalButton); originalButton.parentNode.replaceChild(wrapper, originalButton); } createSegmentedShareButtons(originalButton) { const wrapper = document.createElement('div'); wrapper.className = 'ytSegmentedLikeDislikeButtonViewModelSegmentedButtonsWrapper'; const shareButton = this.createCustomShareButton(originalButton, true); const timestampButton = this.createTimestampCheckboxButton(); wrapper.appendChild(shareButton); wrapper.appendChild(timestampButton); return wrapper; } createCustomShareButton(originalButton, isSegmentedStart = false) { const button = document.createElement('button'); button.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-leading yt-spec-button-shape-next--enable-backdrop-filter-experiment'; if (isSegmentedStart) { button.classList.add('yt-spec-button-shape-next--segmented-start'); } const iconContainer = document.createElement('div'); iconContainer.className = 'yt-spec-button-shape-next__icon'; iconContainer.innerHTML = ` <span class="yt-icon-shape style-scope yt-icon yt-spec-icon-shape"> <div style="width: 100%; height: 100%; display: block; fill: currentcolor;"> <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" focusable="false" aria-hidden="true" style="pointer-events: none; display: inherit; width: 100%; height: 100%;"> <path d="M15 5.63 20.66 12 15 18.37V14h-1c-3.96 0-7.14 1-9.75 3.09 1.84-4.07 5.11-6.4 9.89-7.1l.86-.13V5.63M14 3v6C6.22 10.13 3.11 15.33 2 21c2.78-3.97 6.44-6 12-6v6l8-9-8-9z"></path> </svg> </div> </span> `; const textDiv = document.createElement('div'); //textDiv.className = 'yt-spec-button-shape-next__button-text-content'; textDiv.textContent = this.i18n.get('share'); const feedback = document.createElement('div'); feedback.className = 'yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response'; feedback.innerHTML = `<div class="yt-spec-touch-feedback-shape__stroke"></div><div class="yt-spec-touch-feedback-shape__fill"></div>`; button.appendChild(iconContainer); button.appendChild(textDiv); button.appendChild(feedback); button.addEventListener('click', () => this.handleButtonClick()); return button; } createTimestampCheckboxButton() { const existing = document.querySelector('#yscsb-timestamp-checkbox-wrapper'); if (existing) return existing; const wrapper = document.createElement('label'); wrapper.id = 'yscsb-timestamp-checkbox-wrapper'; wrapper.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-leading yt-spec-button-shape-next--segmented-end yt-spec-button-shape-next--enable-backdrop-filter-experiment'; wrapper.style.display = 'inline-flex'; wrapper.style.alignItems = 'center'; wrapper.style.cursor = 'pointer'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = 'yscsb-timestamp-checkbox'; checkbox.style.display = 'none'; checkbox.addEventListener('change', () => { this.timestampEnabled = checkbox.checked; wrapper.classList.toggle('selected', this.timestampEnabled); }); const iconContainer = document.createElement('div'); iconContainer.className = 'yt-spec-button-shape-next__icon'; iconContainer.innerHTML = ` <svg viewBox="0 0 24 24" width="24" height="24"> <path d="M12 1.5C6.2 1.5 1.5 6.2 1.5 12S6.2 22.5 12 22.5 22.5 17.8 22.5 12 17.8 1.5 12 1.5zm0 19c-4.7 0-8.5-3.8-8.5-8.5S7.3 3.5 12 3.5 20.5 7.3 20.5 12 16.7 20.5 12 20.5zm.75-13v4.25H17v1.5h-5.25V7.5h1.5z"/> </svg> `; const textDiv = document.createElement('div'); //textDiv.className = 'yt-spec-button-shape-next__button-text-content'; textDiv.textContent = this.i18n.get('timestamp'); const feedback = document.createElement('div'); feedback.className = 'yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response'; feedback.innerHTML = `<div class="yt-spec-touch-feedback-shape__stroke"></div><div class="yt-spec-touch-feedback-shape__fill"></div>`; wrapper.appendChild(checkbox); wrapper.appendChild(iconContainer); wrapper.appendChild(textDiv); wrapper.appendChild(feedback); return wrapper; } handleButtonClick() { const shortUrl = this.getShortUrl(); if (shortUrl) { this.copyToClipboard(shortUrl); this.showNotification(this.i18n.get('copied', { name: GM_info.script.name, url: shortUrl })); } else { this.showNotification(this.i18n.get('format_error', { name: GM_info.script.name })); } } getShortUrl() { const currentUrl = new URL(window.location.href); const videoId = currentUrl.searchParams.get('v'); let shortUrl = videoId ? `https://youtu.be/${videoId}` : null; if (shortUrl && this.timestampEnabled) { const videoTime = document.querySelector('video').currentTime; const time = Math.floor(videoTime ? videoTime : 0); if (time > 0) shortUrl += `?t=${time}`; } return shortUrl; } copyToClipboard(text) { GM_setClipboard(text, 'text'); } showNotification(message) { const notification = document.createElement('div'); notification.textContent = message; Object.assign(notification.style, { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: 'rgba(0, 0, 0, 0.7)', color: 'white', padding: '10px 16px', borderRadius: '8px', zIndex: '10000', fontSize: '14px', whiteSpace: 'nowrap' }); document.body.appendChild(notification); setTimeout(() => notification.remove(), this.notificationDuration); } injectStyles() { const style = document.createElement('style'); style.textContent = ` #yscsb-timestamp-checkbox-wrapper.selected { background-color: #c4302b; color: white; } #yscsb-timestamp-checkbox-wrapper.selected svg path { fill: white; } `; document.head.appendChild(style); } } class LocalizationManager { constructor() { const lang = navigator.language.toLowerCase(); if (lang.startsWith('zh')) this.lang = 'zh'; else if (lang.startsWith('ja')) this.lang = 'ja'; else if (lang.startsWith('es')) this.lang = 'es'; else if (lang.startsWith('de')) this.lang = 'de'; else this.lang = 'en'; } get(key, replacements = {}) { const template = this.messages[this.lang][key] || this.messages['en'][key] || key; return template.replace(/\{(\w+)\}/g, (_, name) => replacements[name] || ''); } messages = { en: { copied: "{name}:short URL copied: {url}", format_error: "{name}:short URL format changed, please wait for script update", no_title: "{name}:could not find <title> element", max_retry: "{name}:exceeded retry limit, share button not found", share: "Share", timestamp: "Timestamp" }, zh: { copied: "{name}:縮網址已被複製: {url}", format_error: "{name}:縮網址格式改變,請等待腳本更新", no_title: "{name}:找不到 <title> 元素", max_retry: "{name}:超過最大重試次數,無法找到分享按鈕", share: "分享", timestamp: "時間戳" }, ja: { copied: "{name}:の短縮URLをコピーしました: {url}", format_error: "{name}:の短縮URL形式が変更されました。スクリプトの更新をお待ちください", no_title: "{name}:は <title> 要素を見つけられませんでした", max_retry: "{name}:は最大試行回數を超え、共有ボタンが見つかりません", share: "共有", timestamp: "タイムスタンプ" }, es: { copied: "{name}:URL corta copiada: {url}", format_error: "{name}:El formato de URL corta ha cambiado. Espera una actualización del script", no_title: "{name}:no pudo encontrar el elemento <title>", max_retry: "{name}:superó el número máximo de intentos. Botón de compartir no encontrado", share: "Compartir", timestamp: "Hora" }, de: { copied: "{name}:Kurzlink wurde kopiert: {url}", format_error: "{name}:Kurzlink-Format wurde geändert. Bitte auf ein Skript-Update warten", no_title: "{name}:konnte das <title>-Element nicht finden", max_retry: "{name}:hat die maximale Anzahl an Versuchen überschritten. Teilen-Schaltfläche nicht gefunden", share: "Teilen", timestamp: "Zeitstempel" } }; } class TitleObserver { constructor(onNavigate) { this.currentTitle = document.title; this.onNavigate = onNavigate; this.observe(); } observe() { const titleElement = document.querySelector('title'); if (!titleElement) { console.warn('找不到 <title> 元素'); return; } const observer = new MutationObserver(() => { if (document.title !== this.currentTitle) { this.currentTitle = document.title; if (window.location.href.startsWith('https://www.youtube.com/watch?v=')) { this.onNavigate(); } } }); observer.observe(titleElement, { childList: true }); } } const johnTheTrackingStoper = new YouTubeShortUrlCopier(); new TitleObserver(() => johnTheTrackingStoper.waitForShareButton());
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址